smithy-lang / smithy-kotlin

Smithy code generator for Kotlin (in development)
Apache License 2.0
76 stars 26 forks source link

Invalid signatures due to URL encoding changes (0.36.1-beta vs 1.0.0+) #1008

Closed cloudshiftchris closed 7 months ago

cloudshiftchris commented 7 months ago

Describe the bug

When using ApiGatewayManagementClient (AWS Kotlin SDK 1.0.0 or higher) to send a message on an API GW Websocket calls to ApiGatewayManagementClient.postToConnection now fail with a InvalidSignatureException due to a signature validation error.

This exception did not occur with v0.36.1-beta or previous versions.

This appears to be related to the URL encoding changes, specifically the canonicalization of a Url here. A quick review of that code allows @ as a valid (hence unescaped) character, whereas it was previously escaped, with the endpoint expecting it to be escaped.

Walking through both SDK versions with a debugger we end up with a string-to-sign (fragment) of:

Note the failing version does not escape @ as noted earlier.

While this specific issue pertains to API GW WS SDK calls it's likely there are other areas that are similarly affected given the convergence of signing / canonicalization. Any SDK call with @ in the path will fail to generate the correct canonical string; there may be other characters likewise affected.

Expected Behavior

SDK calls generate a proper, valid signature, as expected and validated by AWS endpoints.

Current Behavior

The noted SDK call generates an invalid signature and is rejected by the AWS endpoint.

The full exception (sensitive portions redacted):

Exception in thread "main" aws.sdk.kotlin.services.apigatewaymanagementapi.model.ApiGatewayManagementException: The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.

The Canonical String for this request should have been
'POST
/%40connections/PiJ_BfnLoAMCJEw%253D

amz-sdk-invocation-id:<redcated>
amz-sdk-request:attempt=1; max=3
content-type:application/octet-stream
host:<redacted>
x-amz-date:20231206T200102Z
x-amz-security-token:<redacted>
x-amz-user-agent:aws-sdk-kotlin/1.0.10

amz-sdk-invocation-id;amz-sdk-request;content-type;host;x-amz-date;x-amz-security-token;x-amz-user-agent
7aa80cee51dcb3ca48c9f0af0a4b30a6ebdcf5bfdcca57352c6dfe7bb96cf64d'

The String-to-Sign should have been
'AWS4-HMAC-SHA256
20231206T200102Z
20231206/us-east-1/execute-api/aws4_request
ee606f6d4ade53ffc098d07d92eb55db183510cbcd6662c08669c6cc211c8027'

    at aws.sdk.kotlin.services.apigatewaymanagementapi.serde.PostToConnectionOperationDeserializerKt.throwPostToConnectionError(PostToConnectionOperationDeserializer.kt:49)
    at aws.sdk.kotlin.services.apigatewaymanagementapi.serde.PostToConnectionOperationDeserializerKt.access$throwPostToConnectionError(PostToConnectionOperationDeserializer.kt:1)
    at aws.sdk.kotlin.services.apigatewaymanagementapi.serde.PostToConnectionOperationDeserializerKt$throwPostToConnectionError$1.invokeSuspend(PostToConnectionOperationDeserializer.kt)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:283)
    at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith$default(DispatchedContinuation.kt:278)
    at kotlinx.coroutines.DispatchedCoroutine.afterResume(Builders.common.kt:261)
    at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:102)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)

Steps to Reproduce

A reproducer can be made available if need be; the above information should be enough to craft unit tests to validate issues with canonicalization.

Possible Solution

n/a

Context

This issue affects the SDK 1.0.0+ and prevents use of SDK calls that incorrectly sign the request. At the moment, for our use cases, it is limited to AWS API GW Websocket 'post to connection' calls.

Any SDK calls that have @ in the URL path may also be affected in a similar manner; the issue may extend beyond just the failure to escape @ and include other characters that invalidate the signature.

Your Environment

ianbotsf commented 7 months ago

Thanks for the bug report, @cloudshiftchris, I've reproduced the issue locally and I should have a fix available shortly.

ianbotsf commented 7 months ago

The fix for this issue has been pushed to main and released in smithy-kotlin 1.0.3. The change should be available in the latest release of aws-sdk-kotlin later today.

cloudshiftchris commented 7 months ago

Amazing. Thanks for the quick turnaround.

ianbotsf commented 7 months ago

Unfortunately the change did not make it into today's SDK release. It's staged and ready to release on Monday. Sorry for the delay.

ianbotsf commented 7 months ago

The fix is now released and available in aws-sdk-kotlin 1.0.14.

cloudshiftchris commented 7 months ago

Ran a repro with 1.0.14 with the same (failing) results; it looks like 1.0.14 still depends on smithy 1.0.2?

For reference this appears to pull in smithy 1.0.2 (the IDE and a Gradle BuildScan show 1.0.2):

plugins {
    kotlin("jvm") version "1.9.21"
}

dependencies {
    repositories {
        mavenCentral()
    }
    implementation("org.apache.logging.log4j:log4j-api:2.22.0")
    implementation("org.apache.logging.log4j:log4j-core:2.22.0")
    implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.22.0")

    // 0.36.1-beta works; 1.0.0+ (up to 1.0.10, as of current date) do not work
    implementation("aws.sdk.kotlin:apigatewaymanagementapi-jvm:1.0.14")

//    implementation("aws.sdk.kotlin:apigatewaymanagementapi-jvm:0.36.1-beta")
}

...do see the ASK SDK commit for libs.versions.toml to consume smithy 1.0.3. Hmmm.

ianbotsf commented 7 months ago

The latest 1.0.15 release of aws-sdk-kotlin now references the correct smithy-kotlin release and properly signs URLs containing special characters.