Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dd715a1
Re-enable aws-signing-tests
lauzadis Mar 19, 2025
61c9b69
Re-enable `testReadAfterExecutionSuppressedException`
lauzadis Mar 19, 2025
f5660b5
ktlint
lauzadis Mar 19, 2025
1281338
Re-enable some tests
lauzadis Mar 19, 2025
986907f
Initialize CRT in CrtAwsSigner
lauzadis Mar 19, 2025
9f8999e
Update FIXME message
lauzadis Mar 19, 2025
3fca267
Ignore SigV4a test on Native
lauzadis Mar 19, 2025
ba8f1cb
Re-enable `AwsChunkedByteReadChannelTestBase`
lauzadis Mar 19, 2025
3bbc14a
Re-enable `AwsChunkedTestBase`
lauzadis Mar 19, 2025
00d10a5
Initialize CRT runtime to get correct error code
lauzadis Mar 19, 2025
eac0502
Update FIXME message
lauzadis Mar 19, 2025
a0d2fc5
ktlint
lauzadis Mar 19, 2025
d8878a3
Implement transferRequestBody
lauzadis Mar 19, 2025
1eebd5e
Update FIXME message
lauzadis Mar 19, 2025
27af57e
Make `DefaultAwsSigner` expect/actual, point to CRT signer on Native
lauzadis Mar 19, 2025
8d73a86
ktlint
lauzadis Mar 19, 2025
ed5da6b
Unignore passing test
lauzadis Mar 19, 2025
5ad9d5e
Update FIXME message
lauzadis Mar 20, 2025
cf45c86
CI
lauzadis Mar 21, 2025
6b1756b
CI
lauzadis Mar 21, 2025
eeb8463
CI
lauzadis Mar 24, 2025
c5905cd
Use setup-build action in artifact-size-metrics to pull in aws-crt-ko…
lauzadis Mar 24, 2025
11dac4e
Try to fix path
lauzadis Mar 24, 2025
2857ecd
CI glitch: size-check Expected — Waiting for status to be reported
lauzadis Mar 24, 2025
3580da4
Temporarily remove `size-check`
lauzadis Mar 24, 2025
d4bb8e4
Revert "Temporarily remove `size-check`"
lauzadis Mar 24, 2025
56d6f71
Add a FIXME comment for `ex` / `ex.cause`
lauzadis Mar 25, 2025
4ea1a06
Address nit / `run { ... }`
lauzadis Mar 25, 2025
d17bf67
Remove getter for `DefaultAwsSigner` values
lauzadis Mar 25, 2025
5621fe0
Add an explanation for CRT.initRuntime
lauzadis Mar 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .github/workflows/artifact-size-metrics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,25 @@ jobs:
steps:
- name: Checkout Sources
uses: actions/checkout@v4
- name: Configure JDK
uses: actions/setup-java@v3
with:
distribution: 'corretto'
java-version: 17
cache: 'gradle'

- name: Setup build
uses: .github/actions/setup-build

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.CI_AWS_ROLE_ARN }}
aws-region: us-west-2

- name: Configure Gradle
uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main

- name: Generate Artifact Size Metrics
run: ./gradlew -Paws.kotlin.native=false artifactSizeMetrics

- name: Analyze Artifact Size Metrics
run: ./gradlew analyzeArtifactSizeMetrics

- name: Show Results
uses: actions/github-script@v7
with:
Expand Down
2 changes: 1 addition & 1 deletion runtime/auth/aws-signing-crt/api/aws-signing-crt.api
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public final class aws/smithy/kotlin/runtime/auth/awssigning/crt/CrtAwsSigner : aws/smithy/kotlin/runtime/auth/awssigning/AwsSigner {
public final class aws/smithy/kotlin/runtime/auth/awssigning/crt/CrtAwsSigner : aws/sdk/kotlin/crt/WithCrt, aws/smithy/kotlin/runtime/auth/awssigning/AwsSigner {
public static final field INSTANCE Laws/smithy/kotlin/runtime/auth/awssigning/crt/CrtAwsSigner;
public fun sign (Laws/smithy/kotlin/runtime/http/request/HttpRequest;Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun signChunk ([B[BLaws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package aws.smithy.kotlin.runtime.auth.awssigning.crt

import aws.sdk.kotlin.crt.WithCrt
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
import aws.smithy.kotlin.runtime.auth.awssigning.*
import aws.smithy.kotlin.runtime.crt.toSignableCrtRequest
Expand All @@ -24,7 +25,7 @@ import aws.sdk.kotlin.crt.http.Headers as CrtHeaders

private const val S3_EXPRESS_HEADER_NAME = "X-Amz-S3session-Token"

public object CrtAwsSigner : AwsSigner {
public object CrtAwsSigner : AwsSigner, WithCrt() {
override suspend fun sign(request: HttpRequest, config: AwsSigningConfig): AwsSigningResult<HttpRequest> {
val isUnsigned = config.hashSpecification is HashSpecification.UnsignedPayload
val isAwsChunked = request.headers.contains("Content-Encoding", "aws-chunked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public final class aws/smithy/kotlin/runtime/auth/awssigning/DefaultAwsSignerBui
public final fun setTelemetryProvider (Laws/smithy/kotlin/runtime/telemetry/TelemetryProvider;)V
}

public final class aws/smithy/kotlin/runtime/auth/awssigning/DefaultAwsSignerKt {
public final class aws/smithy/kotlin/runtime/auth/awssigning/DefaultAwsSignerJVMKt {
public static final fun DefaultAwsSigner (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigner;
public static final fun getDefaultAwsSigner ()Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigner;
}
Expand Down
6 changes: 6 additions & 0 deletions runtime/auth/aws-signing-default/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,11 @@ kotlin {
all {
languageSettings.optIn("aws.smithy.kotlin.runtime.InternalApi")
}

nativeMain {
dependencies {
implementation(project(":runtime:auth:aws-signing-crt"))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,136 +4,5 @@
*/
package aws.smithy.kotlin.runtime.auth.awssigning

import aws.smithy.kotlin.runtime.ExperimentalApi
import aws.smithy.kotlin.runtime.http.Headers
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.telemetry.TelemetryProvider
import aws.smithy.kotlin.runtime.telemetry.logging.logger
import aws.smithy.kotlin.runtime.time.TimestampFormat
import kotlin.coroutines.coroutineContext

/** The default implementation of [AwsSigner] */
public val DefaultAwsSigner: AwsSigner = DefaultAwsSignerImpl()

/** Creates a customized instance of [AwsSigner] */
@Suppress("ktlint:standard:function-naming")
public fun DefaultAwsSigner(block: DefaultAwsSignerBuilder.() -> Unit): AwsSigner =
DefaultAwsSignerBuilder().apply(block).build()

/** A builder class for creating instances of [AwsSigner] using the default implementation */
public class DefaultAwsSignerBuilder {
public var telemetryProvider: TelemetryProvider? = null

public fun build(): AwsSigner = DefaultAwsSignerImpl(
telemetryProvider = telemetryProvider,
)
}

private val AwsSigningAlgorithm.signatureCalculator
get() = when (this) {
AwsSigningAlgorithm.SIGV4 -> SignatureCalculator.SigV4
AwsSigningAlgorithm.SIGV4_ASYMMETRIC -> SignatureCalculator.SigV4a
}

@OptIn(ExperimentalApi::class)
internal class DefaultAwsSignerImpl(
private val canonicalizer: Canonicalizer = Canonicalizer.Default,
private val requestMutator: RequestMutator = RequestMutator.Default,
private val telemetryProvider: TelemetryProvider? = null,
) : AwsSigner {
override suspend fun sign(request: HttpRequest, config: AwsSigningConfig): AwsSigningResult<HttpRequest> {
val logger = telemetryProvider?.loggerProvider?.getOrCreateLogger("DefaultAwsSigner")
?: coroutineContext.logger<DefaultAwsSignerImpl>()

val canonical = canonicalizer.canonicalRequest(request, config)
if (config.logRequest) {
logger.trace { "Canonical request:\n${canonical.requestString}" }
}

val signatureCalculator = config.algorithm.signatureCalculator

val stringToSign = signatureCalculator.stringToSign(canonical.requestString, config)
logger.trace { "String to sign:\n$stringToSign" }

val signingKey = signatureCalculator.signingKey(config)

val signature = signatureCalculator.calculate(signingKey, stringToSign)
logger.debug { "Calculated signature: $signature" }

val signedRequest = requestMutator.appendAuth(config, canonical, signature)

return AwsSigningResult(signedRequest, signature.encodeToByteArray())
}

override suspend fun signChunk(
chunkBody: ByteArray,
prevSignature: ByteArray,
config: AwsSigningConfig,
): AwsSigningResult<Unit> {
val logger = telemetryProvider?.loggerProvider?.getOrCreateLogger("DefaultAwsSigner")
?: coroutineContext.logger<DefaultAwsSignerImpl>()

val signatureCalculator = config.algorithm.signatureCalculator

val stringToSign = signatureCalculator.chunkStringToSign(chunkBody, prevSignature, config)
logger.trace { "Chunk string to sign:\n$stringToSign" }

val signingKey = signatureCalculator.signingKey(config)

val signature = signatureCalculator.calculate(signingKey, stringToSign)
logger.debug { "Calculated chunk signature: $signature" }

return AwsSigningResult(Unit, signature.encodeToByteArray())
}

override suspend fun signChunkTrailer(
trailingHeaders: Headers,
prevSignature: ByteArray,
config: AwsSigningConfig,
): AwsSigningResult<Unit> {
val logger = telemetryProvider?.loggerProvider?.getOrCreateLogger("DefaultAwsSigner")
?: coroutineContext.logger<DefaultAwsSignerImpl>()

val signatureCalculator = config.algorithm.signatureCalculator

// FIXME - can we share canonicalization code more than we are..., also this reduce is inefficient.
// canonicalize the headers
val trailingHeadersBytes = trailingHeaders.entries().sortedBy { e -> e.key.lowercase() }
.map { e ->
buildString {
append(e.key.lowercase())
append(":")
append(e.value.joinToString(",") { v -> v.trim() })
append("\n")
}.encodeToByteArray()
}.reduce { acc, bytes -> acc + bytes }

val stringToSign = signatureCalculator.chunkTrailerStringToSign(trailingHeadersBytes, prevSignature, config)
logger.trace { "Chunk trailer string to sign:\n$stringToSign" }

val signingKey = signatureCalculator.signingKey(config)

val signature = signatureCalculator.calculate(signingKey, stringToSign)
logger.debug { "Calculated chunk signature: $signature" }

return AwsSigningResult(Unit, signature.encodeToByteArray())
}
}

/**
* Formats a credential scope consisting of a signing date, region (SigV4 only), service, and a signature type
*/
internal val AwsSigningConfig.credentialScope: String
get() = run {
val signingDate = signingDate.format(TimestampFormat.ISO_8601_CONDENSED_DATE)
return when (algorithm) {
AwsSigningAlgorithm.SIGV4 -> "$signingDate/$region/$service/aws4_request"
AwsSigningAlgorithm.SIGV4_ASYMMETRIC -> "$signingDate/$service/aws4_request"
}
}

/**
* Formats the value for a credential header/parameter
*/
internal fun credentialValue(config: AwsSigningConfig): String =
"${config.credentials.accessKeyId}/${config.credentialScope}"
public expect val DefaultAwsSigner: AwsSigner
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package aws.smithy.kotlin.runtime.auth.awssigning

import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.time.TimestampFormat

/**
* An object that can mutate requests to include signing attributes.
Expand Down Expand Up @@ -55,3 +56,21 @@ internal class DefaultRequestMutator : RequestMutator {
return canonical.request.build()
}
}

/**
* Formats a credential scope consisting of a signing date, region (SigV4 only), service, and a signature type
*/
internal val AwsSigningConfig.credentialScope: String
get() {
val signingDate = signingDate.format(TimestampFormat.ISO_8601_CONDENSED_DATE)
return when (algorithm) {
AwsSigningAlgorithm.SIGV4 -> "$signingDate/$region/$service/aws4_request"
AwsSigningAlgorithm.SIGV4_ASYMMETRIC -> "$signingDate/$service/aws4_request"
}
}

/**
* Formats the value for a credential header/parameter
*/
internal fun credentialValue(config: AwsSigningConfig): String =
"${config.credentials.accessKeyId}/${config.credentialScope}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.smithy.kotlin.runtime.auth.awssigning

import aws.smithy.kotlin.runtime.ExperimentalApi
import aws.smithy.kotlin.runtime.http.Headers
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.telemetry.TelemetryProvider
import aws.smithy.kotlin.runtime.telemetry.logging.logger
import kotlin.coroutines.coroutineContext

/** The default implementation of [AwsSigner] */
public actual val DefaultAwsSigner: AwsSigner = DefaultAwsSignerImpl()

/** Creates a customized instance of [AwsSigner] */
@Suppress("ktlint:standard:function-naming")
public fun DefaultAwsSigner(block: DefaultAwsSignerBuilder.() -> Unit): AwsSigner =
DefaultAwsSignerBuilder().apply(block).build()

/** A builder class for creating instances of [AwsSigner] using the default implementation */
public class DefaultAwsSignerBuilder {
public var telemetryProvider: TelemetryProvider? = null

public fun build(): AwsSigner = DefaultAwsSignerImpl(
telemetryProvider = telemetryProvider,
)
}
Comment on lines +17 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opinion: It now feels weird that this factory function and builder type exist only in JVM when the named value exists in all source sets.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed separately and agreed that this API disparity is fine for now and can be reexamined if we ever see problems or get feedback.


private val AwsSigningAlgorithm.signatureCalculator
get() = when (this) {
AwsSigningAlgorithm.SIGV4 -> SignatureCalculator.SigV4
AwsSigningAlgorithm.SIGV4_ASYMMETRIC -> SignatureCalculator.SigV4a
}

@OptIn(ExperimentalApi::class)
internal class DefaultAwsSignerImpl(
private val canonicalizer: Canonicalizer = Canonicalizer.Default,
private val requestMutator: RequestMutator = RequestMutator.Default,
private val telemetryProvider: TelemetryProvider? = null,
) : AwsSigner {
override suspend fun sign(request: HttpRequest, config: AwsSigningConfig): AwsSigningResult<HttpRequest> {
val logger = telemetryProvider?.loggerProvider?.getOrCreateLogger("DefaultAwsSigner")
?: coroutineContext.logger<DefaultAwsSignerImpl>()

val canonical = canonicalizer.canonicalRequest(request, config)
if (config.logRequest) {
logger.trace { "Canonical request:\n${canonical.requestString}" }
}

val signatureCalculator = config.algorithm.signatureCalculator

val stringToSign = signatureCalculator.stringToSign(canonical.requestString, config)
logger.trace { "String to sign:\n$stringToSign" }

val signingKey = signatureCalculator.signingKey(config)

val signature = signatureCalculator.calculate(signingKey, stringToSign)
logger.debug { "Calculated signature: $signature" }

val signedRequest = requestMutator.appendAuth(config, canonical, signature)

return AwsSigningResult(signedRequest, signature.encodeToByteArray())
}

override suspend fun signChunk(
chunkBody: ByteArray,
prevSignature: ByteArray,
config: AwsSigningConfig,
): AwsSigningResult<Unit> {
val logger = telemetryProvider?.loggerProvider?.getOrCreateLogger("DefaultAwsSigner")
?: coroutineContext.logger<DefaultAwsSignerImpl>()

val signatureCalculator = config.algorithm.signatureCalculator

val stringToSign = signatureCalculator.chunkStringToSign(chunkBody, prevSignature, config)
logger.trace { "Chunk string to sign:\n$stringToSign" }

val signingKey = signatureCalculator.signingKey(config)

val signature = signatureCalculator.calculate(signingKey, stringToSign)
logger.debug { "Calculated chunk signature: $signature" }

return AwsSigningResult(Unit, signature.encodeToByteArray())
}

override suspend fun signChunkTrailer(
trailingHeaders: Headers,
prevSignature: ByteArray,
config: AwsSigningConfig,
): AwsSigningResult<Unit> {
val logger = telemetryProvider?.loggerProvider?.getOrCreateLogger("DefaultAwsSigner")
?: coroutineContext.logger<DefaultAwsSignerImpl>()

val signatureCalculator = config.algorithm.signatureCalculator

// FIXME - can we share canonicalization code more than we are..., also this reduce is inefficient.
// canonicalize the headers
val trailingHeadersBytes = trailingHeaders.entries().sortedBy { e -> e.key.lowercase() }
.map { e ->
buildString {
append(e.key.lowercase())
append(":")
append(e.value.joinToString(",") { v -> v.trim() })
append("\n")
}.encodeToByteArray()
}.reduce { acc, bytes -> acc + bytes }

val stringToSign = signatureCalculator.chunkTrailerStringToSign(trailingHeadersBytes, prevSignature, config)
logger.trace { "Chunk trailer string to sign:\n$stringToSign" }

val signingKey = signatureCalculator.signingKey(config)

val signature = signatureCalculator.calculate(signingKey, stringToSign)
logger.debug { "Calculated chunk signature: $signature" }

return AwsSigningResult(Unit, signature.encodeToByteArray())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.smithy.kotlin.runtime.auth.awssigning

import aws.smithy.kotlin.runtime.auth.awssigning.crt.CrtAwsSigner

/** The default implementation of [AwsSigner] */
public actual val DefaultAwsSigner: AwsSigner = CrtAwsSigner
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

package aws.smithy.kotlin.runtime.auth.awssigning.tests

import aws.smithy.kotlin.runtime.IgnoreNative
import aws.smithy.kotlin.runtime.auth.awssigning.*
import aws.smithy.kotlin.runtime.auth.awssigning.internal.CHUNK_SIZE_BYTES
import aws.smithy.kotlin.runtime.io.*
Expand All @@ -18,7 +17,6 @@ import kotlin.test.*
import kotlin.time.Duration.Companion.milliseconds

abstract class AwsChunkedByteReadChannelTestBase : AwsChunkedTestBase(AwsChunkedReaderFactory.Channel) {
@IgnoreNative // FIXME Re-enable after Kotlin/Native Implementation
@Test
fun testSlowProducerMultipleChunksPartialLast(): TestResult = runTest {
val numChunks = 6
Expand Down
Loading
Loading