Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package aws.sdk.kotlin.runtime.auth

import aws.sdk.kotlin.runtime.auth.credentials.CredentialsProvider

/**
* A common interface that all AWS service clients implement as part of their configuration state.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.crt.auth.credentials.build
import kotlin.time.Duration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

/**
* Represents a set of AWS credentials
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

/**
* Represents a producer/source of AWS credentials
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.crt.auth.credentials.CredentialsProvider as CredentialsProviderCrt

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.crt.auth.credentials.Credentials as CredentialsCrt
import aws.sdk.kotlin.crt.auth.credentials.CredentialsProvider as CredentialsProviderCrt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.crt.auth.credentials.build
import aws.sdk.kotlin.runtime.crt.SdkDefaultIO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.runtime.AwsSdkSetting
import aws.sdk.kotlin.runtime.ConfigurationException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.crt.auth.credentials.build
import aws.sdk.kotlin.runtime.crt.SdkDefaultIO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

/**
* A credentials provider for a fixed set of credentials
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.signing

import aws.sdk.kotlin.crt.auth.signing.AwsSignedBodyValue
import aws.sdk.kotlin.crt.auth.signing.AwsSigner
import aws.sdk.kotlin.crt.auth.signing.AwsSigningAlgorithm
import aws.sdk.kotlin.crt.auth.signing.AwsSigningConfig
import aws.sdk.kotlin.runtime.InternalSdkApi
import aws.sdk.kotlin.runtime.auth.credentials.CredentialsProvider
import aws.sdk.kotlin.runtime.crt.toSignableCrtRequest
import aws.sdk.kotlin.runtime.crt.update
import aws.sdk.kotlin.runtime.execution.AuthAttributes
Expand All @@ -18,7 +17,6 @@ import aws.smithy.kotlin.runtime.http.*
import aws.smithy.kotlin.runtime.http.operation.SdkHttpOperation
import aws.smithy.kotlin.runtime.http.operation.withContext
import aws.smithy.kotlin.runtime.logging.Logger
import aws.smithy.kotlin.runtime.time.epochMilliseconds
import aws.smithy.kotlin.runtime.util.get

/**
Expand All @@ -44,6 +42,11 @@ public class AwsSigV4SigningMiddleware internal constructor(private val config:
*/
public var signatureType: AwsSignatureType = AwsSignatureType.HTTP_REQUEST_VIA_HEADERS

/**
* The algorithm to sign with
*/
public var algorithm: AwsSigningAlgorithm = AwsSigningAlgorithm.SIGV4

/**
* The uri is assumed to be encoded once in preparation for transmission. Certain services
* do not decode before checking signature, requiring double-encoding the uri in the canonical
Expand Down Expand Up @@ -78,8 +81,8 @@ public class AwsSigV4SigningMiddleware internal constructor(private val config:
override fun create(block: Config.() -> Unit): AwsSigV4SigningMiddleware {
val config = Config().apply(block)

requireNotNull(config.credentialsProvider) { "AwsSigv4Signer requires a credentialsProvider" }
requireNotNull(config.signingService) { "AwsSigv4Signer requires a signing service" }
requireNotNull(config.credentialsProvider) { "AwsSigv4SigningMiddleware requires a credentialsProvider" }
requireNotNull(config.signingService) { "AwsSigv4SigningMiddleware requires a signing service" }

return AwsSigV4SigningMiddleware(config)
}
Expand Down Expand Up @@ -117,19 +120,20 @@ public class AwsSigV4SigningMiddleware internal constructor(private val config:
// then we must decide how to compute the payload hash ourselves (defaults to unsigned payload)
val isUnboundedStream = signableRequest.body == null && req.subject.body is HttpBody.Streaming

val signingConfig: AwsSigningConfig = AwsSigningConfig.build {
// operation signing config is baseConfig + operation specific config/overrides
val opSigningConfig = AwsSigningConfig {
region = req.context[AuthAttributes.SigningRegion]
service = req.context.getOrNull(AuthAttributes.SigningService) ?: checkNotNull(config.signingService)
credentials = resolvedCredentials.toCrt()
algorithm = AwsSigningAlgorithm.SIGV4
date = req.context.getOrNull(AuthAttributes.SigningDate)?.epochMilliseconds
credentials = resolvedCredentials
algorithm = config.algorithm
date = req.context.getOrNull(AuthAttributes.SigningDate)

signatureType = config.signatureType.toCrt()
signatureType = config.signatureType
omitSessionToken = config.omitSessionToken
normalizeUriPath = config.normalizeUriPath
useDoubleUriEncode = config.useDoubleUriEncode

signedBodyHeader = config.signedBodyHeaderType.toCrt()
signedBodyHeader = config.signedBodyHeaderType
signedBodyValue = when {
isUnsignedRequest -> AwsSignedBodyValue.UNSIGNED_PAYLOAD
req.subject.body is HttpBody.Empty -> AwsSignedBodyValue.EMPTY_SHA256
Expand All @@ -141,7 +145,8 @@ public class AwsSigV4SigningMiddleware internal constructor(private val config:
else -> null
}
}
val signedRequest = AwsSigner.signRequest(signableRequest, signingConfig)

val signedRequest = AwsSigner.signRequest(signableRequest, opSigningConfig.toCrt())
req.subject.update(signedRequest)
req.subject.body.resetStream()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth.signing

import aws.sdk.kotlin.crt.auth.signing.AwsSigner
import aws.sdk.kotlin.runtime.crt.toSignableCrtRequest
import aws.sdk.kotlin.runtime.crt.update
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.http.request.toBuilder

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion

I assume the intent is that customers may use this type since there is no internal markers. the KDoc should probably be a bit more thorough in this case, specifying details for parameters for example.

* Container for signed output and signature
*
* @property output The signed output type (e.g. HttpRequest)
* @property signature The resulting signature. Depending on the requested signature type and algorithm,
* this value will be in one of the following formats:
*
* 1. [AwsSignatureType.HTTP_REQUEST_VIA_HEADERS] - hex encoding of the binary signature value
* 2. [AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS] - hex encoding of the binary signature value
* 3. [AwsSignatureType.HTTP_REQUEST_CHUNK] (SIGV4) - hex encoding of the binary signature value
* 4. [AwsSignatureType.HTTP_REQUEST_CHUNK] (SIGV4_ASYMMETRIC) - '*'-padded hex encoding of the binary signature value
* 5. [AwsSignatureType.HTTP_REQUEST_EVENT] - binary signature value (NYI)
*
*/
public data class SigningResult<T>(val output: T, val signature: ByteArray) {
Copy link
Contributor

Choose a reason for hiding this comment

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

question

why is equals/hashCode impl required for this type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Data classes don't handle arrays as expected. What you want usually is to compare the contents of the array not whether they are the same instance (which is what it will do by default). Intellij even warns you of this and generates the more appropriate one for you.

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as SigningResult<*>

if (output != other.output) return false
if (!signature.contentEquals(other.signature)) return false

return true
}

override fun hashCode(): Int {
var result = output?.hashCode() ?: 0
result = 31 * result + signature.contentHashCode()
return result
}
}

/**
* Sign [HttpRequest] using the given signing [config]
*
* @param request the HTTP request to sign
* @param config the signing configuration to use
* @return the signing result
*/
public suspend fun sign(request: HttpRequest, config: AwsSigningConfig): SigningResult<HttpRequest> {
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above regarding kdoc

val crtRequest = request.toSignableCrtRequest()
val crtResult = AwsSigner.sign(crtRequest, config.toCrt())
val crtSignedRequest = checkNotNull(crtResult.signedRequest) { "Signed request unexpectedly null" }
val builder = request.toBuilder()
builder.update(crtSignedRequest)
val output = builder.build()
return SigningResult(output, crtResult.signature)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.signing

import aws.sdk.kotlin.runtime.InternalSdkApi
import aws.sdk.kotlin.runtime.auth.credentials.Credentials
import aws.sdk.kotlin.runtime.auth.credentials.CredentialsProvider
import aws.sdk.kotlin.runtime.auth.credentials.toCrt
import aws.smithy.kotlin.runtime.time.Instant
import aws.smithy.kotlin.runtime.time.epochMilliseconds
import kotlin.time.Duration
import kotlin.time.ExperimentalTime

/**
* Predicate function used to determine if a specific header should be signed or not
*/
public typealias ShouldSignHeaderFn = (String) -> Boolean

/**
* Configuration that tells the underlying AWS signer how to sign requests
*/
@OptIn(ExperimentalTime::class)
@InternalSdkApi
public class AwsSigningConfig private constructor(builder: Builder) {
public companion object {
public operator fun invoke(block: Builder.() -> Unit): AwsSigningConfig = Builder().apply(block).build()
Expand Down Expand Up @@ -78,6 +85,11 @@ public class AwsSigningConfig private constructor(builder: Builder) {
*/
public val signedBodyHeaderType: AwsSignedBodyHeaderType = builder.signedBodyHeader

/**
* Predicate function used to determine if a specific header should be signed or not
*/
public val shouldSignHeader: ShouldSignHeaderFn? = builder.shouldSignHeader

/*
* Signing key control:
*
Expand Down Expand Up @@ -115,6 +127,7 @@ public class AwsSigningConfig private constructor(builder: Builder) {
public var service: String? = null
public var date: Instant? = null
public var algorithm: AwsSigningAlgorithm = AwsSigningAlgorithm.SIGV4
public var shouldSignHeader: ShouldSignHeaderFn? = null
public var signatureType: AwsSignatureType = AwsSignatureType.HTTP_REQUEST_VIA_HEADERS
public var useDoubleUriEncode: Boolean = true
public var normalizeUriPath: Boolean = true
Expand All @@ -140,7 +153,12 @@ public enum class AwsSigningAlgorithm {
* AWS Signature Version 4
* see: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
*/
SIGV4;
SIGV4,

/**
* AWS Signature Version 4 Asymmetric
*/
SIGV4_ASYMMETRIC;
}

/**
Expand Down Expand Up @@ -183,6 +201,18 @@ public enum class AwsSignedBodyHeaderType {
X_AMZ_CONTENT_SHA256;
}

/**
* A set of string constants for various canonical request payload values. If signedBodyValue is not null
* then the value will also be reflected in X-Amz-Content-Sha256
*/
public object AwsSignedBodyValue {
public const val EMPTY_SHA256: String = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
public const val UNSIGNED_PAYLOAD: String = "UNSIGNED-PAYLOAD"
public const val STREAMING_AWS4_HMAC_SHA256_PAYLOAD: String = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
public const val STREAMING_AWS4_ECDSA_P256_SHA256_PAYLOAD: String = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD"
public const val STREAMING_AWS4_HMAC_SHA256_EVENTS: String = "STREAMING-AWS4-HMAC-SHA256-EVENTS"
}

internal fun AwsSignedBodyHeaderType.toCrt(): aws.sdk.kotlin.crt.auth.signing.AwsSignedBodyHeaderType = when (this) {
AwsSignedBodyHeaderType.NONE -> aws.sdk.kotlin.crt.auth.signing.AwsSignedBodyHeaderType.NONE
AwsSignedBodyHeaderType.X_AMZ_CONTENT_SHA256 -> aws.sdk.kotlin.crt.auth.signing.AwsSignedBodyHeaderType.X_AMZ_CONTENT_SHA256
Expand All @@ -197,4 +227,36 @@ internal fun AwsSignatureType.toCrt(): aws.sdk.kotlin.crt.auth.signing.AwsSignat

internal fun AwsSigningAlgorithm.toCrt(): aws.sdk.kotlin.crt.auth.signing.AwsSigningAlgorithm = when (this) {
AwsSigningAlgorithm.SIGV4 -> aws.sdk.kotlin.crt.auth.signing.AwsSigningAlgorithm.SIGV4
AwsSigningAlgorithm.SIGV4_ASYMMETRIC -> aws.sdk.kotlin.crt.auth.signing.AwsSigningAlgorithm.SIGV4_ASYMMETRIC
}

@OptIn(ExperimentalTime::class)
internal suspend fun AwsSigningConfig.toCrt(): aws.sdk.kotlin.crt.auth.signing.AwsSigningConfig {
val config = this
// NOTE: we cannot pass a credentialsProvider due to https://github.com/awslabs/aws-crt-kotlin/issues/15
// the underlying implementation will hang/fail to sign
val resolvedCredentials = config.credentials ?: config.credentialsProvider?.getCredentials()

return aws.sdk.kotlin.crt.auth.signing.AwsSigningConfig.build {
algorithm = config.algorithm.toCrt()
credentials = resolvedCredentials?.toCrt()

date = config.date?.epochMilliseconds

config.expiresAfter?.let {
expirationInSeconds = it.inWholeSeconds
}

normalizeUriPath = config.normalizeUriPath
omitSessionToken = config.omitSessionToken

region = config.region
service = config.service

shouldSignHeader = config.shouldSignHeader
signatureType = config.signatureType.toCrt()
signedBodyHeader = config.signedBodyHeaderType.toCrt()
signedBodyValue = config.signedBodyValue
useDoubleUriEncode = config.useDoubleUriEncode
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package aws.sdk.kotlin.runtime.auth
/*
Copy link
Contributor

Choose a reason for hiding this comment

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

my bad

* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth.signing

import aws.sdk.kotlin.crt.auth.signing.AwsSignatureType
import aws.sdk.kotlin.crt.auth.signing.AwsSignedBodyHeaderType
import aws.sdk.kotlin.crt.auth.signing.AwsSignedBodyValue
import aws.sdk.kotlin.crt.auth.signing.AwsSigner
import aws.sdk.kotlin.crt.auth.signing.AwsSigningConfig
import aws.sdk.kotlin.runtime.auth.credentials.CredentialsProvider
import aws.sdk.kotlin.runtime.auth.credentials.toCrt
import aws.sdk.kotlin.runtime.crt.path
import aws.sdk.kotlin.runtime.crt.queryParameters
import aws.sdk.kotlin.runtime.crt.toCrtHeaders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.runtime.AwsSdkSetting
import aws.sdk.kotlin.runtime.ConfigurationException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0.
*/

package aws.sdk.kotlin.runtime.auth
package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.runtime.testing.runSuspendTest
import kotlin.test.Test
Expand Down
Loading