From b8af0b2a43118f347cc91abf0f127c49c90bfe77 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 3 Aug 2020 11:28:49 +0100 Subject: [PATCH] SwiftCrypto (#3) * Use swift-crypto for hashing requirements * Remove swift 5.0 as it won't compile * Optimization - stop recreating DateFormatter * Ressurected AWSCrypto so we can support earlier OS versions * Update CI for workdir issue, remove libssl-dev --- .github/workflows/swift.yml | 15 +--- Dockerfile | 4 +- Package.swift | 22 +++--- Sources/AWSCrypto/ByteArray.swift | 13 ++-- Sources/AWSCrypto/Digest.swift | 9 ++- Sources/AWSCrypto/HMAC.swift | 70 ++--------------- Sources/AWSCrypto/HashFunction.swift | 53 ++----------- Sources/AWSCrypto/Insecure.swift | 7 +- Sources/AWSCrypto/MD5.swift | 23 +----- Sources/AWSCrypto/SHA1.swift | 61 --------------- Sources/AWSCrypto/SHA2.swift | 43 +---------- Sources/AWSCrypto/SymmetricKey.swift | 14 ++-- Sources/AWSCrypto/exports.swift | 6 ++ Sources/AWSSigner/credentials.swift | 2 +- Sources/AWSSigner/signer.swift | 92 +++++++++++++--------- Sources/CAWSCrypto/include/c_awscrypto.h | 25 ------ Sources/CAWSCrypto/shims.c | 56 -------------- Tests/AWSCryptoTests/AWSCryptoTests.swift | 93 ----------------------- Tests/LinuxMain.swift | 4 +- docker/Dockerfile | 4 +- 20 files changed, 120 insertions(+), 496 deletions(-) delete mode 100644 Sources/AWSCrypto/SHA1.swift create mode 100644 Sources/AWSCrypto/exports.swift delete mode 100644 Sources/CAWSCrypto/include/c_awscrypto.h delete mode 100644 Sources/CAWSCrypto/shims.c delete mode 100644 Tests/AWSCryptoTests/AWSCryptoTests.swift diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 93bb057..743ab0e 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -10,21 +10,16 @@ jobs: uses: actions/checkout@v1 with: fetch-depth: 1 - - name: Build - run: swift build - - name: Test + - name: Build and Test run: swift test linux: runs-on: ubuntu-latest strategy: matrix: - tag: ['5.0', '5.1'] + tag: ['5.1', '5.2'] container: image: swift:${{ matrix.tag }} - volumes: - - $GITHUB_WORKSPACE:/src - options: --workdir /src steps: - name: Checkout uses: actions/checkout@v1 @@ -33,9 +28,7 @@ jobs: - name: Install dependencies run: | apt-get update -qq - apt-get install -q -y tzdata libssl-dev zlib1g-dev - - name: Build - run: swift build - - name: Test + apt-get install -q -y tzdata zlib1g-dev + - name: Build and Test run: swift test diff --git a/Dockerfile b/Dockerfile index 3191544..8118168 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM swift:5.1 as builder +FROM swift:5.2 as builder RUN apt-get -qq update && apt-get install -y \ - libssl-dev zlib1g-dev \ + zlib1g-dev \ && rm -r /var/lib/apt/lists/* WORKDIR /aws-signer diff --git a/Package.swift b/Package.swift index f9b3c83..d7d9f7f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:5.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -15,24 +15,22 @@ let package = Package( ], targets: [ .target(name: "AWSSigner", dependencies: ["AWSCrypto", "NIO", "NIOHTTP1"]), - .target(name: "AWSCrypto", dependencies: ["CAWSCrypto"]), - .target(name: "CAWSCrypto", dependencies: []), - + .target(name: "AWSCrypto", dependencies: []), .target(name: "HTTPClientAWSSigner", dependencies: ["AWSSigner", "AsyncHTTPClient"]), - .testTarget(name: "AWSSignerTests", dependencies: ["AWSSigner", "HTTPClientAWSSigner"]), - .testTarget(name: "AWSCryptoTests", dependencies: ["AWSCrypto"]), + .testTarget(name: "AWSSignerTests", dependencies: ["AWSSigner", "HTTPClientAWSSigner"]) ] ) -// switch for whether to use CAWSSigner to shim between OpenSSL versions +// switch for whether to use swift crypto. Swift crypto requires macOS10.15 or iOS13.I'd rather not pass this requirement on #if os(Linux) -let useOpenSSLShim = true +let useSwiftCrypto = true #else -let useOpenSSLShim = false +let useSwiftCrypto = false #endif -// Decide on where we get our SSL support from. Linux usses NIOSSL to provide SSL. Linux also needs CAWSSDKOpenSSL to shim across different OpenSSL versions for the HMAC functions. -if useOpenSSLShim { - package.dependencies.append(.package(url: "https://github.com/apple/swift-nio-ssl-support.git", from: "1.0.0")) +// Use Swift cypto on Linux. +if useSwiftCrypto { + package.dependencies.append(.package(url: "https://github.com/apple/swift-crypto.git", from: "1.0.0")) + package.targets.first{$0.name == "AWSCrypto"}?.dependencies.append("Crypto") } diff --git a/Sources/AWSCrypto/ByteArray.swift b/Sources/AWSCrypto/ByteArray.swift index 17c43c1..0517999 100644 --- a/Sources/AWSCrypto/ByteArray.swift +++ b/Sources/AWSCrypto/ByteArray.swift @@ -1,10 +1,11 @@ -// ByteArray.swift -// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface +// Replicating the CryptoKit framework interface for < macOS 10.15 // written by AdamFowler 2020/01/30 +#if !os(Linux) + import protocol Foundation.ContiguousBytes /// Protocol for object encapsulating an array of bytes -protocol ByteArray: Sequence, ContiguousBytes, CustomStringConvertible, Hashable where Element == UInt8 { +protocol ByteArray: Sequence, ContiguousBytes, Hashable where Element == UInt8 { init(bytes: [UInt8]) var bytes: [UInt8] { get set } } @@ -21,10 +22,6 @@ extension ByteArray { public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { return try bytes.withUnsafeBytes(body) } - - public var description: String { - // hex digest - return bytes.map{String(format: "%02x", $0)}.joined() - } } +#endif diff --git a/Sources/AWSCrypto/Digest.swift b/Sources/AWSCrypto/Digest.swift index 3b79b31..7a272d3 100644 --- a/Sources/AWSCrypto/Digest.swift +++ b/Sources/AWSCrypto/Digest.swift @@ -1,13 +1,16 @@ -// Digest.swift -// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface +// Replicating the CryptoKit framework interface for < macOS 10.15 // written by AdamFowler 2020/01/30 + +#if !os(Linux) + import protocol Foundation.ContiguousBytes /// Protocol for Digest object returned from HashFunction -public protocol Digest: Sequence, ContiguousBytes, CustomStringConvertible, Hashable where Element == UInt8 { +public protocol Digest: Sequence, ContiguousBytes, Hashable where Element == UInt8 { static var byteCount: Int {get} } /// Protocol for Digest object consisting of a byte array protocol ByteDigest: Digest, ByteArray { } +#endif diff --git a/Sources/AWSCrypto/HMAC.swift b/Sources/AWSCrypto/HMAC.swift index 976598b..df24e09 100644 --- a/Sources/AWSCrypto/HMAC.swift +++ b/Sources/AWSCrypto/HMAC.swift @@ -1,6 +1,9 @@ -// HMAC.swift -// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface +// Replicating the CryptoKit framework interface for < macOS 10.15 // written by AdamFowler 2020/01/30 + +#if !os(Linux) + +import CommonCrypto import protocol Foundation.DataProtocol /// Hash Authentication Code returned by HMAC @@ -8,10 +11,6 @@ public struct HashAuthenticationCode: ByteArray { public var bytes: [UInt8] } -#if canImport(CommonCrypto) - -import CommonCrypto - /// Object generating HMAC for data block given a symmetric key public struct HMAC { @@ -66,63 +65,4 @@ extension HMAC { } } -#else - -import CAWSCrypto - -public struct HMAC { - - let key: SymmetricKey - var context: OpaquePointer - - /// return authentication code for data block given a symmetric key - public static func authenticationCode(for data: D, using key: SymmetricKey) -> HashAuthenticationCode { - var hmac = HMAC(key: key) - hmac.update(data: data) - return hmac.finalize() - } - - /// update HMAC calculation with a block of data - public mutating func update(data: D) { - if let digest = data.withContiguousStorageIfAvailable({ bytes in - return self.update(bufferPointer: bytes) - }) { - return digest - } else { - var buffer = UnsafeMutableBufferPointer.allocate(capacity: data.count) - data.copyBytes(to: buffer) - defer { buffer.deallocate() } - self.update(bufferPointer: .init(buffer)) - } - } -} - -extension HMAC { - /// initialize HMAC with symmetric key - public init(key: SymmetricKey) { - self.key = key - self.context = AWSCRYPTO_HMAC_CTX_new() - self.initialize() - } - - /// initialize HMAC calculation - mutating func initialize() { - HMAC_Init_ex(context, key.bytes, Int32(key.bytes.count), H.algorithm, nil) - } - - /// update HMAC calculation with a buffer - mutating func update(bufferPointer: UnsafeBufferPointer) { - HMAC_Update(context, bufferPointer.baseAddress, bufferPointer.count) - } - - /// finalize HMAC calculation and return authentication code - public mutating func finalize() -> HashAuthenticationCode { - var authenticationCode: [UInt8] = .init(repeating: 0, count: H.Digest.byteCount) - var length: UInt32 = 0 - HMAC_Final(context, &authenticationCode, &length) - AWSCRYPTO_HMAC_CTX_free(context) - return .init(bytes: authenticationCode) - } -} - #endif diff --git a/Sources/AWSCrypto/HashFunction.swift b/Sources/AWSCrypto/HashFunction.swift index 113be60..9ead639 100644 --- a/Sources/AWSCrypto/HashFunction.swift +++ b/Sources/AWSCrypto/HashFunction.swift @@ -1,7 +1,9 @@ -// HashFunction.swift -// HashFunction protocol and OpenSSL, CommonCrypto specialisations -// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface +// Replicating the CryptoKit framework interface for < macOS 10.15 // written by AdamFowler 2020/01/30 + +#if !os(Linux) + +import CommonCrypto import protocol Foundation.DataProtocol /// Protocol for Hashing function @@ -59,55 +61,10 @@ extension HashFunction { } } -#if canImport(CommonCrypto) - -import CommonCrypto - /// public protocol for Common Crypto hash functions public protocol CCHashFunction: HashFunction { static var algorithm: CCHmacAlgorithm { get } } -#else - -import CAWSCrypto - -/// public protocol for OpenSSL hash function -public protocol OpenSSLHashFunction: HashFunction { - static var algorithm: OpaquePointer { get } -} - -/// internal protocol for OpenSSL hash functions that hides implementation details -protocol _OpenSSLHashFunction: OpenSSLHashFunction where Digest: ByteDigest { - var context: OpaquePointer { get set } - init(context: OpaquePointer) -} - -extension _OpenSSLHashFunction { - /// initialization - public init() { - self.init(context: AWSCRYPTO_EVP_MD_CTX_new()) - initialize() - } - - /// initialize hash function - mutating func initialize() { - EVP_DigestInit_ex(context, Self.algorithm, nil) - } - - /// update hash function with data - public mutating func update(bufferPointer: UnsafeRawBufferPointer) { - EVP_DigestUpdate(context, bufferPointer.baseAddress, bufferPointer.count) - } - - /// finalize hash function and return digest - public mutating func finalize() -> Self.Digest { - var digest: [UInt8] = .init(repeating: 0, count: Digest.byteCount) - var count: UInt32 = 0 - EVP_DigestFinal_ex(context, &digest, &count) - AWSCRYPTO_EVP_MD_CTX_free(context) - return .init(bytes: .init(digest)) - } -} #endif diff --git a/Sources/AWSCrypto/Insecure.swift b/Sources/AWSCrypto/Insecure.swift index 0760b47..4529db7 100644 --- a/Sources/AWSCrypto/Insecure.swift +++ b/Sources/AWSCrypto/Insecure.swift @@ -1,5 +1,8 @@ -// Insecure.swift -// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface +// Replicating the CryptoKit framework interface for < macOS 10.15 // written by AdamFowler 2020/01/30 +#if !os(Linux) + public enum Insecure {} + +#endif diff --git a/Sources/AWSCrypto/MD5.swift b/Sources/AWSCrypto/MD5.swift index e826431..d08f259 100644 --- a/Sources/AWSCrypto/MD5.swift +++ b/Sources/AWSCrypto/MD5.swift @@ -1,7 +1,7 @@ -// MD5.swift -// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface +// Replicating the CryptoKit framework interface for < macOS 10.15 // written by AdamFowler 2020/01/30 -#if canImport(CommonCrypto) + +#if !os(Linux) import CommonCrypto @@ -40,21 +40,4 @@ public extension Insecure { } } -#else - -import CAWSCrypto - -public extension Insecure { - struct MD5Digest : ByteDigest { - public static var byteCount: Int { return Int(MD5_DIGEST_LENGTH) } - public var bytes: [UInt8] - } - - struct MD5: _OpenSSLHashFunction { - public typealias Digest = MD5Digest - public static var algorithm: OpaquePointer { return EVP_md5() } - var context: OpaquePointer - } -} - #endif diff --git a/Sources/AWSCrypto/SHA1.swift b/Sources/AWSCrypto/SHA1.swift deleted file mode 100644 index 54c0b1f..0000000 --- a/Sources/AWSCrypto/SHA1.swift +++ /dev/null @@ -1,61 +0,0 @@ -// SHA1.swift -// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface -// written by AdamFowler 2020/01/30 -#if canImport(CommonCrypto) - -import CommonCrypto - -public extension Insecure { - - struct SHA1Digest : ByteDigest { - public static var byteCount: Int { return Int(CC_SHA1_DIGEST_LENGTH) } - public var bytes: [UInt8] - } - - struct SHA1: CCHashFunction { - public typealias Digest = SHA1Digest - public static var algorithm: CCHmacAlgorithm { return CCHmacAlgorithm(kCCHmacAlgSHA1) } - var context: CC_SHA1_CTX - - public static func hash(bufferPointer: UnsafeRawBufferPointer) -> Self.Digest { - var digest: [UInt8] = .init(repeating: 0, count: Digest.byteCount) - CC_SHA1(bufferPointer.baseAddress, CC_LONG(bufferPointer.count), &digest) - return .init(bytes: digest) - } - - public init() { - context = CC_SHA1_CTX() - CC_SHA1_Init(&context) - } - - public mutating func update(bufferPointer: UnsafeRawBufferPointer) { - CC_SHA1_Update(&context, bufferPointer.baseAddress, CC_LONG(bufferPointer.count)) - } - - public mutating func finalize() -> Self.Digest { - var digest: [UInt8] = .init(repeating: 0, count: Digest.byteCount) - CC_SHA1_Final(&digest, &context) - return .init(bytes: digest) - } - } -} - -#else - -import CAWSCrypto - -public extension Insecure { - struct SHA1Digest : ByteDigest { - public static var byteCount: Int { return Int(SHA_DIGEST_LENGTH) } - public var bytes: [UInt8] - } - - struct SHA1: _OpenSSLHashFunction { - public typealias Digest = SHA1Digest - public static var algorithm: OpaquePointer { return EVP_sha1() } - var context: OpaquePointer - } -} - -#endif - diff --git a/Sources/AWSCrypto/SHA2.swift b/Sources/AWSCrypto/SHA2.swift index 3dfff3a..77d2a11 100644 --- a/Sources/AWSCrypto/SHA2.swift +++ b/Sources/AWSCrypto/SHA2.swift @@ -1,7 +1,7 @@ -// SHA2.swift -// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface +// Replicating the CryptoKit framework interface for < macOS 10.15 // written by AdamFowler 2020/01/30 -#if canImport(CommonCrypto) + +#if !os(Linux) import CommonCrypto @@ -101,41 +101,4 @@ public struct SHA512: CCHashFunction { } } -#else - -import CAWSCrypto - -public struct SHA256Digest : ByteDigest { - public static var byteCount: Int { return Int(SHA256_DIGEST_LENGTH) } - public var bytes: [UInt8] -} - -public struct SHA256: _OpenSSLHashFunction { - public typealias Digest = SHA256Digest - public static var algorithm: OpaquePointer { return EVP_sha256() } - var context: OpaquePointer -} - -public struct SHA384Digest : ByteDigest { - public static var byteCount: Int { return Int(SHA384_DIGEST_LENGTH) } - public var bytes: [UInt8] -} - -public struct SHA384: _OpenSSLHashFunction { - public typealias Digest = SHA384Digest - public static var algorithm: OpaquePointer { return EVP_sha384() } - var context: OpaquePointer -} - -public struct SHA512Digest : ByteDigest { - public static var byteCount: Int { return Int(SHA512_DIGEST_LENGTH) } - public var bytes: [UInt8] -} - -public struct SHA512: _OpenSSLHashFunction { - public typealias Digest = SHA512Digest - public static var algorithm: OpaquePointer { return EVP_sha512() } - var context: OpaquePointer -} - #endif diff --git a/Sources/AWSCrypto/SymmetricKey.swift b/Sources/AWSCrypto/SymmetricKey.swift index 74c51bd..f75efc0 100644 --- a/Sources/AWSCrypto/SymmetricKey.swift +++ b/Sources/AWSCrypto/SymmetricKey.swift @@ -1,6 +1,8 @@ -// SymmetricKey.swift -// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface +// Replicating the CryptoKit framework interface for < macOS 10.15 // written by AdamFowler 2020/01/30 + +#if !os(Linux) + import protocol Foundation.ContiguousBytes /// Symmetric key object @@ -15,14 +17,12 @@ public struct SymmetricKey: ContiguousBytes { let bytes = data.withUnsafeBytes { buffer in return [UInt8](buffer) } - self.init(bytes: bytes) - } - - public init(bytes: [UInt8]) { self.bytes = bytes } - + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { return try self.bytes.withUnsafeBytes(body) } } + +#endif diff --git a/Sources/AWSCrypto/exports.swift b/Sources/AWSCrypto/exports.swift new file mode 100644 index 0000000..0fffb0f --- /dev/null +++ b/Sources/AWSCrypto/exports.swift @@ -0,0 +1,6 @@ +// Replicating the CryptoKit framework interface for < macOS 10.15 +// written by AdamFowler 2020/01/30 + +#if os(Linux) +@_exported import Crypto +#endif diff --git a/Sources/AWSSigner/credentials.swift b/Sources/AWSSigner/credentials.swift index 3cbff07..14ccf04 100644 --- a/Sources/AWSSigner/credentials.swift +++ b/Sources/AWSSigner/credentials.swift @@ -4,7 +4,7 @@ // // Created by Adam Fowler on 29/08/2019. // -import Foundation +import class Foundation.ProcessInfo /// Protocol for providing credential details for accessing AWS services public protocol Credential { diff --git a/Sources/AWSSigner/signer.swift b/Sources/AWSSigner/signer.swift index e63d64b..f94cc1d 100644 --- a/Sources/AWSSigner/signer.swift +++ b/Sources/AWSSigner/signer.swift @@ -7,7 +7,13 @@ // AWS documentation about signing requests is here https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html // -import Foundation +import struct Foundation.CharacterSet +import struct Foundation.Data +import struct Foundation.Date +import class Foundation.DateFormatter +import struct Foundation.Locale +import struct Foundation.TimeZone +import struct Foundation.URL import AWSCrypto import NIO import NIOHTTP1 @@ -20,23 +26,25 @@ public struct AWSSigner { public let name: String /// AWS region you are working in public let region: String - - static let hashedEmptyBody = SHA256.hash(data: [UInt8]()).description - + + static let hashedEmptyBody = SHA256.hash(data: [UInt8]()).hexDigest() + + static private let timeStampDateFormatter: DateFormatter = createTimeStampDateFormatter() + /// Initialise the Signer class with AWS credentials public init(credentials: Credential, name: String, region: String) { self.credentials = credentials self.name = name self.region = region } - + /// Enum for holding your body data public enum BodyData { case string(String) case data(Data) case byteBuffer(ByteBuffer) } - + /// Generate signed headers, for a HTTP request public func signHeaders(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: BodyData? = nil, date: Date = Date()) -> HTTPHeaders { let bodyHash = AWSSigner.hashedPayload(body) @@ -49,28 +57,27 @@ public struct AWSSigner { if let sessionToken = credentials.sessionToken { headers.add(name: "x-amz-security-token", value: sessionToken) } - + // construct signing data. Do this after adding the headers as it uses data from the headers let signingData = AWSSigner.SigningData(url: url, method: method, headers: headers, body: body, bodyHash: bodyHash, date: dateString, signer: self) - + // construct authorization string let authorization = "AWS4-HMAC-SHA256 " + "Credential=\(credentials.accessKeyId)/\(signingData.date)/\(region)/\(name)/aws4_request, " + "SignedHeaders=\(signingData.signedHeaders), " + "Signature=\(signature(signingData: signingData))" - + // add Authorization header headers.add(name: "Authorization", value: authorization) - + return headers } - + /// Generate a signed URL, for a HTTP request public func signURL(url: URL, method: HTTPMethod = .GET, body: BodyData? = nil, date: Date = Date(), expires: Int = 86400) -> URL { let headers = HTTPHeaders([("host", url.host ?? "")]) // Create signing data - let signingData = AWSSigner.SigningData(url: url, method: method, headers: headers, body: body, date: AWSSigner.timestamp(date), signer: self) - + var signingData = AWSSigner.SigningData(url: url, method: method, headers: headers, body: body, date: AWSSigner.timestamp(date), signer: self) // Construct query string. Start with original query strings and append all the signing info. var query = url.query ?? "" if query.count > 0 { @@ -89,19 +96,19 @@ public struct AWSSigner { .sorted() .joined(separator: "&") .queryEncode() - + // update unsignedURL in the signingData so when the canonical request is constructed it includes all the signing query items signingData.unsignedURL = URL(string: url.absoluteString.split(separator: "?")[0]+"?"+query)! // NEED TO DEAL WITH SITUATION WHERE THIS FAILS query += "&X-Amz-Signature=\(signature(signingData: signingData))" - + // Add signature to query items and build a new Request let signedURL = URL(string: url.absoluteString.split(separator: "?")[0]+"?"+query)! return signedURL } - + /// structure used to store data used throughout the signing process - class SigningData { + struct SigningData { let url : URL let method : HTTPMethod let hashedPayload : String @@ -109,9 +116,9 @@ public struct AWSSigner { let headersToSign: [String: String] let signedHeaders : String var unsignedURL : URL - + var date : String { return String(datetime.prefix(8))} - + init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: BodyData? = nil, bodyHash: String? = nil, date: String, signer: AWSSigner) { self.url = url self.method = method @@ -125,7 +132,7 @@ public struct AWSSigner { } else { self.hashedPayload = AWSSigner.hashedPayload(body) } - + let headersNotToSign: Set = [ "Authorization" ] @@ -142,7 +149,7 @@ public struct AWSSigner { self.signedHeaders = signedHeadersArray.sorted().joined(separator: ";") } } - + // Stage 3 Calculating signature as in https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html func signature(signingData: SigningData) -> String { let kDate = HMAC.authenticationCode(for: Data(signingData.date.utf8), using: SymmetricKey(data: Array("AWS4\(credentials.secretAccessKey)".utf8))) @@ -150,18 +157,18 @@ public struct AWSSigner { let kService = HMAC.authenticationCode(for: Data(name.utf8), using: SymmetricKey(data: kRegion)) let kSigning = HMAC.authenticationCode(for: Data("aws4_request".utf8), using: SymmetricKey(data: kService)) let kSignature = HMAC.authenticationCode(for: stringToSign(signingData: signingData), using: SymmetricKey(data: kSigning)) - return kSignature.description + return kSignature.hexDigest() } - + /// Stage 2 Create the string to sign as in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html func stringToSign(signingData: SigningData) -> Data { let stringToSign = "AWS4-HMAC-SHA256\n" + "\(signingData.datetime)\n" + "\(signingData.date)/\(region)/\(name)/aws4_request\n" + - SHA256.hash(data: canonicalRequest(signingData: signingData)).description + SHA256.hash(data: canonicalRequest(signingData: signingData)).hexDigest() return Data(stringToSign.utf8) } - + /// Stage 1 Create the canonical request as in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html func canonicalRequest(signingData: SigningData) -> Data { let canonicalHeaders = signingData.headersToSign.map { return "\($0.key.lowercased()):\($0.value.trimmingCharacters(in: CharacterSet.whitespaces))" } @@ -175,20 +182,20 @@ public struct AWSSigner { signingData.hashedPayload return Data(canonicalRequest.utf8) } - + /// Create a SHA256 hash of the Requests body static func hashedPayload(_ payload: BodyData?) -> String { guard let payload = payload else { return hashedEmptyBody } let hash : String? switch payload { case .string(let string): - hash = SHA256.hash(data: Data(string.utf8)).description + hash = SHA256.hash(data: Data(string.utf8)).hexDigest() case .data(let data): - hash = SHA256.hash(data: data).description + hash = SHA256.hash(data: data).hexDigest() case .byteBuffer(let byteBuffer): let byteBufferView = byteBuffer.readableBytesView hash = byteBufferView.withContiguousStorageIfAvailable { bytes in - return SHA256.hash(data: bytes).description + return SHA256.hash(data: bytes).hexDigest() } } if let hash = hash { @@ -197,19 +204,23 @@ public struct AWSSigner { return hashedEmptyBody } } - + /// return a hexEncoded string buffer from an array of bytes static func hexEncoded(_ buffer: [UInt8]) -> String { return buffer.map{String(format: "%02x", $0)}.joined(separator: "") } - - /// return a timestamp formatted for signing requests - static func timestamp(_ date: Date) -> String { + /// create timestamp dateformatter + static private func createTimeStampDateFormatter() -> DateFormatter { let formatter = DateFormatter() formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" formatter.timeZone = TimeZone(abbreviation: "UTC") formatter.locale = Locale(identifier: "en_US_POSIX") - return formatter.string(from: date) + return formatter + } + + /// return a timestamp formatted for signing requests + static func timestamp(_ date: Date) -> String { + return timeStampDateFormatter.string(from: date) } } @@ -217,16 +228,23 @@ extension String { func queryEncode() -> String { return addingPercentEncoding(withAllowedCharacters: String.queryAllowedCharacters) ?? self } - + func uriEncode() -> String { return addingPercentEncoding(withAllowedCharacters: String.uriAllowedCharacters) ?? self } - + func uriEncodeWithSlash() -> String { return addingPercentEncoding(withAllowedCharacters: String.uriAllowedWithSlashCharacters) ?? self } - + static let uriAllowedWithSlashCharacters = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~/") static let uriAllowedCharacters = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~") static let queryAllowedCharacters = CharacterSet(charactersIn:"/;+").inverted } + +public extension Sequence where Element == UInt8 { + /// return a hexEncoded string buffer from an array of bytes + func hexDigest() -> String { + return self.map{String(format: "%02x", $0)}.joined(separator: "") + } +} diff --git a/Sources/CAWSCrypto/include/c_awscrypto.h b/Sources/CAWSCrypto/include/c_awscrypto.h deleted file mode 100644 index bb9dcc1..0000000 --- a/Sources/CAWSCrypto/include/c_awscrypto.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// c_awscrypto.h -// AWSSDKSwiftCore -// -// Created by Adam Fowler on 2019/08/08. -// - -#ifndef C_AWSSDK_OPENSSL_H -#define C_AWSSDK_OPENSSL_H - -#ifdef __linux__ - -#include -#include -#include - -HMAC_CTX *AWSCRYPTO_HMAC_CTX_new(); -void AWSCRYPTO_HMAC_CTX_free(HMAC_CTX* ctx); - -EVP_MD_CTX *AWSCRYPTO_EVP_MD_CTX_new(void); -void AWSCRYPTO_EVP_MD_CTX_free(EVP_MD_CTX *ctx); - -#endif // __linux__ - -#endif // C_AWSSDK_OPENSSL_H diff --git a/Sources/CAWSCrypto/shims.c b/Sources/CAWSCrypto/shims.c deleted file mode 100644 index b27b296..0000000 --- a/Sources/CAWSCrypto/shims.c +++ /dev/null @@ -1,56 +0,0 @@ -// -// shims.c -// AWSSDKSwiftCore -// -// Created by Adam Fowler on 2019/08/08. -// - -#ifdef __linux__ - -// These are functions that shim over differences in different OpenSSL versions, -// which are best handled by using the C preprocessor. -#include "c_awscrypto.h" -#include - -HMAC_CTX *AWSCRYPTO_HMAC_CTX_new() { -#if OPENSSL_VERSION_NUMBER < 0x10100000L || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) - HMAC_CTX *ctx = OPENSSL_malloc(sizeof(HMAC_CTX)); - if (ctx != NULL) { - HMAC_CTX_init(ctx); - } - return ctx; -#else - return HMAC_CTX_new(); -#endif -} - -void AWSCRYPTO_HMAC_CTX_free(HMAC_CTX* ctx) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) - if (ctx != NULL) { - HMAC_CTX_cleanup(ctx); - OPENSSL_free(ctx); - } -#else - HMAC_CTX_free(ctx); -#endif -} - -EVP_MD_CTX *AWSCRYPTO_EVP_MD_CTX_new(void) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) - return EVP_MD_CTX_create(); -#else - return EVP_MD_CTX_new(); -#endif -} - -void AWSCRYPTO_EVP_MD_CTX_free(EVP_MD_CTX *ctx) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) - if (ctx != NULL) { - EVP_MD_CTX_destroy(ctx); - } -#else - EVP_MD_CTX_free(ctx); -#endif -} - -#endif // __linux__ diff --git a/Tests/AWSCryptoTests/AWSCryptoTests.swift b/Tests/AWSCryptoTests/AWSCryptoTests.swift deleted file mode 100644 index 58957be..0000000 --- a/Tests/AWSCryptoTests/AWSCryptoTests.swift +++ /dev/null @@ -1,93 +0,0 @@ -import XCTest -@testable import AWSCrypto - -final class AWSCryptoTests: XCTestCase { - - // create a buffer of random values. Will always create the same given you supply the same z and w values - // Random number generator from https://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation - func createRandomBuffer(_ w: UInt, _ z: UInt, size: Int) -> [UInt8] { - var z = z - var w = w - func getUInt8() -> UInt8 - { - z = 36969 * (z & 65535) + (z >> 16); - w = 18000 * (w & 65535) + (w >> 16); - return UInt8(((z << 16) + w) & 0xff); - } - var data = Array(repeating: 0, count: size) - for i in 0...authenticationCode(for: data, using: SymmetricKey(bytes: key)) - XCTAssertEqual(authenticationKey.description, "ddec250211f1b546254bab3fb027af1acc4842898e8af6eeadcdbf8e2c6c1ff5") - } - - func testHMACInitUpdateFinal() { - let data = createRandomBuffer(21, 81, size: 762061) - let key = createRandomBuffer(102, 3, size: 32) - let authenticationKey = HMAC.authenticationCode(for: data, using: SymmetricKey(bytes: key)) - - var hmac = HMAC(key: SymmetricKey(bytes: key)) - hmac.update(data: data[0..<126749]) - hmac.update(data: data[126749..<762061]) - let authenticationKey2 = hmac.finalize() - - XCTAssertEqual(authenticationKey, authenticationKey2) - XCTAssertEqual(authenticationKey.description, authenticationKey2.description) - } - - static var allTests = [ - ("testMD5", testMD5), - ("testSHA256", testSHA256), - ("testHMAC", testHMAC), - ] -} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 76007e8..6dfc6f3 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,9 +1,7 @@ import XCTest @testable import AWSSignerTests -@testable import AWSCryptoTests XCTMain([ - testCase(AWSSignerTests.allTests), - testCase(AWSCryptoTests.allTests) + testCase(AWSSignerTests.allTests) ]) diff --git a/docker/Dockerfile b/docker/Dockerfile index c6d5947..e7a2912 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ -FROM swift:5.1.3 as builder +FROM swift:5.2 as builder RUN apt-get -qq update && apt-get install -y \ - libssl-dev zlib1g-dev \ + zlib1g-dev \ && rm -r /var/lib/apt/lists/*