@@ -70,34 +70,62 @@ internal interface SignatureCalculator {
7070 fun chunkTrailerStringToSign (trailingHeaders : ByteArray , prevSignature : ByteArray , config : AwsSigningConfig ): String
7171}
7272
73- internal class SigV4SignatureCalculator (private val sha256Provider : HashSupplier = ::Sha256 ) : SignatureCalculator {
74- override fun calculate (signingKey : ByteArray , stringToSign : String ): String =
75- hmac(signingKey, stringToSign.encodeToByteArray(), sha256Provider).encodeToHex()
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+ }
7686
77- override fun chunkStringToSign ( chunkBody : ByteArray , prevSignature : ByteArray , config : AwsSigningConfig ): String =
78- buildString {
79- appendLine(" AWS4-HMAC-SHA256-PAYLOAD " )
80- appendLine(config.signingDate.format( TimestampFormat . ISO_8601_CONDENSED ) )
81- appendLine(config.credentialScope )
82- appendLine(prevSignature.decodeToString()) // Should already be a byte array of ASCII hex chars
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+ }
8393
84- val nonSignatureHeadersHash = when (config.signatureType) {
85- AwsSignatureType .HTTP_REQUEST_EVENT -> eventStreamNonSignatureHeaders(config.signingDate)
86- else -> HashSpecification .EmptyBody .hash
87- }
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
8899
89- appendLine(nonSignatureHeadersHash)
90- append(chunkBody.hash(sha256Provider).encodeToHex())
100+ val nonSignatureHeadersHash = when (config.signatureType) {
101+ AwsSignatureType .HTTP_REQUEST_EVENT -> eventStreamNonSignatureHeaders(config.signingDate)
102+ else -> HashSpecification .EmptyBody .hash
91103 }
92104
105+ appendLine(nonSignatureHeadersHash)
106+ append(chunkBody.hash(sha256Provider).encodeToHex())
107+ }
108+
93109 override fun chunkTrailerStringToSign (trailingHeaders : ByteArray , prevSignature : ByteArray , config : AwsSigningConfig ): String =
94110 buildString {
95- appendLine(" AWS4-HMAC-SHA256 -TRAILER" )
111+ appendLine(" ${algorithm.signingName} -TRAILER" )
96112 appendLine(config.signingDate.format(TimestampFormat .ISO_8601_CONDENSED ))
97113 appendLine(config.credentialScope)
98114 appendLine(prevSignature.decodeToString())
99115 append(trailingHeaders.hash(sha256Provider).encodeToHex())
100116 }
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+
126+ internal class SigV4SignatureCalculator (override val sha256Provider : HashSupplier = ::Sha256 ) : SigV4xSignatureCalculator(AwsSigningAlgorithm .SIGV4 , sha256Provider) {
127+ override fun calculate (signingKey : ByteArray , stringToSign : String ): String =
128+ hmac(signingKey, stringToSign.encodeToByteArray(), sha256Provider).encodeToHex()
101129
102130 override fun signingKey (config : AwsSigningConfig ): ByteArray {
103131 fun hmac (key : ByteArray , message : String ) = hmac(key, message.encodeToByteArray(), sha256Provider)
@@ -108,36 +136,20 @@ internal class SigV4SignatureCalculator(private val sha256Provider: HashSupplier
108136 val kService = hmac(kRegion, config.service)
109137 return hmac(kService, " aws4_request" )
110138 }
111-
112- override fun stringToSign (canonicalRequest : String , config : AwsSigningConfig ): String =
113- buildString {
114- appendLine(" AWS4-HMAC-SHA256" )
115- appendLine(config.signingDate.format(TimestampFormat .ISO_8601_CONDENSED ))
116- appendLine(config.credentialScope)
117- append(canonicalRequest.encodeToByteArray().hash(sha256Provider).encodeToHex())
118- }
119139}
120140
121- // FIXME Copied a few functions from SigV4SignatureCalculator, refactor to share
122- internal class SigV4aSignatureCalculator (
123- val sha256Provider : HashSupplier = ::Sha256 ,
124- ) : SignatureCalculator {
125- override fun calculate (signingKey : ByteArray , stringToSign : String ): String =
126- ecdsasecp256r1(signingKey, stringToSign.encodeToByteArray()).encodeToHex()
141+ /* *
142+ * The maximum number of iterations to attempt private key derivation using KDF in counter mode
143+ * Taken from CRT: https://github.com/awslabs/aws-c-auth/blob/e8360a65e0f3337d4ac827945e00c3b55a641a5f/source/key_derivation.c#L22
144+ */
145+ internal const val MAX_KDF_COUNTER_ITERATIONS = 254 .toByte()
127146
128- override fun stringToSign (
129- canonicalRequest : String ,
130- config : AwsSigningConfig ,
131- ): String = buildString {
132- appendLine(" AWS4-ECDSA-P256-SHA256" )
133- appendLine(config.signingDate.format(TimestampFormat .ISO_8601_CONDENSED ))
134- appendLine(config.credentialScope)
135- append(canonicalRequest.encodeToByteArray().hash(sha256Provider).encodeToHex())
136- }
147+ internal class SigV4aSignatureCalculator (override val sha256Provider : HashSupplier = ::Sha256 ) : SigV4xSignatureCalculator(AwsSigningAlgorithm .SIGV4_ASYMMETRIC , sha256Provider) {
148+ override fun calculate (signingKey : ByteArray , stringToSign : String ): String = ecdsasecp256r1(signingKey, stringToSign.encodeToByteArray()).encodeToHex()
137149
150+ // See https://github.com/awslabs/aws-c-auth/blob/e8360a65e0f3337d4ac827945e00c3b55a641a5f/source/key_derivation.c#L70 for more details of derivation process
138151 override fun signingKey (config : AwsSigningConfig ): ByteArray {
139- // 1.1: Compute fixed input string
140- var counter: UByte = 1u
152+ var counter: Byte = 1
141153 var privateKey: ByteArray
142154
143155 // N value from NIST P-256 curve
@@ -150,32 +162,15 @@ internal class SigV4aSignatureCalculator(
150162 val inputKey = (" AWS4A" + config.credentials.secretAccessKey).encodeToByteArray()
151163
152164 do {
153- // See https://github.com/awslabs/aws-c-auth/blob/e8360a65e0f3337d4ac827945e00c3b55a641a5f/source/key_derivation.c#L70 for
154- // more details of derivation process
155-
156- // FIXME CRT implementation (4 bytes) and internal docs (1 byte) conflict.
157- val headerBytes = byteArrayOf(0x00 , 0x00 , 0x00 , 0x01 )
158-
159- // 256 big endian
160- // FIXME CRT implementation (4 bytes) and internal docs (2 bytes) conflict.
161- val lengthBytes = byteArrayOf(0x00 , 0x00 , 0x01 , 0x00 )
162-
163- val fixedInputString: ByteArray = headerBytes +
164- " AWS4-ECDSA-P256-SHA256" .encodeToByteArray() +
165- byteArrayOf(0x00 ) +
166- config.credentials.accessKeyId.encodeToByteArray() +
167- counter.toByte() +
168- lengthBytes
169-
170165 // 1.2: Compute K0
171- val k0 = hmac(inputKey, fixedInputString, sha256Provider)
166+ val k0 = hmac(inputKey, fixedInputString(config.credentials.accessKeyId, counter) , sha256Provider)
172167
173168 // 2: Compute the ECC key pair
174169 val c = BigInteger (k0)
175170
176171 privateKey = (c + BigInteger (" 1" )).toByteArray()
177172
178- if (counter.toByte() == 254 .toByte() && c > n - BigInteger (" 2" )) {
173+ if (counter == MAX_KDF_COUNTER_ITERATIONS && c > n - BigInteger (" 2" )) {
179174 throw IllegalStateException (" Counter exceeded maximum length" )
180175 } else {
181176 counter++
@@ -185,36 +180,19 @@ internal class SigV4aSignatureCalculator(
185180 return privateKey
186181 }
187182
188- override fun chunkStringToSign (
189- chunkBody : ByteArray ,
190- prevSignature : ByteArray ,
191- config : AwsSigningConfig ,
192- ): String = buildString {
193- appendLine(" AWS4-ECDSA-P256-SHA256-PAYLOAD" )
194- appendLine(config.signingDate.format(TimestampFormat .ISO_8601_CONDENSED ))
195- appendLine(config.credentialScope)
196- appendLine(prevSignature.decodeToString()) // Should already be a byte array of ASCII hex chars
197-
198- val nonSignatureHeadersHash = when (config.signatureType) {
199- AwsSignatureType .HTTP_REQUEST_EVENT -> eventStreamNonSignatureHeaders(config.signingDate)
200- else -> HashSpecification .EmptyBody .hash
201- }
202-
203- appendLine(nonSignatureHeadersHash)
204- append(chunkBody.hash(sha256Provider).encodeToHex())
205- }
183+ /* *
184+ * Computes the fixed input string used for KDF
185+ * The final output looks like:
186+ * 0x00000001 || "AWS4-ECDSA-P256-SHA256" || 0x00 || AccessKeyId || counter || 0x00000100
187+ */
188+ private fun fixedInputString (accessKeyId : String , counter : Byte ): ByteArray =
189+ byteArrayOf(0x00 , 0x00 , 0x00 , 0x01 ) + // FIXME CRT implementation (4 bytes) and internal docs (1 byte) conflict.
190+ " AWS4-ECDSA-P256-SHA256" .encodeToByteArray() +
191+ byteArrayOf(0x00 ) +
192+ accessKeyId.encodeToByteArray() +
193+ counter +
194+ byteArrayOf(0x00 , 0x00 , 0x01 , 0x00 ) // FIXME CRT implementation (4 bytes) and internal docs (2 bytes) conflict.
206195
207- override fun chunkTrailerStringToSign (
208- trailingHeaders : ByteArray ,
209- prevSignature : ByteArray ,
210- config : AwsSigningConfig ,
211- ): String = buildString {
212- appendLine(" AWS4-ECDSA-P256-SHA256-TRAILER" )
213- appendLine(config.signingDate.format(TimestampFormat .ISO_8601_CONDENSED ))
214- appendLine(config.credentialScope)
215- appendLine(prevSignature.decodeToString())
216- append(trailingHeaders.hash(sha256Provider).encodeToHex())
217- }
218196}
219197
220198private const val HEADER_TIMESTAMP_TYPE : Byte = 8
0 commit comments