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
This commit is contained in:
Adam Fowler 2020-08-03 11:28:49 +01:00 committed by GitHub
parent f28d6f6503
commit b8af0b2a43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 120 additions and 496 deletions

View File

@ -10,21 +10,16 @@ jobs:
uses: actions/checkout@v1 uses: actions/checkout@v1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Build - name: Build and Test
run: swift build
- name: Test
run: swift test run: swift test
linux: linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
tag: ['5.0', '5.1'] tag: ['5.1', '5.2']
container: container:
image: swift:${{ matrix.tag }} image: swift:${{ matrix.tag }}
volumes:
- $GITHUB_WORKSPACE:/src
options: --workdir /src
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v1 uses: actions/checkout@v1
@ -33,9 +28,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
apt-get update -qq apt-get update -qq
apt-get install -q -y tzdata libssl-dev zlib1g-dev apt-get install -q -y tzdata zlib1g-dev
- name: Build - name: Build and Test
run: swift build
- name: Test
run: swift test run: swift test

View File

@ -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 \ RUN apt-get -qq update && apt-get install -y \
libssl-dev zlib1g-dev \ zlib1g-dev \
&& rm -r /var/lib/apt/lists/* && rm -r /var/lib/apt/lists/*
WORKDIR /aws-signer WORKDIR /aws-signer

View File

@ -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. // The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription import PackageDescription
@ -15,24 +15,22 @@ let package = Package(
], ],
targets: [ targets: [
.target(name: "AWSSigner", dependencies: ["AWSCrypto", "NIO", "NIOHTTP1"]), .target(name: "AWSSigner", dependencies: ["AWSCrypto", "NIO", "NIOHTTP1"]),
.target(name: "AWSCrypto", dependencies: ["CAWSCrypto"]), .target(name: "AWSCrypto", dependencies: []),
.target(name: "CAWSCrypto", dependencies: []),
.target(name: "HTTPClientAWSSigner", dependencies: ["AWSSigner", "AsyncHTTPClient"]), .target(name: "HTTPClientAWSSigner", dependencies: ["AWSSigner", "AsyncHTTPClient"]),
.testTarget(name: "AWSSignerTests", dependencies: ["AWSSigner", "HTTPClientAWSSigner"]), .testTarget(name: "AWSSignerTests", dependencies: ["AWSSigner", "HTTPClientAWSSigner"])
.testTarget(name: "AWSCryptoTests", dependencies: ["AWSCrypto"]),
] ]
) )
// 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) #if os(Linux)
let useOpenSSLShim = true let useSwiftCrypto = true
#else #else
let useOpenSSLShim = false let useSwiftCrypto = false
#endif #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. // Use Swift cypto on Linux.
if useOpenSSLShim { if useSwiftCrypto {
package.dependencies.append(.package(url: "https://github.com/apple/swift-nio-ssl-support.git", from: "1.0.0")) 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")
} }

View File

@ -1,10 +1,11 @@
// ByteArray.swift // Replicating the CryptoKit framework interface for < macOS 10.15
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
// written by AdamFowler 2020/01/30 // written by AdamFowler 2020/01/30
#if !os(Linux)
import protocol Foundation.ContiguousBytes import protocol Foundation.ContiguousBytes
/// Protocol for object encapsulating an array of bytes /// 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]) init(bytes: [UInt8])
var bytes: [UInt8] { get set } var bytes: [UInt8] { get set }
} }
@ -21,10 +22,6 @@ extension ByteArray {
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
return try bytes.withUnsafeBytes(body) return try bytes.withUnsafeBytes(body)
} }
public var description: String {
// hex digest
return bytes.map{String(format: "%02x", $0)}.joined()
}
} }
#endif

View File

@ -1,13 +1,16 @@
// Digest.swift // Replicating the CryptoKit framework interface for < macOS 10.15
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
// written by AdamFowler 2020/01/30 // written by AdamFowler 2020/01/30
#if !os(Linux)
import protocol Foundation.ContiguousBytes import protocol Foundation.ContiguousBytes
/// Protocol for Digest object returned from HashFunction /// 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} static var byteCount: Int {get}
} }
/// Protocol for Digest object consisting of a byte array /// Protocol for Digest object consisting of a byte array
protocol ByteDigest: Digest, ByteArray { } protocol ByteDigest: Digest, ByteArray { }
#endif

View File

@ -1,6 +1,9 @@
// HMAC.swift // Replicating the CryptoKit framework interface for < macOS 10.15
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
// written by AdamFowler 2020/01/30 // written by AdamFowler 2020/01/30
#if !os(Linux)
import CommonCrypto
import protocol Foundation.DataProtocol import protocol Foundation.DataProtocol
/// Hash Authentication Code returned by HMAC /// Hash Authentication Code returned by HMAC
@ -8,10 +11,6 @@ public struct HashAuthenticationCode: ByteArray {
public var bytes: [UInt8] public var bytes: [UInt8]
} }
#if canImport(CommonCrypto)
import CommonCrypto
/// Object generating HMAC for data block given a symmetric key /// Object generating HMAC for data block given a symmetric key
public struct HMAC<H: CCHashFunction> { public struct HMAC<H: CCHashFunction> {
@ -66,63 +65,4 @@ extension HMAC {
} }
} }
#else
import CAWSCrypto
public struct HMAC<H: OpenSSLHashFunction> {
let key: SymmetricKey
var context: OpaquePointer
/// return authentication code for data block given a symmetric key
public static func authenticationCode<D : DataProtocol>(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<D: DataProtocol>(data: D) {
if let digest = data.withContiguousStorageIfAvailable({ bytes in
return self.update(bufferPointer: bytes)
}) {
return digest
} else {
var buffer = UnsafeMutableBufferPointer<UInt8>.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<UInt8>) {
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 #endif

View File

@ -1,7 +1,9 @@
// HashFunction.swift // Replicating the CryptoKit framework interface for < macOS 10.15
// HashFunction protocol and OpenSSL, CommonCrypto specialisations
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
// written by AdamFowler 2020/01/30 // written by AdamFowler 2020/01/30
#if !os(Linux)
import CommonCrypto
import protocol Foundation.DataProtocol import protocol Foundation.DataProtocol
/// Protocol for Hashing function /// Protocol for Hashing function
@ -59,55 +61,10 @@ extension HashFunction {
} }
} }
#if canImport(CommonCrypto)
import CommonCrypto
/// public protocol for Common Crypto hash functions /// public protocol for Common Crypto hash functions
public protocol CCHashFunction: HashFunction { public protocol CCHashFunction: HashFunction {
static var algorithm: CCHmacAlgorithm { get } 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 #endif

View File

@ -1,5 +1,8 @@
// Insecure.swift // Replicating the CryptoKit framework interface for < macOS 10.15
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
// written by AdamFowler 2020/01/30 // written by AdamFowler 2020/01/30
#if !os(Linux)
public enum Insecure {} public enum Insecure {}
#endif

View File

@ -1,7 +1,7 @@
// MD5.swift // Replicating the CryptoKit framework interface for < macOS 10.15
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
// written by AdamFowler 2020/01/30 // written by AdamFowler 2020/01/30
#if canImport(CommonCrypto)
#if !os(Linux)
import CommonCrypto 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 #endif

View File

@ -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

View File

@ -1,7 +1,7 @@
// SHA2.swift // Replicating the CryptoKit framework interface for < macOS 10.15
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
// written by AdamFowler 2020/01/30 // written by AdamFowler 2020/01/30
#if canImport(CommonCrypto)
#if !os(Linux)
import CommonCrypto 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 #endif

View File

@ -1,6 +1,8 @@
// SymmetricKey.swift // Replicating the CryptoKit framework interface for < macOS 10.15
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
// written by AdamFowler 2020/01/30 // written by AdamFowler 2020/01/30
#if !os(Linux)
import protocol Foundation.ContiguousBytes import protocol Foundation.ContiguousBytes
/// Symmetric key object /// Symmetric key object
@ -15,14 +17,12 @@ public struct SymmetricKey: ContiguousBytes {
let bytes = data.withUnsafeBytes { buffer in let bytes = data.withUnsafeBytes { buffer in
return [UInt8](buffer) return [UInt8](buffer)
} }
self.init(bytes: bytes)
}
public init(bytes: [UInt8]) {
self.bytes = bytes self.bytes = bytes
} }
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
return try self.bytes.withUnsafeBytes(body) return try self.bytes.withUnsafeBytes(body)
} }
} }
#endif

View File

@ -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

View File

@ -4,7 +4,7 @@
// //
// Created by Adam Fowler on 29/08/2019. // Created by Adam Fowler on 29/08/2019.
// //
import Foundation import class Foundation.ProcessInfo
/// Protocol for providing credential details for accessing AWS services /// Protocol for providing credential details for accessing AWS services
public protocol Credential { public protocol Credential {

View File

@ -7,7 +7,13 @@
// AWS documentation about signing requests is here https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html // 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 AWSCrypto
import NIO import NIO
import NIOHTTP1 import NIOHTTP1
@ -20,23 +26,25 @@ public struct AWSSigner {
public let name: String public let name: String
/// AWS region you are working in /// AWS region you are working in
public let region: String 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 /// Initialise the Signer class with AWS credentials
public init(credentials: Credential, name: String, region: String) { public init(credentials: Credential, name: String, region: String) {
self.credentials = credentials self.credentials = credentials
self.name = name self.name = name
self.region = region self.region = region
} }
/// Enum for holding your body data /// Enum for holding your body data
public enum BodyData { public enum BodyData {
case string(String) case string(String)
case data(Data) case data(Data)
case byteBuffer(ByteBuffer) case byteBuffer(ByteBuffer)
} }
/// Generate signed headers, for a HTTP request /// 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 { public func signHeaders(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: BodyData? = nil, date: Date = Date()) -> HTTPHeaders {
let bodyHash = AWSSigner.hashedPayload(body) let bodyHash = AWSSigner.hashedPayload(body)
@ -49,28 +57,27 @@ public struct AWSSigner {
if let sessionToken = credentials.sessionToken { if let sessionToken = credentials.sessionToken {
headers.add(name: "x-amz-security-token", value: 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 // 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) let signingData = AWSSigner.SigningData(url: url, method: method, headers: headers, body: body, bodyHash: bodyHash, date: dateString, signer: self)
// construct authorization string // construct authorization string
let authorization = "AWS4-HMAC-SHA256 " + let authorization = "AWS4-HMAC-SHA256 " +
"Credential=\(credentials.accessKeyId)/\(signingData.date)/\(region)/\(name)/aws4_request, " + "Credential=\(credentials.accessKeyId)/\(signingData.date)/\(region)/\(name)/aws4_request, " +
"SignedHeaders=\(signingData.signedHeaders), " + "SignedHeaders=\(signingData.signedHeaders), " +
"Signature=\(signature(signingData: signingData))" "Signature=\(signature(signingData: signingData))"
// add Authorization header // add Authorization header
headers.add(name: "Authorization", value: authorization) headers.add(name: "Authorization", value: authorization)
return headers return headers
} }
/// Generate a signed URL, for a HTTP request /// 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 { public func signURL(url: URL, method: HTTPMethod = .GET, body: BodyData? = nil, date: Date = Date(), expires: Int = 86400) -> URL {
let headers = HTTPHeaders([("host", url.host ?? "")]) let headers = HTTPHeaders([("host", url.host ?? "")])
// Create signing data // 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. // Construct query string. Start with original query strings and append all the signing info.
var query = url.query ?? "" var query = url.query ?? ""
if query.count > 0 { if query.count > 0 {
@ -89,19 +96,19 @@ public struct AWSSigner {
.sorted() .sorted()
.joined(separator: "&") .joined(separator: "&")
.queryEncode() .queryEncode()
// update unsignedURL in the signingData so when the canonical request is constructed it includes all the signing query items // 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 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))" query += "&X-Amz-Signature=\(signature(signingData: signingData))"
// Add signature to query items and build a new Request // Add signature to query items and build a new Request
let signedURL = URL(string: url.absoluteString.split(separator: "?")[0]+"?"+query)! let signedURL = URL(string: url.absoluteString.split(separator: "?")[0]+"?"+query)!
return signedURL return signedURL
} }
/// structure used to store data used throughout the signing process /// structure used to store data used throughout the signing process
class SigningData { struct SigningData {
let url : URL let url : URL
let method : HTTPMethod let method : HTTPMethod
let hashedPayload : String let hashedPayload : String
@ -109,9 +116,9 @@ public struct AWSSigner {
let headersToSign: [String: String] let headersToSign: [String: String]
let signedHeaders : String let signedHeaders : String
var unsignedURL : URL var unsignedURL : URL
var date : String { return String(datetime.prefix(8))} 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) { init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: BodyData? = nil, bodyHash: String? = nil, date: String, signer: AWSSigner) {
self.url = url self.url = url
self.method = method self.method = method
@ -125,7 +132,7 @@ public struct AWSSigner {
} else { } else {
self.hashedPayload = AWSSigner.hashedPayload(body) self.hashedPayload = AWSSigner.hashedPayload(body)
} }
let headersNotToSign: Set<String> = [ let headersNotToSign: Set<String> = [
"Authorization" "Authorization"
] ]
@ -142,7 +149,7 @@ public struct AWSSigner {
self.signedHeaders = signedHeadersArray.sorted().joined(separator: ";") self.signedHeaders = signedHeadersArray.sorted().joined(separator: ";")
} }
} }
// Stage 3 Calculating signature as in https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html // Stage 3 Calculating signature as in https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
func signature(signingData: SigningData) -> String { func signature(signingData: SigningData) -> String {
let kDate = HMAC<SHA256>.authenticationCode(for: Data(signingData.date.utf8), using: SymmetricKey(data: Array("AWS4\(credentials.secretAccessKey)".utf8))) let kDate = HMAC<SHA256>.authenticationCode(for: Data(signingData.date.utf8), using: SymmetricKey(data: Array("AWS4\(credentials.secretAccessKey)".utf8)))
@ -150,18 +157,18 @@ public struct AWSSigner {
let kService = HMAC<SHA256>.authenticationCode(for: Data(name.utf8), using: SymmetricKey(data: kRegion)) let kService = HMAC<SHA256>.authenticationCode(for: Data(name.utf8), using: SymmetricKey(data: kRegion))
let kSigning = HMAC<SHA256>.authenticationCode(for: Data("aws4_request".utf8), using: SymmetricKey(data: kService)) let kSigning = HMAC<SHA256>.authenticationCode(for: Data("aws4_request".utf8), using: SymmetricKey(data: kService))
let kSignature = HMAC<SHA256>.authenticationCode(for: stringToSign(signingData: signingData), using: SymmetricKey(data: kSigning)) let kSignature = HMAC<SHA256>.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 /// 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 { func stringToSign(signingData: SigningData) -> Data {
let stringToSign = "AWS4-HMAC-SHA256\n" + let stringToSign = "AWS4-HMAC-SHA256\n" +
"\(signingData.datetime)\n" + "\(signingData.datetime)\n" +
"\(signingData.date)/\(region)/\(name)/aws4_request\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) 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 /// 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 { func canonicalRequest(signingData: SigningData) -> Data {
let canonicalHeaders = signingData.headersToSign.map { return "\($0.key.lowercased()):\($0.value.trimmingCharacters(in: CharacterSet.whitespaces))" } let canonicalHeaders = signingData.headersToSign.map { return "\($0.key.lowercased()):\($0.value.trimmingCharacters(in: CharacterSet.whitespaces))" }
@ -175,20 +182,20 @@ public struct AWSSigner {
signingData.hashedPayload signingData.hashedPayload
return Data(canonicalRequest.utf8) return Data(canonicalRequest.utf8)
} }
/// Create a SHA256 hash of the Requests body /// Create a SHA256 hash of the Requests body
static func hashedPayload(_ payload: BodyData?) -> String { static func hashedPayload(_ payload: BodyData?) -> String {
guard let payload = payload else { return hashedEmptyBody } guard let payload = payload else { return hashedEmptyBody }
let hash : String? let hash : String?
switch payload { switch payload {
case .string(let string): case .string(let string):
hash = SHA256.hash(data: Data(string.utf8)).description hash = SHA256.hash(data: Data(string.utf8)).hexDigest()
case .data(let data): case .data(let data):
hash = SHA256.hash(data: data).description hash = SHA256.hash(data: data).hexDigest()
case .byteBuffer(let byteBuffer): case .byteBuffer(let byteBuffer):
let byteBufferView = byteBuffer.readableBytesView let byteBufferView = byteBuffer.readableBytesView
hash = byteBufferView.withContiguousStorageIfAvailable { bytes in hash = byteBufferView.withContiguousStorageIfAvailable { bytes in
return SHA256.hash(data: bytes).description return SHA256.hash(data: bytes).hexDigest()
} }
} }
if let hash = hash { if let hash = hash {
@ -197,19 +204,23 @@ public struct AWSSigner {
return hashedEmptyBody return hashedEmptyBody
} }
} }
/// return a hexEncoded string buffer from an array of bytes /// return a hexEncoded string buffer from an array of bytes
static func hexEncoded(_ buffer: [UInt8]) -> String { static func hexEncoded(_ buffer: [UInt8]) -> String {
return buffer.map{String(format: "%02x", $0)}.joined(separator: "") return buffer.map{String(format: "%02x", $0)}.joined(separator: "")
} }
/// create timestamp dateformatter
/// return a timestamp formatted for signing requests static private func createTimeStampDateFormatter() -> DateFormatter {
static func timestamp(_ date: Date) -> String {
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
formatter.timeZone = TimeZone(abbreviation: "UTC") formatter.timeZone = TimeZone(abbreviation: "UTC")
formatter.locale = Locale(identifier: "en_US_POSIX") 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 { func queryEncode() -> String {
return addingPercentEncoding(withAllowedCharacters: String.queryAllowedCharacters) ?? self return addingPercentEncoding(withAllowedCharacters: String.queryAllowedCharacters) ?? self
} }
func uriEncode() -> String { func uriEncode() -> String {
return addingPercentEncoding(withAllowedCharacters: String.uriAllowedCharacters) ?? self return addingPercentEncoding(withAllowedCharacters: String.uriAllowedCharacters) ?? self
} }
func uriEncodeWithSlash() -> String { func uriEncodeWithSlash() -> String {
return addingPercentEncoding(withAllowedCharacters: String.uriAllowedWithSlashCharacters) ?? self return addingPercentEncoding(withAllowedCharacters: String.uriAllowedWithSlashCharacters) ?? self
} }
static let uriAllowedWithSlashCharacters = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~/") static let uriAllowedWithSlashCharacters = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~/")
static let uriAllowedCharacters = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~") static let uriAllowedCharacters = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
static let queryAllowedCharacters = CharacterSet(charactersIn:"/;+").inverted 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: "")
}
}

View File

@ -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 <openssl/hmac.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
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

View File

@ -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 <string.h>
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__

View File

@ -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<UInt8>(repeating: 0, count: size)
for i in 0..<size {
data[i] = getUInt8()
}
return data
}
func testMD5() {
let data = createRandomBuffer(34, 2345, size: 234896)
let digest = Insecure.MD5.hash(data: data)
XCTAssertEqual(digest.description, "3abdd8d79be09bc250d60ada1f000912")
}
func testSHA1() {
let data = createRandomBuffer(41, 83, size: 562384)
let digest = Insecure.SHA1.hash(data: data)
XCTAssertEqual(digest.description, "2ea7cc3ce53e940a5877cbf5c8dfc992e4833fb3")
}
func testSHA256() {
let data = createRandomBuffer(872, 12489, size: 562741)
let digest = SHA256.hash(data: data)
XCTAssertEqual(digest.description, "3cff070559024d8652d1257e5f455787e95ebd8e95378d62df1a466f78860f74")
}
func testSHA384() {
let data = createRandomBuffer(872, 12489, size: 562741)
let digest = SHA384.hash(data: data)
XCTAssertEqual(digest.description, "d03a6a749dd66fb7bb261e34014c69e217684440b0c853727ac5bc12147edddc304cadbec8df8f77ec2ee44cc6b53bc3")
}
func testSHA512() {
let data = createRandomBuffer(872, 12489, size: 562741)
let digest = SHA512.hash(data: data)
XCTAssertEqual(digest.description, "15fc2df3a1c3649b83baf0f28d1a611bee8339a050d9d2c2ac4afad18f3187f725530b09bb6b2044131648d11d608c394804bc02ce2110b76d231ea75201000d")
}
func testSHA256InitUpdateFinal() {
let data = createRandomBuffer(8372, 12489, size: 562741)
let digest = SHA256.hash(data: data)
var sha256 = SHA256()
sha256.update(data: data[0..<238768])
sha256.update(data: data[238768..<562741])
let digest2 = sha256.finalize()
XCTAssertEqual(digest, digest2)
XCTAssertEqual(digest.description, digest2.description)
}
func testHMAC() {
let data = createRandomBuffer(1, 91, size: 347237)
let key = createRandomBuffer(102, 3, size: 32)
let authenticationKey = HMAC<SHA256>.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<SHA256>.authenticationCode(for: data, using: SymmetricKey(bytes: key))
var hmac = HMAC<SHA256>(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),
]
}

View File

@ -1,9 +1,7 @@
import XCTest import XCTest
@testable import AWSSignerTests @testable import AWSSignerTests
@testable import AWSCryptoTests
XCTMain([ XCTMain([
testCase(AWSSignerTests.allTests), testCase(AWSSignerTests.allTests)
testCase(AWSCryptoTests.allTests)
]) ])

View File

@ -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 \ RUN apt-get -qq update && apt-get install -y \
libssl-dev zlib1g-dev \ zlib1g-dev \
&& rm -r /var/lib/apt/lists/* && rm -r /var/lib/apt/lists/*