-
Notifications
You must be signed in to change notification settings - Fork 31
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
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
| // Convert [this] [ByteArray] to a positive [BigInteger] | ||
| private fun ByteArray.toPositiveBigInteger(): BigInteger = if (isNotEmpty() && (get(0).toInt() and 0x80) != 0) { | ||
| BigInteger(byteArrayOf(0x00) + this) // Prepend 0x00 to ensure positive value | ||
| } else { | ||
| BigInteger(this) | ||
| } |
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.
Question: Won't the The BigInteger implementations trim unnecessary leading zeroes automatically? Could we just do:
private fun ByteArray.toPositiveBigInteger() = BigInteger(byteArrayOf(0x00) + this) // Prepend 0x00 to ensure positive value| /** | ||
| * 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 SigV4xSignatureCalculator( |
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: Using x to signify commonality between SigV4 and SigV4a is not very clear. I suggest calling this class BaseSigV4SignatureCalculator.
| check(algorithm == AwsSigningAlgorithm.SIGV4 || algorithm == AwsSigningAlgorithm.SIGV4_ASYMMETRIC) { | ||
| "This class should only be used for the ${AwsSigningAlgorithm.SIGV4} or ${AwsSigningAlgorithm.SIGV4_ASYMMETRIC} algorithms, got $algorithm" | ||
| } |
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.
Nit: Extract duplicated signing algorithms to a val and reference it in both places:
// top-level
private val supportedAlgorithms = setOf(AwsSigningAlgorithm.SIGV4, AwsSigningAlgorithm.SIGV4_ASYMMETRIC)
// in `init`
check(algorithm in supportedAlgorithms) {
"This class only supports algorithms $supportedAlgorithms, got $algorithm"
}| internal val AwsSigningAlgorithm.signingName: String | ||
| get() = when (this) { | ||
| AwsSigningAlgorithm.SIGV4 -> "AWS4-HMAC-SHA256" | ||
| AwsSigningAlgorithm.SIGV4_ASYMMETRIC -> "AWS4-ECDSA-P256-SHA256" | ||
| } |
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.
Question: Should this just be part of the AwsSigningAlgorithm class itself?
| * 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/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) |
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.
Nit: This is a real stale link. Can we refresh it?
| /** | ||
| * ECDSA on the SECP256R1 curve. | ||
| */ | ||
| public expect fun ecdsasecp256r1(key: ByteArray, message: ByteArray): ByteArray |
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: ECSDA and Secp256r1 are distinct words so we should uppercase that s (e.g., ecdsaSecp256r1)
| import java.security.spec.* | ||
| import kotlin.test.Test | ||
|
|
||
| class EcdsaJVMTest { |
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.
Question: Can we move these tests into common instead of jvm?
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.
Not easily, due to the use of java.security.*. Given the FIXME written in EcdsaNative.kt (which we discussed / agreed upon(?) in standup), we are unlikely to ever implement this on Native, so I think it's fine to leave as JVM-only.
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.
Specifically, that we'll be using CrtAwsSigner on Native instead of DefaultAwsSigner. This ecdsaSecp256r1 will not be used on Native in that case
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.
Got it, thanks for the clarification.
|
|
||
| class DefaultSignatureCalculatorTest { | ||
| class SigV4SignatureCalculatorTest { | ||
| // Test adapted from https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html | ||
| @Test | ||
| fun testCalculate() { |
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.
Nit: We should add tests for SigV4a signature calculation with explicit inputs/outputs.
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.
java.security's ECDSA implementation is non-deterministic, it chooses a random k value with each call to sign(). We'd get different (yet still valid) signatures when providing a consistent input. This library doesn't allow overriding the random generator which would allow us to provide a fixed "random" input for tests. The top recommendation seems to be switching to BouncyCastle and overriding the implementation of SecureRandom used.
Would you be fine with the tests asserting on everything except the signature value?
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.
We spoke offline and agreed to assert on everything except the signature value. Added a test suite for "string to sign" with test cases copied from CRT.
| */ | ||
| private fun fixedInputString(accessKeyId: String, counter: Byte): ByteArray = | ||
| byteArrayOf(0x00, 0x00, 0x00, 0x01) + | ||
| "AWS4-ECDSA-P256-SHA256".encodeToByteArray() + |
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.
Nit: Could reuse AwsSigningAlgorithm.SIGV4_ASYMMETRIC.signingName
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| class SigV4aSignatureCalculatorTest { | ||
| @Test | ||
| fun testStringToSign() = TESTS.forEach { testId -> | ||
| runTest { |
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.
Suggestion: Logging which test is running or adding a message to the assertEquals in case the assertion fails
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| /** | ||
| * The name of this algorithm to use when signing requests. | ||
| */ | ||
| public val signingName: String | ||
| get() = when (this) { | ||
| SIGV4 -> "AWS4-HMAC-SHA256" | ||
| SIGV4_ASYMMETRIC -> "AWS4-ECDSA-P256-SHA256" | ||
| } |
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
public enum class AwsSigningAlgorithm(public val signingName: String) {
SIGV4("AWS4-HMAC-SHA256"),
SIGV4_ASYMMETRIC("AWS4-ECDSA-P256-SHA256"),
}| /** | ||
| * ECDSA on the SECP256R1 curve. | ||
| */ | ||
| // FIXME Implement using aws-c-cal: https://github.com/awslabs/aws-c-cal/blob/main/include/aws/cal/ecc.h | ||
| /** | ||
| * Will need to be implemented and exposed in aws-crt-kotlin. | ||
| * Or maybe we can _only_ offer the CRT signer on Native? | ||
| * Will require updating DefaultAwsSigner to be expect/actual and set to CrtSigner on Native. | ||
| */ | ||
| public actual fun ecdsaSecp256r1(key: ByteArray, message: ByteArray): ByteArray = TODO("Not yet implemented") |
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.
Nit: The FIXME ends and a new KDocs block opens but it seems like the content of it is more related to the FIXME than documentation about what the function does. Restructure to just continue the FIXME:
/**
* ECDSA on the SECP256R1 curve.
*/
// FIXME Implement using aws-c-cal: https://github.com/awslabs/aws-c-cal/blob/main/include/aws/cal/ecc.h
// Will need to be implemented and exposed in aws-crt-kotlin.
// Or maybe we can _only_ offer the CRT signer on Native?
// Will require updating DefaultAwsSigner to be expect/actual and set to CrtSigner on Native.
public actual fun ecdsaSecp256r1(key: ByteArray, message: ByteArray): ByteArray = TODO("Not yet implemented")| import java.security.spec.* | ||
| import kotlin.test.Test | ||
|
|
||
| class EcdsaJVMTest { |
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.
Got it, thanks for the clarification.
| /** | ||
| * Tests for [SigV4aSignatureCalculator]. Currently only tests forming the string-to-sign. | ||
| * | ||
| * TODO Add tests against header-signature.txt when java.security implements RFC 6979 / deterministic ECDSA. | ||
| * https://bugs.openjdk.org/browse/JDK-8239382 | ||
| */ | ||
| class SigV4aSignatureCalculatorTest { |
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.
Nit: TODO should not be part of KDocs.
Affected ArtifactsSignificantly increased in size
Changed in size
|
Issue #
Description of changes
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.