Skip to content

Commit

Permalink
Feat: Chunk Signer (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
waahm7 committed Feb 2, 2023
1 parent c38f4f7 commit 99b7d62
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 35 deletions.
105 changes: 101 additions & 4 deletions Source/AwsCommonRuntimeKit/auth/signing/Signer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public class Signer {
allocator.rawValue,
signable,
configBasePointer,
onSigningComplete,
onRequestSigningComplete,
signRequestCore.passRetained())
!= AWS_OP_SUCCESS {

Expand All @@ -72,6 +72,82 @@ public class Signer {
}
}
}

/// Signs a body chunk according to the supplied signing configuration
/// - Parameters:
/// - chunk: Chunk to sign
/// - previousSignature: The signature of the previous component of the request: either the request itself for the first chunk,
/// or the previous chunk otherwise.
/// - config: The `SigningConfig` to use when signing.
/// - allocator: (Optional) allocator to override
/// - Returns: Signature of the chunk
/// - Throws: CommonRunTimeError.crtError
public static func signChunk(chunk: Data,
previousSignature: String,
config: SigningConfig,
allocator: Allocator = defaultAllocator) async throws -> String {
let iStreamCore = IStreamCore(iStreamable: ByteBuffer(data: chunk), allocator: allocator)
guard let signable = previousSignature.withByteCursorPointer({ previousSignatureCursor in
aws_signable_new_chunk(allocator.rawValue, iStreamCore.rawValue, previousSignatureCursor.pointee)
}) else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
defer {
aws_signable_destroy(signable)
}

return try await sign(config: config, signable: signable, allocator: allocator)
}

/// Signs trailing headers according to the supplied signing configuration
/// - Parameters:
/// - headers: list of headers to be sent in the trailer.
/// - previousSignature: The signature of the previous component of the request: either the request itself for the first chunk,
/// or the previous chunk otherwise.
/// - config: The `SigningConfig` to use when signing.
/// - allocator: (Optional) allocator to override
/// - Returns: Signing Result
/// - Throws: CommonRunTimeError.crtError
public static func signTrailerHeaders(headers: [HTTPHeader],
previousSignature: String,
config: SigningConfig,
allocator: Allocator = defaultAllocator) async throws -> String {

guard let signable = previousSignature.withByteCursorPointer({ previousSignatureCursor in
headers.withCHeaders(allocator: allocator) { cHeaders in
aws_signable_new_trailing_headers(allocator.rawValue, cHeaders, previousSignatureCursor.pointee)
}
}) else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
defer {
aws_signable_destroy(signable)
}
return try await sign(config: config, signable: signable, allocator: allocator)
}

private static func sign(config: SigningConfig,
signable: UnsafePointer<aws_signable>,
allocator: Allocator) async throws -> String {

try await withCheckedThrowingContinuation { continuation in
config.withCPointer { configPointer in
configPointer.withMemoryRebound(to: aws_signing_config_base.self,
capacity: 1) { configBasePointer in
let continuationCore = ContinuationCore(continuation: continuation)
if aws_sign_request_aws(allocator.rawValue,
signable,
configBasePointer,
onSigningComplete,
continuationCore.passRetained())
!= AWS_OP_SUCCESS {
continuationCore.release()
continuation.resume(throwing: CommonRunTimeError.crtError(.makeFromLastError()))
}
}
}
}
}
}

class SignRequestCore {
Expand Down Expand Up @@ -102,9 +178,9 @@ class SignRequestCore {
}
}

private func onSigningComplete(signingResult: UnsafeMutablePointer<aws_signing_result>?,
errorCode: Int32,
userData: UnsafeMutableRawPointer!) {
private func onRequestSigningComplete(signingResult: UnsafeMutablePointer<aws_signing_result>?,
errorCode: Int32,
userData: UnsafeMutableRawPointer!) {
let signRequestCore = Unmanaged<SignRequestCore>.fromOpaque(userData).takeRetainedValue()
if errorCode != AWS_OP_SUCCESS {
signRequestCore.continuation.resume(throwing: CommonRunTimeError.crtError(CRTError(code: errorCode)))
Expand All @@ -121,3 +197,24 @@ private func onSigningComplete(signingResult: UnsafeMutablePointer<aws_signing_r
signRequestCore.continuation.resume(throwing: CommonRunTimeError.crtError(.makeFromLastError()))
}
}

private func onSigningComplete(signingResult: UnsafeMutablePointer<aws_signing_result>?,
errorCode: Int32,
userData: UnsafeMutableRawPointer!) {
let chunkSignerCore = Unmanaged<ContinuationCore<String>>.fromOpaque(userData).takeRetainedValue()
guard errorCode == AWS_OP_SUCCESS else {
chunkSignerCore.continuation.resume(throwing: CommonRunTimeError.crtError(CRTError(code: errorCode)))
return
}

// Success
var awsStringPointer: UnsafeMutablePointer<aws_string>!
guard aws_signing_result_get_property(
signingResult!,
g_aws_signature_property_name,
&awsStringPointer) == AWS_OP_SUCCESS else {
chunkSignerCore.continuation.resume(throwing: CommonRunTimeError.crtError(.makeFromLastError()))
return
}
chunkSignerCore.continuation.resume(returning: String(awsString: awsStringPointer)!)
}
46 changes: 24 additions & 22 deletions Source/AwsCommonRuntimeKit/auth/signing/SigningConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,32 +137,22 @@ private func onShouldSignHeader(nameCursor: UnsafePointer<aws_byte_cursor>!,
}

public enum SignatureType {
/**
A signature for a full http request should be computed, with header updates applied to the signing result.
*/

/// A signature for a full http request should be computed, with header updates applied to the signing result.
case requestHeaders

/**
A signature for a full http request should be computed, with query param updates applied to the signing result.
*/
/// A signature for a full http request should be computed, with query param updates applied to the signing result.
case requestQueryParams

/**
Compute a signature for a payload chunk. The signable's input stream should be the chunk data and the
signable should contain the most recent signature value (either the original http request or the most recent
chunk) in the "previous-signature" property.
*/
/// Compute a signature for a payload chunk. The signable's input stream should be the chunk data and the
/// signable should contain the most recent signature value (either the original http request or the most recent
/// chunk) in the "previous-signature" property.
case requestChunk

/**
Compute a signature for an event stream event. The signable's input stream should be the event payload, the
signable should contain the most recent signature value (either the original http request or the most recent
event) in the "previous-signature" property as well as any event headers that should be signed with the
exception of ":date"
This option is not yet supported.
*/
case requestEvent
/// Compute a signature for the trailing headers.
/// the signable should contain the most recent signature value (either the original http request or the most recent
/// chunk) in the "previous-signature" property.
case requestTrailingHeaders
}

public enum SignedBodyHeaderType {
Expand All @@ -174,14 +164,26 @@ public enum SignedBodyHeaderType {
case contentSha256
}

/// Optional string to use as the canonical request's body value.
/// Typically, this is the SHA-256 of the (request/chunk/event) payload, written as lowercase hex.
/// If this has been precalculated, it can be set here. Special values used by certain services can also be set.
public enum SignedBodyValue: String {
/// if string is empty a public value will be calculated from the payload during signing
/// if empty, a public value will be calculated from the payload during signing
case empty = ""
/// For empty sha256
case emptySha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
/// Use this in the case of needing to not use the payload for signing
case unsignedPayload = "UNSIGNED-PAYLOAD"
/// For streaming sha256 payload
case streamingSha256Payload = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
/// For streaming sigv4a sha256 payload
case streamingECDSA_P256Sha256Payload = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD"
/// For streaming sigv4a sha256 payload trailer
case streamingECDSA_P256Sha256PayloadTrailer = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER"
/// For streaming sigv4a sha256 events
case streamingSha256Events = "STREAMING-AWS4-HMAC-SHA256-EVENTS"
/// For streaming unsigned payload trailer
case streamingUnSignedPayloadTrailer = "STREAMING-UNSIGNED-PAYLOAD-TRAILER"
}

public enum SigningAlgorithmType {
Expand All @@ -201,7 +203,7 @@ extension SignatureType: RawRepresentable, CaseIterable {
case .requestHeaders: return AWS_ST_HTTP_REQUEST_HEADERS
case .requestQueryParams: return AWS_ST_HTTP_REQUEST_QUERY_PARAMS
case .requestChunk: return AWS_ST_HTTP_REQUEST_CHUNK
case .requestEvent: return AWS_ST_HTTP_REQUEST_EVENT
case .requestTrailingHeaders: return AWS_ST_HTTP_REQUEST_TRAILING_HEADERS
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions Source/AwsCommonRuntimeKit/http/HTTPHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,22 @@ public class HTTPHeader: CStruct {
}
}
}

extension Array where Element == HTTPHeader {
func withCHeaders<Result>(allocator: Allocator,
_ body: (OpaquePointer) -> Result) -> Result {
let cHeaders: OpaquePointer = aws_http_headers_new(allocator.rawValue)
defer {
aws_http_headers_release(cHeaders)
}
forEach {
$0.withCPointer {
guard aws_http_headers_add_header(cHeaders, $0) == AWS_OP_SUCCESS else {
let error = CRTError.makeFromLastError()
fatalError("Unable to add header due to error code: \(error.code) message:\(error.message)")
}
}
}
return body(cHeaders)
}
}
2 changes: 1 addition & 1 deletion Source/AwsCommonRuntimeKit/http/HTTPRequestOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public struct HTTPRequestOptions {
/// When using HTTP/2, set http2ManualDataWrites to true to specify that request body data will be provided over time.
/// The stream will only be polled for writing when data has been supplied via `HTTP2Stream.writeData`
public var http2ManualDataWrites: Bool = false

public init(request: HTTPRequestBase,
onInterimResponse: OnInterimResponse? = nil,
onResponse: @escaping OnResponse,
Expand Down
8 changes: 4 additions & 4 deletions Source/AwsCommonRuntimeKit/http/HTTPStreamCallbackCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ private func onResponseHeaderBlockDone(stream: UnsafeMutablePointer<aws_http_str
var status: Int32 = 0
guard aws_http_stream_get_incoming_response_status(stream!, &status) == AWS_OP_SUCCESS else {
fatalError(
"""
Failed to get HTTP status code in onResponseHeaderBlockDone callback with error
\(CommonRunTimeError.crtError(.makeFromLastError()))
"""
"""
Failed to get HTTP status code in onResponseHeaderBlockDone callback with error
\(CommonRunTimeError.crtError(.makeFromLastError()))
"""
)
}
switch HTTPHeaderBlock(rawValue: headerBlock) {
Expand Down
Loading

0 comments on commit 99b7d62

Please sign in to comment.