Skip to content

Commit

Permalink
feat: add signing for chunk trailing headers (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
lauzadis authored Nov 21, 2022
1 parent 48d5f3e commit 58a220c
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 1 deletion.
8 changes: 8 additions & 0 deletions .changes/5c32e493-2c13-4eef-9ffc-3c3a2326bcf6.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "5c32e493-2c13-4eef-9ffc-3c3a2326bcf6",
"type": "feature",
"description": "Implement signChunkTrailer",
"issues": [
"awslabs/aws-sdk-kotlin/#747"
]
}
3 changes: 3 additions & 0 deletions src/common/src/aws/sdk/kotlin/crt/auth/signing/AwsSigner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

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

import aws.sdk.kotlin.crt.http.Headers
import aws.sdk.kotlin.crt.http.HttpRequest

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

public suspend fun signChunk(chunkBody: ByteArray, prevSignature: ByteArray, config: AwsSigningConfig): AwsSigningResult

public suspend fun signChunkTrailer(trailingHeaders: Headers, prevSignature: ByteArray, config: AwsSigningConfig): AwsSigningResult
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ public enum class AwsSignatureType(public val value: Int) {
HTTP_REQUEST_VIA_HEADERS(0),
HTTP_REQUEST_VIA_QUERY_PARAMS(1),
HTTP_REQUEST_CHUNK(2),
HTTP_REQUEST_EVENT(3);
HTTP_REQUEST_EVENT(3),
HTTP_REQUEST_TRAILING_HEADERS(6);
}

public object AwsSignedBodyValue {
Expand Down
33 changes: 33 additions & 0 deletions src/common/test/aws/sdk/kotlin/crt/auth/signing/SigningTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package aws.sdk.kotlin.crt.auth.signing
import aws.sdk.kotlin.crt.*
import aws.sdk.kotlin.crt.auth.credentials.StaticCredentialsProvider
import aws.sdk.kotlin.crt.auth.credentials.build
import aws.sdk.kotlin.crt.http.Headers
import aws.sdk.kotlin.crt.http.HttpRequest
import aws.sdk.kotlin.crt.http.HttpRequestBodyStream
import aws.sdk.kotlin.crt.http.headers
Expand Down Expand Up @@ -193,4 +194,36 @@ class SigningTest : CrtTest() {
assertTrue(signedRequest.headers["Authorization"]!!.contains(prefix), signedRequest.headers["Authorization"])
}
}

@Test
fun testSigningChunkTrailingHeaders() = runSuspendTest {
StaticCredentialsProvider.build {
accessKeyId = "AKID"
secretAccessKey = "SECRET"
}.use { provider ->

val creds = provider.getCredentials()

val signingConfig = AwsSigningConfig.build {
algorithm = AwsSigningAlgorithm.SIGV4
signatureType = AwsSignatureType.HTTP_REQUEST_TRAILING_HEADERS
region = "foo"
service = "bar"
date = 1651022625000
credentialsProvider = provider
credentials = creds
}

val trailingHeaders = Headers.build {
append("x-amz-checksum-crc32", "AAAAAA==")
append("x-amz-arbitrary-header-with-value", "test")
}

val previousSignature = "106d0654706e3e8dde144d69ca9882ea38d4d72576056c724ba763f8ed3074f3".encodeToByteArray()

val signature = AwsSigner.signChunkTrailer(trailingHeaders, previousSignature, signingConfig).signature.decodeToString()
val expectedSignature = "24f8ed01c7add645b75e65d2382fae5233b97526fdd1a2c4094933b93f6a08bf" // validated using DefaultAwsSigner
assertEquals(expectedSignature, signature)
}
}
}
25 changes: 25 additions & 0 deletions src/jvm/src/aws/sdk/kotlin/crt/auth/signing/AwsSignerJVM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import aws.sdk.kotlin.crt.http.from
import aws.sdk.kotlin.crt.http.into
import kotlinx.coroutines.future.await
import software.amazon.awssdk.crt.auth.credentials.Credentials
import software.amazon.awssdk.crt.http.HttpHeader
import java.util.function.Predicate
import software.amazon.awssdk.crt.auth.signing.AwsSigner as AwsSignerJni
import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig as AwsSigningConfigJni
Expand Down Expand Up @@ -68,6 +69,30 @@ public actual object AwsSigner {
AwsSigningResult(null, jniResult.signature)
}
}

/**
* Signs a chunk consisting of trailing headers
* @param trailingHeaders the trailing headers to be signed
* @param prevSignature the signature of the previous component of the request (in most cases, this will be the signature
* of the final data chunk, since the trailing header chunk is always sent last)
* @param config the signing configuration to use
* @return signing result, which provides access to all signing-related result properties
*/
public actual suspend fun signChunkTrailer(trailingHeaders: Headers, prevSignature: ByteArray, config: AwsSigningConfig): AwsSigningResult {
if (trailingHeaders.isEmpty()) {
throw IllegalArgumentException("can't sign empty trailing headers")
}

// canonicalize the headers
val headers: List<HttpHeader> = trailingHeaders.entries().sortedBy { e -> e.key.lowercase() }
.map { e -> HttpHeader(e.key.lowercase(), e.value.joinToString(",") { v -> v.trim() }) }

return asyncCrtJniCall {
val signFuture = AwsSignerJni.sign(headers, prevSignature, config.into())
val jniResult = signFuture.await()
AwsSigningResult(null, jniResult.signature)
}
}
}

private fun AwsSigningConfig.into(): AwsSigningConfigJni {
Expand Down

0 comments on commit 58a220c

Please sign in to comment.