Skip to content

Commit 58a220c

Browse files
authored
feat: add signing for chunk trailing headers (#60)
1 parent 48d5f3e commit 58a220c

File tree

5 files changed

+71
-1
lines changed

5 files changed

+71
-1
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "5c32e493-2c13-4eef-9ffc-3c3a2326bcf6",
3+
"type": "feature",
4+
"description": "Implement signChunkTrailer",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin/#747"
7+
]
8+
}

src/common/src/aws/sdk/kotlin/crt/auth/signing/AwsSigner.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package aws.sdk.kotlin.crt.auth.signing
77

8+
import aws.sdk.kotlin.crt.http.Headers
89
import aws.sdk.kotlin.crt.http.HttpRequest
910

1011
public expect object AwsSigner {
@@ -13,4 +14,6 @@ public expect object AwsSigner {
1314
public suspend fun sign(request: HttpRequest, config: AwsSigningConfig): AwsSigningResult
1415

1516
public suspend fun signChunk(chunkBody: ByteArray, prevSignature: ByteArray, config: AwsSigningConfig): AwsSigningResult
17+
18+
public suspend fun signChunkTrailer(trailingHeaders: Headers, prevSignature: ByteArray, config: AwsSigningConfig): AwsSigningResult
1619
}

src/common/src/aws/sdk/kotlin/crt/auth/signing/AwsSigningConfig.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public enum class AwsSignatureType(public val value: Int) {
1818
HTTP_REQUEST_VIA_HEADERS(0),
1919
HTTP_REQUEST_VIA_QUERY_PARAMS(1),
2020
HTTP_REQUEST_CHUNK(2),
21-
HTTP_REQUEST_EVENT(3);
21+
HTTP_REQUEST_EVENT(3),
22+
HTTP_REQUEST_TRAILING_HEADERS(6);
2223
}
2324

2425
public object AwsSignedBodyValue {

src/common/test/aws/sdk/kotlin/crt/auth/signing/SigningTest.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package aws.sdk.kotlin.crt.auth.signing
88
import aws.sdk.kotlin.crt.*
99
import aws.sdk.kotlin.crt.auth.credentials.StaticCredentialsProvider
1010
import aws.sdk.kotlin.crt.auth.credentials.build
11+
import aws.sdk.kotlin.crt.http.Headers
1112
import aws.sdk.kotlin.crt.http.HttpRequest
1213
import aws.sdk.kotlin.crt.http.HttpRequestBodyStream
1314
import aws.sdk.kotlin.crt.http.headers
@@ -193,4 +194,36 @@ class SigningTest : CrtTest() {
193194
assertTrue(signedRequest.headers["Authorization"]!!.contains(prefix), signedRequest.headers["Authorization"])
194195
}
195196
}
197+
198+
@Test
199+
fun testSigningChunkTrailingHeaders() = runSuspendTest {
200+
StaticCredentialsProvider.build {
201+
accessKeyId = "AKID"
202+
secretAccessKey = "SECRET"
203+
}.use { provider ->
204+
205+
val creds = provider.getCredentials()
206+
207+
val signingConfig = AwsSigningConfig.build {
208+
algorithm = AwsSigningAlgorithm.SIGV4
209+
signatureType = AwsSignatureType.HTTP_REQUEST_TRAILING_HEADERS
210+
region = "foo"
211+
service = "bar"
212+
date = 1651022625000
213+
credentialsProvider = provider
214+
credentials = creds
215+
}
216+
217+
val trailingHeaders = Headers.build {
218+
append("x-amz-checksum-crc32", "AAAAAA==")
219+
append("x-amz-arbitrary-header-with-value", "test")
220+
}
221+
222+
val previousSignature = "106d0654706e3e8dde144d69ca9882ea38d4d72576056c724ba763f8ed3074f3".encodeToByteArray()
223+
224+
val signature = AwsSigner.signChunkTrailer(trailingHeaders, previousSignature, signingConfig).signature.decodeToString()
225+
val expectedSignature = "24f8ed01c7add645b75e65d2382fae5233b97526fdd1a2c4094933b93f6a08bf" // validated using DefaultAwsSigner
226+
assertEquals(expectedSignature, signature)
227+
}
228+
}
196229
}

src/jvm/src/aws/sdk/kotlin/crt/auth/signing/AwsSignerJVM.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import aws.sdk.kotlin.crt.http.from
1212
import aws.sdk.kotlin.crt.http.into
1313
import kotlinx.coroutines.future.await
1414
import software.amazon.awssdk.crt.auth.credentials.Credentials
15+
import software.amazon.awssdk.crt.http.HttpHeader
1516
import java.util.function.Predicate
1617
import software.amazon.awssdk.crt.auth.signing.AwsSigner as AwsSignerJni
1718
import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig as AwsSigningConfigJni
@@ -68,6 +69,30 @@ public actual object AwsSigner {
6869
AwsSigningResult(null, jniResult.signature)
6970
}
7071
}
72+
73+
/**
74+
* Signs a chunk consisting of trailing headers
75+
* @param trailingHeaders the trailing headers to be signed
76+
* @param prevSignature the signature of the previous component of the request (in most cases, this will be the signature
77+
* of the final data chunk, since the trailing header chunk is always sent last)
78+
* @param config the signing configuration to use
79+
* @return signing result, which provides access to all signing-related result properties
80+
*/
81+
public actual suspend fun signChunkTrailer(trailingHeaders: Headers, prevSignature: ByteArray, config: AwsSigningConfig): AwsSigningResult {
82+
if (trailingHeaders.isEmpty()) {
83+
throw IllegalArgumentException("can't sign empty trailing headers")
84+
}
85+
86+
// canonicalize the headers
87+
val headers: List<HttpHeader> = trailingHeaders.entries().sortedBy { e -> e.key.lowercase() }
88+
.map { e -> HttpHeader(e.key.lowercase(), e.value.joinToString(",") { v -> v.trim() }) }
89+
90+
return asyncCrtJniCall {
91+
val signFuture = AwsSignerJni.sign(headers, prevSignature, config.into())
92+
val jniResult = signFuture.await()
93+
AwsSigningResult(null, jniResult.signature)
94+
}
95+
}
7196
}
7297

7398
private fun AwsSigningConfig.into(): AwsSigningConfigJni {

0 commit comments

Comments
 (0)