Skip to content

Commit 74f6a6e

Browse files
authored
feat: sigva (#30)
1 parent 34d7e8e commit 74f6a6e

File tree

7 files changed

+105
-8
lines changed

7 files changed

+105
-8
lines changed

build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ kotlin {
100100
val jvmTest by getting {
101101
dependencies {
102102
val junitVersion: String by project
103-
api("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
104103
api("org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion")
105104
implementation("org.junit.jupiter:junit-jupiter:$junitVersion")
106105
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-debug:$coroutinesVersion")

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ kotestVersion=4.6.2
1919
kotlinxCliVersion=0.3.2
2020

2121
# JVM
22-
crtJavaVersion=0.14.2
22+
crtJavaVersion=0.14.7

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ import aws.sdk.kotlin.crt.http.HttpRequest
99

1010
public expect object AwsSigner {
1111
public suspend fun signRequest(request: HttpRequest, config: AwsSigningConfig): HttpRequest
12+
13+
public suspend fun sign(request: HttpRequest, config: AwsSigningConfig): AwsSigningResult
1214
}

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
@@ -10,7 +10,8 @@ import aws.sdk.kotlin.crt.auth.credentials.Credentials
1010
import aws.sdk.kotlin.crt.auth.credentials.CredentialsProvider
1111

1212
public enum class AwsSigningAlgorithm(public val value: Int) {
13-
SIGV4(0);
13+
SIGV4(0),
14+
SIGV4_ASYMMETRIC(1);
1415
}
1516

1617
public enum class AwsSignatureType(public val value: Int) {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package aws.sdk.kotlin.crt.auth.signing
2+
3+
import aws.sdk.kotlin.crt.http.HttpRequest
4+
5+
/**
6+
* Wrapper that holds signing-related output. Depending on the signing configuration, not all members may be
7+
* assigned and some members, like signature, may have a variable format.
8+
*/
9+
public data class AwsSigningResult(
10+
/**
11+
* The signed HTTP request from the result, may be null if an http request was not signed
12+
*/
13+
val signedRequest: HttpRequest?,
14+
15+
/**
16+
* Gets the signature value from the result. Depending on the requested signature type and algorithm, this value
17+
* will be in one of the following formats:
18+
*
19+
* (1) HTTP_REQUEST_VIA_HEADERS - hex encoding of the binary signature value
20+
* (2) HTTP_REQUEST_VIA_QUERY_PARAMS - hex encoding of the binary signature value
21+
* (3) HTTP_REQUEST_CHUNK/SIGV4 - hex encoding of the binary signature value
22+
* (4) HTTP_REQUEST_CHUNK/SIGV4_ASYMMETRIC - '*'-padded hex encoding of the binary signature value
23+
* (5) HTTP_REQUEST_EVENT - binary signature value (NYI)
24+
*
25+
* @return the signature value from the signing process
26+
*/
27+
val signature: ByteArray
28+
) {
29+
30+
override fun equals(other: Any?): Boolean {
31+
if (this === other) return true
32+
if (other == null || this::class != other::class) return false
33+
34+
other as AwsSigningResult
35+
36+
if (signedRequest != other.signedRequest) return false
37+
if (!signature.contentEquals(other.signature)) return false
38+
39+
return true
40+
}
41+
42+
override fun hashCode(): Int {
43+
var result = signedRequest?.hashCode() ?: 0
44+
result = 31 * result + signature.contentHashCode()
45+
return result
46+
}
47+
}

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ class SigningTest : CrtTest() {
135135
signedRequest.headers.contains(
136136
"Authorization",
137137
"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=28038455d6de14eafc1f9222cf5aa6f1a96197d7deb8263271d420d138af7f11"
138-
)
138+
),
139+
"sigv4 authorization not equal: " + signedRequest.headers["Authorization"]
139140
)
140141
}
141142
}
@@ -165,4 +166,31 @@ class SigningTest : CrtTest() {
165166
assertEquals("AWS_AUTH_SIGNING_ILLEGAL_REQUEST_HEADER", ex.errorName)
166167
}
167168
}
169+
170+
@Test
171+
fun testSigningSigV4Asymmetric() = runSuspendTest {
172+
StaticCredentialsProvider.build {
173+
accessKeyId = TEST_ACCESS_KEY_ID
174+
secretAccessKey = TEST_SECRET_ACCESS_KEY
175+
}.use { provider ->
176+
val request = createSigV4TestSuiteRequest()
177+
val signingConfig = AwsSigningConfig.build {
178+
algorithm = AwsSigningAlgorithm.SIGV4_ASYMMETRIC
179+
signatureType = AwsSignatureType.HTTP_REQUEST_VIA_HEADERS
180+
region = "us-east-1"
181+
service = "service"
182+
date = TEST_DATE_EPOCH_MILLI
183+
credentialsProvider = provider
184+
useDoubleUriEncode = true
185+
normalizeUriPath = true
186+
signedBodyValue = AwsSignedBodyValue.EMPTY_SHA256
187+
expirationInSeconds = 60
188+
}
189+
190+
val signedRequest = AwsSigner.signRequest(request, signingConfig)
191+
assertTrue(signedRequest.headers.contains("X-Amz-Date", "20150830T123600Z"), "${signedRequest.headers}")
192+
val prefix = "AWS4-ECDSA-P256-SHA256 Credential=AKIDEXAMPLE/20150830/service/aws4_request, SignedHeaders=host;x-amz-date;x-amz-region-set, Signature="
193+
assertTrue(signedRequest.headers["Authorization"]!!.contains(prefix), signedRequest.headers["Authorization"])
194+
}
195+
}
168196
}

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

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,35 @@ import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignatureType
1919
import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSignedBodyHeaderType as AwsSignedBodyHeaderTypeJni
2020
import software.amazon.awssdk.crt.auth.signing.AwsSigningConfig.AwsSigningAlgorithm as AwsSigningAlgorithmJni
2121

22+
/**
23+
* Static class for a variety of AWS signing APIs.
24+
*/
2225
public actual object AwsSigner {
23-
public actual suspend fun signRequest(request: HttpRequest, config: AwsSigningConfig): HttpRequest {
26+
27+
/**
28+
* Signs an http request according to the supplied signing configuration
29+
* @param request http request to sign
30+
* @param config signing configuration
31+
* @return signed request
32+
*/
33+
public actual suspend fun signRequest(request: HttpRequest, config: AwsSigningConfig): HttpRequest =
34+
checkNotNull(sign(request, config).signedRequest) { "AwsSigningResult request must not be null" }
35+
36+
/**
37+
* Signs an http request according to the supplied signing configuration
38+
* @param request http request to sign
39+
* @param config signing configuration
40+
* @return signing result, which provides access to all signing-related result properties
41+
*/
42+
public actual suspend fun sign(request: HttpRequest, config: AwsSigningConfig): AwsSigningResult {
2443
// FIXME - this would be a good area where talking directly to JNI would be convenient so we don't have to
2544
// do [KotlinHttpReq -> CrtJava -> Native] and back
2645
val jniReq = request.into()
2746
return asyncCrtJniCall {
28-
val reqFuture = AwsSignerJni.signRequest(jniReq, config.into())
29-
val signedJniReq = reqFuture.await()
30-
HttpRequest.from(signedJniReq)
47+
val reqFuture = AwsSignerJni.sign(jniReq, config.into())
48+
val jniResult = reqFuture.await()
49+
val signedRequest = HttpRequest.from(jniResult.signedRequest)
50+
AwsSigningResult(signedRequest, jniResult.signature)
3151
}
3252
}
3353
}

0 commit comments

Comments
 (0)