generated from amazon-archives/__template_Apache-2.0
-
Notifications
You must be signed in to change notification settings - Fork 30
feat: Kotlin implementation of SigV4a signing #1246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 36 commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
59c73d5
Commit latest changes
lauzadis fa7f73e
Commit latest changes
lauzadis 071defd
SigV4a request working!
lauzadis f2bd711
SigV4a
lauzadis 0623841
Merge branch 'main' of github.com:smithy-lang/smithy-kotlin into sigv4a
lauzadis 5f9786c
Expand on comment
lauzadis 52df61d
Commonize into `Sigv4xSignatureCalculator`
lauzadis 00b62ee
optimize with nMinusTwo
lauzadis a6a2a78
ktlint
lauzadis dc51c46
Definitely working now.
lauzadis 0c49144
(working) Refactor to separate files
lauzadis 580925b
Ensure BigIntegers N and C are positive
lauzadis f2ae996
ktlint
lauzadis 75b84af
Enable more SigV4a tests
lauzadis 7303ac7
kt
lauzadis 23364f7
Remove FIXME, signing region set config is already routed correctly
lauzadis 7b81166
Add some docs
lauzadis a34b9b5
Refactor tests
lauzadis 3e897b1
Refactor nMinusTwo to a const
lauzadis 5d7de0c
changelog
lauzadis e5d6d3c
Deprecate UnsupportedSigningAlgorithmException
lauzadis b2afdf5
Fix a typo
lauzadis e14154c
Rename ecdsaSecp256r1
lauzadis 4761322
Always prepend 0x00
lauzadis 53cb567
Rename `BaseSigV4SignatureCalculator`
lauzadis 9c69efe
Refactor to a single `supportedAlgorithms` list
lauzadis c1c4e7c
Add `signingName` to `AwsSigningAlgorithm` class
lauzadis d61e010
Refresh stale link
lauzadis e054ee1
Using signingName in fixedInputString
lauzadis c931684
apiDump
lauzadis 29b34aa
ktlint
lauzadis 08a7ef8
Add SigV4aSignatureCalculatorTest
lauzadis fb52307
Add a TODO
lauzadis 41cf492
Remove duplicated test suite
lauzadis 9e09404
Normalize CRLF
lauzadis 0aa93f7
ktlint
lauzadis 3023dc6
Unescaped CRLF
lauzadis ec832b1
Fix mixing of KDocs and TODOs/FIXMEs
lauzadis 12a2c0d
Embed signingName as a constructor parameter
lauzadis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "id": "4b6debe1-7706-484a-8599-ef8c14cecde2", | ||
| "type": "feature", | ||
| "description": "Add SigV4a support to the default AWS signer" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
...ault/common/src/aws/smithy/kotlin/runtime/auth/awssigning/BaseSigV4SignatureCalculator.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package aws.smithy.kotlin.runtime.auth.awssigning | ||
|
|
||
| import aws.smithy.kotlin.runtime.hashing.HashSupplier | ||
| import aws.smithy.kotlin.runtime.hashing.Sha256 | ||
| import aws.smithy.kotlin.runtime.hashing.hash | ||
| import aws.smithy.kotlin.runtime.hashing.sha256 | ||
| import aws.smithy.kotlin.runtime.text.encoding.encodeToHex | ||
| import aws.smithy.kotlin.runtime.time.Instant | ||
| import aws.smithy.kotlin.runtime.time.TimestampFormat | ||
| import aws.smithy.kotlin.runtime.time.epochMilliseconds | ||
|
|
||
| /** | ||
| * Common signature implementation used for SigV4 and SigV4a, primarily for forming the strings-to-sign which don't differ | ||
| * between the two signing algorithms (besides their names). | ||
| */ | ||
| internal abstract class BaseSigV4SignatureCalculator( | ||
| val algorithm: AwsSigningAlgorithm, | ||
| open val sha256Provider: HashSupplier = ::Sha256, | ||
| ) : SignatureCalculator { | ||
| private val supportedAlgorithms = setOf(AwsSigningAlgorithm.SIGV4, AwsSigningAlgorithm.SIGV4_ASYMMETRIC) | ||
|
|
||
| init { | ||
| check(algorithm in supportedAlgorithms) { | ||
| "This class should only be used for ${supportedAlgorithms.joinToString()}, got $algorithm" | ||
| } | ||
| } | ||
|
|
||
| override fun stringToSign(canonicalRequest: String, config: AwsSigningConfig): String = buildString { | ||
| appendLine(algorithm.signingName) | ||
| appendLine(config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED)) | ||
| appendLine(config.credentialScope) | ||
| append(canonicalRequest.encodeToByteArray().hash(sha256Provider).encodeToHex()) | ||
| } | ||
|
|
||
| override fun chunkStringToSign(chunkBody: ByteArray, prevSignature: ByteArray, config: AwsSigningConfig): String = buildString { | ||
| appendLine("${algorithm.signingName}-PAYLOAD") | ||
| appendLine(config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED)) | ||
| appendLine(config.credentialScope) | ||
| appendLine(prevSignature.decodeToString()) // Should already be a byte array of ASCII hex chars | ||
|
|
||
| val nonSignatureHeadersHash = when (config.signatureType) { | ||
| AwsSignatureType.HTTP_REQUEST_EVENT -> eventStreamNonSignatureHeaders(config.signingDate) | ||
| else -> HashSpecification.EmptyBody.hash | ||
| } | ||
|
|
||
| appendLine(nonSignatureHeadersHash) | ||
| append(chunkBody.hash(sha256Provider).encodeToHex()) | ||
| } | ||
|
|
||
| override fun chunkTrailerStringToSign(trailingHeaders: ByteArray, prevSignature: ByteArray, config: AwsSigningConfig): String = | ||
| buildString { | ||
| appendLine("${algorithm.signingName}-TRAILER") | ||
| appendLine(config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED)) | ||
| appendLine(config.credentialScope) | ||
| appendLine(prevSignature.decodeToString()) | ||
| append(trailingHeaders.hash(sha256Provider).encodeToHex()) | ||
| } | ||
| } | ||
|
|
||
| private const val HEADER_TIMESTAMP_TYPE: Byte = 8 | ||
|
|
||
| /** | ||
| * Return the sha256 hex representation of the encoded event stream date header | ||
| * | ||
| * ``` | ||
| * sha256Hex( Header(":date", HeaderValue::Timestamp(date)).encodeToByteArray() ) | ||
| * ``` | ||
| * | ||
| * NOTE: This duplicates parts of the event stream encoding implementation here to avoid a direct dependency. | ||
| * Should this become more involved than encoding a single date header we should reconsider this choice. | ||
| * | ||
| * see [Event Stream implementation](https://github.com/smithy-lang/smithy-kotlin/blob/612c39ba446e6403ea2bd9a51c4d1db111b6e26f/runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/Header.kt#L52) | ||
| */ | ||
| private fun eventStreamNonSignatureHeaders(date: Instant): String { | ||
| val bytes = ByteArray(15) | ||
| // encode header name | ||
| val name = ":date".encodeToByteArray() | ||
| var offset = 0 | ||
| bytes[offset++] = name.size.toByte() | ||
| name.copyInto(bytes, destinationOffset = offset) | ||
| offset += name.size | ||
|
|
||
| // encode header value | ||
| bytes[offset++] = HEADER_TIMESTAMP_TYPE | ||
| writeLongBE(bytes, offset, date.epochMilliseconds) | ||
| return bytes.sha256().encodeToHex() | ||
| } | ||
|
|
||
| private fun writeLongBE(dest: ByteArray, offset: Int, x: Long) { | ||
| var idx = offset | ||
| for (i in 7 downTo 0) { | ||
| dest[idx++] = ((x ushr (i * 8)) and 0xff).toByte() | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
...-default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/SigV4SignatureCalculator.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package aws.smithy.kotlin.runtime.auth.awssigning | ||
|
|
||
| import aws.smithy.kotlin.runtime.hashing.HashSupplier | ||
| import aws.smithy.kotlin.runtime.hashing.Sha256 | ||
| import aws.smithy.kotlin.runtime.hashing.hmac | ||
| import aws.smithy.kotlin.runtime.text.encoding.encodeToHex | ||
| import aws.smithy.kotlin.runtime.time.TimestampFormat | ||
|
|
||
| /** | ||
| * [SignatureCalculator] for the SigV4 ("AWS4-HMAC-SHA256") algorithm | ||
| * @param sha256Provider the [HashSupplier] to use for computing SHA-256 hashes | ||
| */ | ||
| internal class SigV4SignatureCalculator(override val sha256Provider: HashSupplier = ::Sha256) : BaseSigV4SignatureCalculator(AwsSigningAlgorithm.SIGV4, sha256Provider) { | ||
| override fun calculate(signingKey: ByteArray, stringToSign: String): String = | ||
| hmac(signingKey, stringToSign.encodeToByteArray(), sha256Provider).encodeToHex() | ||
|
|
||
| override fun signingKey(config: AwsSigningConfig): ByteArray { | ||
| fun hmac(key: ByteArray, message: String) = hmac(key, message.encodeToByteArray(), sha256Provider) | ||
|
|
||
| val initialKey = ("AWS4" + config.credentials.secretAccessKey).encodeToByteArray() | ||
| val kDate = hmac(initialKey, config.signingDate.format(TimestampFormat.ISO_8601_CONDENSED_DATE)) | ||
| val kRegion = hmac(kDate, config.region) | ||
| val kService = hmac(kRegion, config.service) | ||
| return hmac(kService, "aws4_request") | ||
| } | ||
| } |
88 changes: 88 additions & 0 deletions
88
...default/common/src/aws/smithy/kotlin/runtime/auth/awssigning/SigV4aSignatureCalculator.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package aws.smithy.kotlin.runtime.auth.awssigning | ||
|
|
||
| import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials | ||
| import aws.smithy.kotlin.runtime.collections.ReadThroughCache | ||
| import aws.smithy.kotlin.runtime.content.BigInteger | ||
| import aws.smithy.kotlin.runtime.hashing.HashSupplier | ||
| import aws.smithy.kotlin.runtime.hashing.Sha256 | ||
| import aws.smithy.kotlin.runtime.hashing.ecdsaSecp256r1 | ||
| import aws.smithy.kotlin.runtime.hashing.hmac | ||
| import aws.smithy.kotlin.runtime.text.encoding.decodeHexBytes | ||
| import aws.smithy.kotlin.runtime.text.encoding.encodeToHex | ||
| import aws.smithy.kotlin.runtime.time.Instant | ||
| import aws.smithy.kotlin.runtime.util.ExpiringValue | ||
| import kotlinx.coroutines.runBlocking | ||
| import kotlin.time.Duration.Companion.hours | ||
|
|
||
| /** | ||
| * The maximum number of iterations to attempt private key derivation using KDF in counter mode | ||
| * Taken from CRT: https://github.com/awslabs/aws-c-auth/blob/e8360a65e0f3337d4ac827945e00c3b55a641a5f/source/key_derivation.c#L22 | ||
| */ | ||
| internal const val MAX_KDF_COUNTER_ITERATIONS = 254.toByte() | ||
|
|
||
| // N value from NIST P-256 curve, minus two. | ||
| internal val N_MINUS_TWO = "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F".decodeHexBytes().toPositiveBigInteger() | ||
|
|
||
| /** | ||
| * A [SignatureCalculator] for the SigV4a ("AWS4-ECDSA-P256-SHA256") algorithm. | ||
| * @param sha256Provider the [HashSupplier] to use for computing SHA-256 hashes | ||
| */ | ||
| internal class SigV4aSignatureCalculator(override val sha256Provider: HashSupplier = ::Sha256) : BaseSigV4SignatureCalculator(AwsSigningAlgorithm.SIGV4_ASYMMETRIC, sha256Provider) { | ||
| private val privateKeyCache = ReadThroughCache<Credentials, ByteArray>( | ||
| minimumSweepPeriod = 1.hours, // note: Sweeps are effectively a no-op because expiration is [Instant.MAX_VALUE] | ||
| ) | ||
|
|
||
| override fun calculate(signingKey: ByteArray, stringToSign: String): String = ecdsaSecp256r1(signingKey, stringToSign.encodeToByteArray()).encodeToHex() | ||
|
|
||
| /** | ||
| * Retrieve a signing key based on the signing credentials. If not cached, the key will be derived using a counter-based key derivation function (KDF) | ||
| * as specified in NIST SP 800-108. | ||
| * | ||
| * See https://github.com/awslabs/aws-c-auth/blob/e8360a65e0f3337d4ac827945e00c3b55a641a5f/source/key_derivation.c#L70 and | ||
| * https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html#derive-signing-key-sigv4a for | ||
| * more information on the derivation process. | ||
| */ | ||
| override fun signingKey(config: AwsSigningConfig): ByteArray = runBlocking { | ||
| privateKeyCache.get(config.credentials) { | ||
| var counter: Byte = 1 | ||
| var privateKey: ByteArray | ||
|
|
||
| val inputKey = ("AWS4A" + config.credentials.secretAccessKey).encodeToByteArray() | ||
|
|
||
| do { | ||
| val k0 = hmac(inputKey, fixedInputString(config.credentials.accessKeyId, counter), sha256Provider) | ||
|
|
||
| val c = k0.toPositiveBigInteger() | ||
| privateKey = (c + BigInteger("1")).toByteArray() | ||
|
|
||
| if (counter == MAX_KDF_COUNTER_ITERATIONS && c > N_MINUS_TWO) { | ||
| throw IllegalStateException("Counter exceeded maximum length") | ||
| } else { | ||
| counter++ | ||
| } | ||
| } while (c > N_MINUS_TWO) | ||
|
|
||
| ExpiringValue<ByteArray>(privateKey, Instant.MAX_VALUE) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Forms the fixed input string used for ECDSA private key derivation | ||
| * The final output looks like: | ||
| * 0x00000001 || "AWS4-ECDSA-P256-SHA256" || 0x00 || AccessKeyId || counter || 0x00000100 | ||
| */ | ||
| private fun fixedInputString(accessKeyId: String, counter: Byte): ByteArray = | ||
| byteArrayOf(0x00, 0x00, 0x00, 0x01) + | ||
| AwsSigningAlgorithm.SIGV4_ASYMMETRIC.signingName.encodeToByteArray() + | ||
| byteArrayOf(0x00) + | ||
| accessKeyId.encodeToByteArray() + | ||
| counter + | ||
| byteArrayOf(0x00, 0x00, 0x01, 0x00) | ||
| } | ||
|
|
||
| // Convert [this] [ByteArray] to a positive [BigInteger] by prepending 0x00. | ||
| private fun ByteArray.toPositiveBigInteger() = BigInteger(byteArrayOf(0x00) + this) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: Embed as a constructor parameter