Skip to content

Commit 0c49144

Browse files
committed
(working) Refactor to separate files
1 parent dc51c46 commit 0c49144

File tree

4 files changed

+196
-165
lines changed

4 files changed

+196
-165
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.auth.awssigning
6+
7+
import aws.smithy.kotlin.runtime.hashing.HashSupplier
8+
import aws.smithy.kotlin.runtime.hashing.Sha256
9+
import aws.smithy.kotlin.runtime.hashing.hmac
10+
import aws.smithy.kotlin.runtime.text.encoding.encodeToHex
11+
import aws.smithy.kotlin.runtime.time.TimestampFormat
12+
13+
internal class SigV4SignatureCalculator(override val sha256Provider: HashSupplier = ::Sha256) : SigV4xSignatureCalculator(AwsSigningAlgorithm.SIGV4, sha256Provider) {
14+
override fun calculate(signingKey: ByteArray, stringToSign: String): String =
15+
hmac(signingKey, stringToSign.encodeToByteArray(), sha256Provider).encodeToHex()
16+
17+
override fun signingKey(config: AwsSigningConfig): ByteArray {
18+
fun hmac(key: ByteArray, message: String) = hmac(key, message.encodeToByteArray(), sha256Provider)
19+
20+
val initialKey = ("AWS4" + config.credentials.secretAccessKey).encodeToByteArray()
21+
val kDate = hmac(initialKey, config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED_DATE))
22+
val kRegion = hmac(kDate, config.region)
23+
val kService = hmac(kRegion, config.service)
24+
return hmac(kService, "aws4_request")
25+
}
26+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.auth.awssigning
6+
7+
import aws.smithy.kotlin.runtime.content.BigInteger
8+
import aws.smithy.kotlin.runtime.hashing.HashSupplier
9+
import aws.smithy.kotlin.runtime.hashing.Sha256
10+
import aws.smithy.kotlin.runtime.hashing.ecdsasecp256r1
11+
import aws.smithy.kotlin.runtime.hashing.hmac
12+
import aws.smithy.kotlin.runtime.text.encoding.decodeHexBytes
13+
import aws.smithy.kotlin.runtime.text.encoding.encodeToHex
14+
15+
/**
16+
* The maximum number of iterations to attempt private key derivation using KDF in counter mode
17+
* Taken from CRT: https://github.com/awslabs/aws-c-auth/blob/e8360a65e0f3337d4ac827945e00c3b55a641a5f/source/key_derivation.c#L22
18+
*/
19+
internal const val MAX_KDF_COUNTER_ITERATIONS = 254.toByte()
20+
21+
internal class SigV4aSignatureCalculator(override val sha256Provider: HashSupplier = ::Sha256) : SigV4xSignatureCalculator(AwsSigningAlgorithm.SIGV4_ASYMMETRIC, sha256Provider) {
22+
override fun calculate(signingKey: ByteArray, stringToSign: String): String = ecdsasecp256r1(signingKey, stringToSign.encodeToByteArray()).encodeToHex()
23+
24+
// See https://github.com/awslabs/aws-c-auth/blob/e8360a65e0f3337d4ac827945e00c3b55a641a5f/source/key_derivation.c#L70 for more details of derivation process
25+
override fun signingKey(config: AwsSigningConfig): ByteArray {
26+
var counter: Byte = 1
27+
var privateKey: ByteArray
28+
29+
// N value from NIST P-256 curve, minus two.
30+
val nMinusTwo = BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F".decodeHexBytes())
31+
32+
// FIXME Public docs say secret access key needs to be Base64 encoded, that's not right.
33+
// (or maybe it's already base64-encoded, and they are just repeating it)
34+
val inputKey = ("AWS4A" + config.credentials.secretAccessKey).encodeToByteArray()
35+
36+
do {
37+
// 1.2: Compute K0
38+
val k0 = hmac(inputKey, fixedInputString(config.credentials.accessKeyId, counter), sha256Provider)
39+
40+
// 2: Compute the ECC key pair
41+
val c = BigInteger(k0)
42+
43+
privateKey = (c + BigInteger("1")).toByteArray()
44+
45+
if (counter == MAX_KDF_COUNTER_ITERATIONS && c > nMinusTwo) {
46+
throw IllegalStateException("Counter exceeded maximum length")
47+
} else {
48+
counter++
49+
}
50+
} while (c > nMinusTwo)
51+
52+
return privateKey
53+
}
54+
55+
/**
56+
* Computes the fixed input string used for ECDSA private key derivation
57+
* The final output looks like:
58+
* 0x00000001 || "AWS4-ECDSA-P256-SHA256" || 0x00 || AccessKeyId || counter || 0x00000100
59+
*/
60+
private fun fixedInputString(accessKeyId: String, counter: Byte): ByteArray =
61+
byteArrayOf(0x00, 0x00, 0x00, 0x01) + // FIXME CRT implementation (4 bytes) and internal docs (1 byte) conflict.
62+
"AWS4-ECDSA-P256-SHA256".encodeToByteArray() +
63+
byteArrayOf(0x00) +
64+
accessKeyId.encodeToByteArray() +
65+
counter +
66+
byteArrayOf(0x00, 0x00, 0x01, 0x00) // FIXME CRT implementation (4 bytes) and internal docs (2 bytes) conflict.
67+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.auth.awssigning
6+
7+
import aws.smithy.kotlin.runtime.hashing.HashSupplier
8+
import aws.smithy.kotlin.runtime.hashing.Sha256
9+
import aws.smithy.kotlin.runtime.hashing.hash
10+
import aws.smithy.kotlin.runtime.hashing.sha256
11+
import aws.smithy.kotlin.runtime.text.encoding.encodeToHex
12+
import aws.smithy.kotlin.runtime.time.Instant
13+
import aws.smithy.kotlin.runtime.time.TimestampFormat
14+
import aws.smithy.kotlin.runtime.time.epochMilliseconds
15+
16+
/**
17+
* Common signature implementation used for SigV4 and SigV4a, primarily for forming the strings-to-sign which don't differ
18+
* between the two signing algorithms (besides their names).
19+
*/
20+
internal abstract class SigV4xSignatureCalculator(
21+
val algorithm: AwsSigningAlgorithm,
22+
open val sha256Provider: HashSupplier = ::Sha256,
23+
) : SignatureCalculator {
24+
init {
25+
check(algorithm == AwsSigningAlgorithm.SIGV4 || algorithm == AwsSigningAlgorithm.SIGV4_ASYMMETRIC) {
26+
"This class should only be used for the ${AwsSigningAlgorithm.SIGV4} or ${AwsSigningAlgorithm.SIGV4_ASYMMETRIC} algorithms, got $algorithm"
27+
}
28+
}
29+
30+
override fun stringToSign(canonicalRequest: String, config: AwsSigningConfig): String = buildString {
31+
appendLine(algorithm.signingName)
32+
appendLine(config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED))
33+
appendLine(config.credentialScope)
34+
append(canonicalRequest.encodeToByteArray().hash(sha256Provider).encodeToHex())
35+
}
36+
37+
override fun chunkStringToSign(chunkBody: ByteArray, prevSignature: ByteArray, config: AwsSigningConfig): String = buildString {
38+
appendLine("${algorithm.signingName}-PAYLOAD")
39+
appendLine(config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED))
40+
appendLine(config.credentialScope)
41+
appendLine(prevSignature.decodeToString()) // Should already be a byte array of ASCII hex chars
42+
43+
val nonSignatureHeadersHash = when (config.signatureType) {
44+
AwsSignatureType.HTTP_REQUEST_EVENT -> eventStreamNonSignatureHeaders(config.signingDate)
45+
else -> HashSpecification.EmptyBody.hash
46+
}
47+
48+
appendLine(nonSignatureHeadersHash)
49+
append(chunkBody.hash(sha256Provider).encodeToHex())
50+
}
51+
52+
override fun chunkTrailerStringToSign(trailingHeaders: ByteArray, prevSignature: ByteArray, config: AwsSigningConfig): String =
53+
buildString {
54+
appendLine("${algorithm.signingName}-TRAILER")
55+
appendLine(config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED))
56+
appendLine(config.credentialScope)
57+
appendLine(prevSignature.decodeToString())
58+
append(trailingHeaders.hash(sha256Provider).encodeToHex())
59+
}
60+
}
61+
62+
internal val AwsSigningAlgorithm.signingName: String
63+
get() = when (this) {
64+
AwsSigningAlgorithm.SIGV4 -> "AWS4-HMAC-SHA256"
65+
AwsSigningAlgorithm.SIGV4_ASYMMETRIC -> "AWS4-ECDSA-P256-SHA256"
66+
}
67+
68+
69+
private const val HEADER_TIMESTAMP_TYPE: Byte = 8
70+
71+
/**
72+
* Return the sha256 hex representation of the encoded event stream date header
73+
*
74+
* ```
75+
* sha256Hex( Header(":date", HeaderValue::Timestamp(date)).encodeToByteArray() )
76+
* ```
77+
*
78+
* NOTE: This duplicates parts of the event stream encoding implementation here to avoid a direct dependency.
79+
* Should this become more involved than encoding a single date header we should reconsider this choice.
80+
*
81+
* see [Event Stream implementation](https://github.com/awslabs/aws-sdk-kotlin/blob/v0.16.4-beta/aws-runtime/protocols/aws-event-stream/common/src/aws/sdk/kotlin/runtime/protocol/eventstream/Header.kt#L51)
82+
*/
83+
private fun eventStreamNonSignatureHeaders(date: Instant): String {
84+
val bytes = ByteArray(15)
85+
// encode header name
86+
val name = ":date".encodeToByteArray()
87+
var offset = 0
88+
bytes[offset++] = name.size.toByte()
89+
name.copyInto(bytes, destinationOffset = offset)
90+
offset += name.size
91+
92+
// encode header value
93+
bytes[offset++] = HEADER_TIMESTAMP_TYPE
94+
writeLongBE(bytes, offset, date.epochMilliseconds)
95+
return bytes.sha256().encodeToHex()
96+
}
97+
98+
private fun writeLongBE(dest: ByteArray, offset: Int, x: Long) {
99+
var idx = offset
100+
for (i in 7 downTo 0) {
101+
dest[idx++] = ((x ushr (i * 8)) and 0xff).toByte()
102+
}
103+
}

runtime/auth/aws-signing-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/SignatureCalculator.kt

Lines changed: 0 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@
44
*/
55
package aws.smithy.kotlin.runtime.auth.awssigning
66

7-
import aws.smithy.kotlin.runtime.content.BigInteger
8-
import aws.smithy.kotlin.runtime.hashing.*
9-
import aws.smithy.kotlin.runtime.text.encoding.decodeHexBytes
10-
import aws.smithy.kotlin.runtime.text.encoding.encodeToHex
11-
import aws.smithy.kotlin.runtime.time.Instant
12-
import aws.smithy.kotlin.runtime.time.TimestampFormat
13-
import aws.smithy.kotlin.runtime.time.epochMilliseconds
14-
157
/**
168
* An object that can calculate signatures based on canonical requests.
179
*/
@@ -69,160 +61,3 @@ internal interface SignatureCalculator {
6961
*/
7062
fun chunkTrailerStringToSign(trailingHeaders: ByteArray, prevSignature: ByteArray, config: AwsSigningConfig): String
7163
}
72-
73-
/**
74-
* Common signature implementation used for SigV4 and SigV4a, primarily for forming the strings-to-sign which don't differ
75-
* between the two signing algorithms (besides their names).
76-
*/
77-
internal abstract class SigV4xSignatureCalculator(
78-
val algorithm: AwsSigningAlgorithm,
79-
open val sha256Provider: HashSupplier = ::Sha256,
80-
) : SignatureCalculator {
81-
init {
82-
check(algorithm == AwsSigningAlgorithm.SIGV4 || algorithm == AwsSigningAlgorithm.SIGV4_ASYMMETRIC) {
83-
"This class should only be used for the ${AwsSigningAlgorithm.SIGV4} or ${AwsSigningAlgorithm.SIGV4_ASYMMETRIC} algorithms, got $algorithm"
84-
}
85-
}
86-
87-
override fun stringToSign(canonicalRequest: String, config: AwsSigningConfig): String = buildString {
88-
appendLine(algorithm.signingName)
89-
appendLine(config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED))
90-
appendLine(config.credentialScope)
91-
append(canonicalRequest.encodeToByteArray().hash(sha256Provider).encodeToHex())
92-
}
93-
94-
override fun chunkStringToSign(chunkBody: ByteArray, prevSignature: ByteArray, config: AwsSigningConfig): String = buildString {
95-
appendLine("${algorithm.signingName}-PAYLOAD")
96-
appendLine(config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED))
97-
appendLine(config.credentialScope)
98-
appendLine(prevSignature.decodeToString()) // Should already be a byte array of ASCII hex chars
99-
100-
val nonSignatureHeadersHash = when (config.signatureType) {
101-
AwsSignatureType.HTTP_REQUEST_EVENT -> eventStreamNonSignatureHeaders(config.signingDate)
102-
else -> HashSpecification.EmptyBody.hash
103-
}
104-
105-
appendLine(nonSignatureHeadersHash)
106-
append(chunkBody.hash(sha256Provider).encodeToHex())
107-
}
108-
109-
override fun chunkTrailerStringToSign(trailingHeaders: ByteArray, prevSignature: ByteArray, config: AwsSigningConfig): String =
110-
buildString {
111-
appendLine("${algorithm.signingName}-TRAILER")
112-
appendLine(config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED))
113-
appendLine(config.credentialScope)
114-
appendLine(prevSignature.decodeToString())
115-
append(trailingHeaders.hash(sha256Provider).encodeToHex())
116-
}
117-
}
118-
119-
internal val AwsSigningAlgorithm.signingName: String
120-
get() = when (this) {
121-
AwsSigningAlgorithm.SIGV4 -> "AWS4-HMAC-SHA256"
122-
AwsSigningAlgorithm.SIGV4_ASYMMETRIC -> "AWS4-ECDSA-P256-SHA256"
123-
}
124-
125-
internal class SigV4SignatureCalculator(override val sha256Provider: HashSupplier = ::Sha256) : SigV4xSignatureCalculator(AwsSigningAlgorithm.SIGV4, sha256Provider) {
126-
override fun calculate(signingKey: ByteArray, stringToSign: String): String =
127-
hmac(signingKey, stringToSign.encodeToByteArray(), sha256Provider).encodeToHex()
128-
129-
override fun signingKey(config: AwsSigningConfig): ByteArray {
130-
fun hmac(key: ByteArray, message: String) = hmac(key, message.encodeToByteArray(), sha256Provider)
131-
132-
val initialKey = ("AWS4" + config.credentials.secretAccessKey).encodeToByteArray()
133-
val kDate = hmac(initialKey, config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED_DATE))
134-
val kRegion = hmac(kDate, config.region)
135-
val kService = hmac(kRegion, config.service)
136-
return hmac(kService, "aws4_request")
137-
}
138-
}
139-
140-
/**
141-
* The maximum number of iterations to attempt private key derivation using KDF in counter mode
142-
* Taken from CRT: https://github.com/awslabs/aws-c-auth/blob/e8360a65e0f3337d4ac827945e00c3b55a641a5f/source/key_derivation.c#L22
143-
*/
144-
internal const val MAX_KDF_COUNTER_ITERATIONS = 254.toByte()
145-
146-
internal class SigV4aSignatureCalculator(override val sha256Provider: HashSupplier = ::Sha256) : SigV4xSignatureCalculator(AwsSigningAlgorithm.SIGV4_ASYMMETRIC, sha256Provider) {
147-
override fun calculate(signingKey: ByteArray, stringToSign: String): String = ecdsasecp256r1(signingKey, stringToSign.encodeToByteArray()).encodeToHex()
148-
149-
// See https://github.com/awslabs/aws-c-auth/blob/e8360a65e0f3337d4ac827945e00c3b55a641a5f/source/key_derivation.c#L70 for more details of derivation process
150-
override fun signingKey(config: AwsSigningConfig): ByteArray {
151-
var counter: Byte = 1
152-
var privateKey: ByteArray
153-
154-
// N value from NIST P-256 curve, minus two.
155-
val nMinusTwo = BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F".decodeHexBytes())
156-
157-
// FIXME Public docs say secret access key needs to be Base64 encoded, that's not right.
158-
// (or maybe it's already base64-encoded, and they are just repeating it)
159-
val inputKey = ("AWS4A" + config.credentials.secretAccessKey).encodeToByteArray()
160-
161-
do {
162-
// 1.2: Compute K0
163-
val k0 = hmac(inputKey, fixedInputString(config.credentials.accessKeyId, counter), sha256Provider)
164-
165-
// 2: Compute the ECC key pair
166-
val c = BigInteger(k0)
167-
168-
privateKey = (c + BigInteger("1")).toByteArray()
169-
170-
if (counter == MAX_KDF_COUNTER_ITERATIONS && c > nMinusTwo) {
171-
throw IllegalStateException("Counter exceeded maximum length")
172-
} else {
173-
counter++
174-
}
175-
} while (c > nMinusTwo)
176-
177-
return privateKey
178-
}
179-
180-
/**
181-
* Computes the fixed input string used for ECDSA private key derivation
182-
* The final output looks like:
183-
* 0x00000001 || "AWS4-ECDSA-P256-SHA256" || 0x00 || AccessKeyId || counter || 0x00000100
184-
*/
185-
private fun fixedInputString(accessKeyId: String, counter: Byte): ByteArray =
186-
byteArrayOf(0x00, 0x00, 0x00, 0x01) + // FIXME CRT implementation (4 bytes) and internal docs (1 byte) conflict.
187-
"AWS4-ECDSA-P256-SHA256".encodeToByteArray() +
188-
byteArrayOf(0x00) +
189-
accessKeyId.encodeToByteArray() +
190-
counter +
191-
byteArrayOf(0x00, 0x00, 0x01, 0x00) // FIXME CRT implementation (4 bytes) and internal docs (2 bytes) conflict.
192-
}
193-
194-
private const val HEADER_TIMESTAMP_TYPE: Byte = 8
195-
196-
/**
197-
* Return the sha256 hex representation of the encoded event stream date header
198-
*
199-
* ```
200-
* sha256Hex( Header(":date", HeaderValue::Timestamp(date)).encodeToByteArray() )
201-
* ```
202-
*
203-
* NOTE: This duplicates parts of the event stream encoding implementation here to avoid a direct dependency.
204-
* Should this become more involved than encoding a single date header we should reconsider this choice.
205-
*
206-
* see [Event Stream implementation](https://github.com/awslabs/aws-sdk-kotlin/blob/v0.16.4-beta/aws-runtime/protocols/aws-event-stream/common/src/aws/sdk/kotlin/runtime/protocol/eventstream/Header.kt#L51)
207-
*/
208-
private fun eventStreamNonSignatureHeaders(date: Instant): String {
209-
val bytes = ByteArray(15)
210-
// encode header name
211-
val name = ":date".encodeToByteArray()
212-
var offset = 0
213-
bytes[offset++] = name.size.toByte()
214-
name.copyInto(bytes, destinationOffset = offset)
215-
offset += name.size
216-
217-
// encode header value
218-
bytes[offset++] = HEADER_TIMESTAMP_TYPE
219-
writeLongBE(bytes, offset, date.epochMilliseconds)
220-
return bytes.sha256().encodeToHex()
221-
}
222-
223-
private fun writeLongBE(dest: ByteArray, offset: Int, x: Long) {
224-
var idx = offset
225-
for (i in 7 downTo 0) {
226-
dest[idx++] = ((x ushr (i * 8)) and 0xff).toByte()
227-
}
228-
}

0 commit comments

Comments
 (0)