Skip to content

Commit 75b84af

Browse files
committed
Enable more SigV4a tests
1 parent f2ae996 commit 75b84af

File tree

4 files changed

+72
-30
lines changed

4 files changed

+72
-30
lines changed

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

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@
44
*/
55
package aws.smithy.kotlin.runtime.auth.awssigning
66

7+
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
8+
import aws.smithy.kotlin.runtime.collections.LruCache
9+
import aws.smithy.kotlin.runtime.collections.ReadThroughCache
710
import aws.smithy.kotlin.runtime.content.BigInteger
811
import aws.smithy.kotlin.runtime.hashing.HashSupplier
912
import aws.smithy.kotlin.runtime.hashing.Sha256
1013
import aws.smithy.kotlin.runtime.hashing.ecdsasecp256r1
1114
import aws.smithy.kotlin.runtime.hashing.hmac
1215
import aws.smithy.kotlin.runtime.text.encoding.decodeHexBytes
1316
import aws.smithy.kotlin.runtime.text.encoding.encodeToHex
17+
import aws.smithy.kotlin.runtime.time.Instant
18+
import aws.smithy.kotlin.runtime.util.ExpiringValue
19+
import kotlinx.coroutines.runBlocking
20+
import kotlin.time.Duration.Companion.hours
1421

1522
/**
1623
* The maximum number of iterations to attempt private key derivation using KDF in counter mode
@@ -19,37 +26,43 @@ import aws.smithy.kotlin.runtime.text.encoding.encodeToHex
1926
internal const val MAX_KDF_COUNTER_ITERATIONS = 254.toByte()
2027

2128
internal class SigV4aSignatureCalculator(override val sha256Provider: HashSupplier = ::Sha256) : SigV4xSignatureCalculator(AwsSigningAlgorithm.SIGV4_ASYMMETRIC, sha256Provider) {
29+
private val privateKeyCache = ReadThroughCache<Credentials, ByteArray>(
30+
minimumSweepPeriod = 1.hours // note: Sweeps are effectively a no-op because expiration is [Instant.MAX_VALUE]
31+
)
32+
2233
override fun calculate(signingKey: ByteArray, stringToSign: String): String = ecdsasecp256r1(signingKey, stringToSign.encodeToByteArray()).encodeToHex()
2334

2435
// 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
36+
override fun signingKey(config: AwsSigningConfig): ByteArray = runBlocking {
37+
privateKeyCache.get(config.credentials) {
38+
var counter: Byte = 1
39+
var privateKey: ByteArray
2840

29-
// N value from NIST P-256 curve, minus two.
30-
val nMinusTwo = "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F".decodeHexBytes().toPositiveBigInteger()
41+
// N value from NIST P-256 curve, minus two.
42+
val nMinusTwo = "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F".decodeHexBytes().toPositiveBigInteger()
3143

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()
44+
// FIXME Public docs say secret access key needs to be Base64 encoded, that's not right.
45+
// (or maybe it's already base64-encoded, and they are just repeating it)
46+
val inputKey = ("AWS4A" + config.credentials.secretAccessKey).encodeToByteArray()
3547

36-
do {
37-
// 1.2: Compute K0
38-
val k0 = hmac(inputKey, fixedInputString(config.credentials.accessKeyId, counter), sha256Provider)
48+
do {
49+
// 1.2: Compute K0
50+
val k0 = hmac(inputKey, fixedInputString(config.credentials.accessKeyId, counter), sha256Provider)
3951

40-
// 2: Compute the ECC key pair
41-
val c = k0.toPositiveBigInteger()
52+
// 2: Compute the ECC key pair
53+
val c = k0.toPositiveBigInteger()
4254

43-
privateKey = (c + BigInteger("1")).toByteArray()
55+
privateKey = (c + BigInteger("1")).toByteArray()
4456

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)
57+
if (counter == MAX_KDF_COUNTER_ITERATIONS && c > nMinusTwo) {
58+
throw IllegalStateException("Counter exceeded maximum length")
59+
} else {
60+
counter++
61+
}
62+
} while (c > nMinusTwo)
5163

52-
return privateKey
64+
ExpiringValue<ByteArray>(privateKey, Instant.MAX_VALUE)
65+
}
5366
}
5467

5568
/**

runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultBasicSigningTest.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,7 @@
55
package aws.smithy.kotlin.runtime.auth.awssigning
66

77
import aws.smithy.kotlin.runtime.auth.awssigning.tests.BasicSigningTestBase
8-
import kotlinx.coroutines.test.TestResult
9-
import kotlin.test.Ignore
10-
import kotlin.test.Test
118

129
class DefaultBasicSigningTest : BasicSigningTestBase() {
1310
override val signer: AwsSigner = DefaultAwsSigner
14-
15-
@Ignore
16-
@Test
17-
override fun testSignRequestSigV4Asymmetric(): TestResult = TODO("Add support for SigV4a in default signer")
1811
}

runtime/auth/aws-signing-default/common/test/aws/smithy/kotlin/runtime/auth/awssigning/DefaultRequestMutatorTest.kt

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ import kotlin.test.assertEquals
1616

1717
class DefaultRequestMutatorTest {
1818
@Test
19-
fun testAppendAuthHeader() {
19+
fun testSigV4AppendAuthHeader() {
2020
val canonical = CanonicalRequest(baseRequest.toBuilder(), "", "action;host;x-amz-date", "")
2121
val signature = "0123456789abcdef"
2222

2323
val config = AwsSigningConfig {
24+
algorithm = AwsSigningAlgorithm.SIGV4
2425
region = "us-west-2"
2526
service = "fooservice"
2627
signingDate = Instant.fromIso8601("20220427T012345Z")
@@ -45,6 +46,41 @@ class DefaultRequestMutatorTest {
4546

4647
assertEquals(expectedHeaders, mutated.headers.entries())
4748
}
49+
50+
@Test
51+
fun testSigV4aAppendAuthHeader() {
52+
val canonical = CanonicalRequest(baseRequest.toBuilder(), "", "action;host;x-amz-date", "")
53+
val signature = "0123456789abcdef"
54+
55+
val config = AwsSigningConfig {
56+
algorithm = AwsSigningAlgorithm.SIGV4_ASYMMETRIC
57+
region = "us-west-2"
58+
service = "fooservice"
59+
signingDate = Instant.fromIso8601("20220427T012345Z")
60+
credentials = Credentials("", "secret key")
61+
omitSessionToken = true
62+
}
63+
64+
val mutated = RequestMutator.Default.appendAuth(config, canonical, signature)
65+
66+
assertEquals(baseRequest.method, mutated.method)
67+
assertEquals(baseRequest.url.toString(), mutated.url.toString())
68+
assertEquals(baseRequest.body, mutated.body)
69+
70+
val expectedCredentialScope = "20220427/fooservice/aws4_request"
71+
val expectedAuthValue = buildString {
72+
append("AWS4-ECDSA-P256-SHA256 ")
73+
append("Credential=${config.credentials.accessKeyId}/$expectedCredentialScope, ")
74+
append("SignedHeaders=${canonical.signedHeaders}, ")
75+
append("Signature=$signature")
76+
}
77+
val expectedHeaders = Headers {
78+
appendAll(baseRequest.headers)
79+
append("Authorization", expectedAuthValue)
80+
}.entries()
81+
82+
assertEquals(expectedHeaders, mutated.headers.entries())
83+
}
4884
}
4985

5086
private val baseRequest = HttpRequest {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import kotlinx.coroutines.test.runTest
1414
import kotlin.test.Test
1515
import kotlin.test.assertEquals
1616

17-
class DefaultSignatureCalculatorTest {
17+
class SigV4SignatureCalculatorTest {
1818
// Test adapted from https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
1919
@Test
2020
fun testCalculate() {

0 commit comments

Comments
 (0)