Move crypto code to AWSCrypto module
This commit is contained in:
parent
8c14c703ee
commit
81dc1183ed
|
@ -14,9 +14,14 @@ let package = Package(
|
|||
.package(url: "https://github.com/swift-server/async-http-client", .upToNextMajor(from: "1.0.0"))
|
||||
],
|
||||
targets: [
|
||||
.target(name: "AWSSigner", dependencies: ["NIOHTTP1"]),
|
||||
.target(name: "AWSSigner", dependencies: ["AWSCrypto", "NIO", "NIOHTTP1"]),
|
||||
.target(name: "AWSCrypto", dependencies: ["CAWSCrypto"]),
|
||||
.target(name: "CAWSCrypto", dependencies: []),
|
||||
|
||||
.target(name: "HTTPClientAWSSigner", dependencies: ["AWSSigner", "AsyncHTTPClient"]),
|
||||
|
||||
.testTarget(name: "AWSSignerTests", dependencies: ["AWSSigner", "HTTPClientAWSSigner"]),
|
||||
.testTarget(name: "AWSCryptoTests", dependencies: ["AWSCrypto"]),
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -27,12 +32,7 @@ let useOpenSSLShim = true
|
|||
let useOpenSSLShim = false
|
||||
#endif
|
||||
|
||||
// AWSSDKSwiftCore target
|
||||
let awsSdkSwiftCoreTarget = package.targets.first(where: {$0.name == "AWSSigner"})
|
||||
|
||||
// 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.targets.append(.target(name: "CAWSSigner"))
|
||||
awsSdkSwiftCoreTarget?.dependencies.append("CAWSSigner")
|
||||
package.dependencies.append(.package(url: "https://github.com/apple/swift-nio-ssl-support.git", from: "1.0.0"))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// ByteArray.swift
|
||||
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
|
||||
// written by AdamFowler 2020/01/30
|
||||
import protocol Foundation.ContiguousBytes
|
||||
|
||||
/// Protocol for object encapsulating an array of bytes
|
||||
protocol ByteArray: Sequence, ContiguousBytes, CustomStringConvertible, Hashable where Element == UInt8 {
|
||||
init(bytes: [UInt8])
|
||||
var bytes: [UInt8] { get set }
|
||||
}
|
||||
|
||||
extension ByteArray {
|
||||
public func makeIterator() -> Array<UInt8>.Iterator {
|
||||
return self.bytes.makeIterator()
|
||||
}
|
||||
|
||||
public init?(bufferPointer: UnsafeRawBufferPointer) {
|
||||
self.init(bytes: [UInt8](bufferPointer))
|
||||
}
|
||||
|
||||
public func withUnsafeBytes<R>(_ 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Digest.swift
|
||||
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
|
||||
// written by AdamFowler 2020/01/30
|
||||
import protocol Foundation.ContiguousBytes
|
||||
|
||||
/// Protocol for Digest object returned from HashFunction
|
||||
public protocol Digest: Sequence, ContiguousBytes, CustomStringConvertible, Hashable where Element == UInt8 {
|
||||
static var byteCount: Int {get}
|
||||
}
|
||||
|
||||
/// Protocol for Digest object consisting of a byte array
|
||||
protocol ByteDigest: Digest, ByteArray { }
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
// HMAC.swift
|
||||
// based on the Vapor/open-crypto project which tries to replicate the CryptoKit framework interface
|
||||
// written by AdamFowler 2020/01/30
|
||||
import protocol Foundation.DataProtocol
|
||||
|
||||
/// Hash Authentication Code returned by HMAC
|
||||
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<H: CCHashFunction> {
|
||||
|
||||
let key: [UInt8]
|
||||
var context: CCHmacContext
|
||||
|
||||
/// return authentication code for data block given a symmetric key
|
||||
public static func authenticationCode<D : DataProtocol>(for data: D, using key: [UInt8]) -> 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(bytes: .init(bytes))
|
||||
}) {
|
||||
return digest
|
||||
} else {
|
||||
var buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: data.count)
|
||||
data.copyBytes(to: buffer)
|
||||
defer { buffer.deallocate() }
|
||||
self.update(bytes: .init(buffer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension HMAC {
|
||||
/// initialize HMAC with symmetric key
|
||||
public init(key: [UInt8]) {
|
||||
self.key = key
|
||||
self.context = CCHmacContext()
|
||||
self.initialize()
|
||||
}
|
||||
|
||||
/// initialize HMAC calculation
|
||||
mutating func initialize() {
|
||||
CCHmacInit(&context, H.algorithm, key, key.count)
|
||||
}
|
||||
|
||||
/// update HMAC calculation with a buffer
|
||||
public mutating func update(bytes: UnsafeRawBufferPointer) {
|
||||
CCHmacUpdate(&context, bytes.baseAddress, bytes.count)
|
||||
}
|
||||
|
||||
/// finalize HMAC calculation and return authentication code
|
||||
public mutating func finalize() -> HashAuthenticationCode {
|
||||
var authenticationCode: [UInt8] = .init(repeating: 0, count: H.Digest.byteCount)
|
||||
CCHmacFinal(&context, &authenticationCode)
|
||||
return .init(bytes: authenticationCode)
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
import CAWSCrypto
|
||||
|
||||
public struct HMAC<H: OpenSSLHashFunction> {
|
||||
|
||||
let key: [UInt8]
|
||||
var context: OpaquePointer
|
||||
|
||||
/// return authentication code for data block given a symmetric key
|
||||
public static func authenticationCode<D : DataProtocol>(for data: D, using key: [UInt8]) -> 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(bytes: bytes)
|
||||
}) {
|
||||
return digest
|
||||
} else {
|
||||
var buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: data.count)
|
||||
data.copyBytes(to: buffer)
|
||||
defer { buffer.deallocate() }
|
||||
self.update(bytes: .init(buffer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension HMAC {
|
||||
/// initialize HMAC with symmetric key
|
||||
public init(key: [UInt8]) {
|
||||
self.key = key
|
||||
self.context = AWSCRYPTO_HMAC_CTX_new()
|
||||
self.initialize()
|
||||
}
|
||||
|
||||
/// initialize HMAC calculation
|
||||
mutating func initialize() {
|
||||
HMAC_Init_ex(context, key, Int32(key.count), H.algorithm, nil)
|
||||
}
|
||||
|
||||
/// update HMAC calculation with a buffer
|
||||
mutating func update(bytes: UnsafeBufferPointer<UInt8>) {
|
||||
HMAC_Update(context, bytes.baseAddress, bytes.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
|
|
@ -0,0 +1,135 @@
|
|||
// HashFunction.swift
|
||||
// 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
|
||||
import protocol Foundation.DataProtocol
|
||||
|
||||
/// Protocol for Hashing function
|
||||
public protocol HashFunction {
|
||||
/// associated digest object
|
||||
associatedtype Digest: AWSCrypto.Digest
|
||||
|
||||
/// hash raw buffer
|
||||
static func hash(bytes: UnsafeRawBufferPointer) -> Self.Digest
|
||||
|
||||
/// initialization
|
||||
init()
|
||||
|
||||
/// update hash function with data
|
||||
mutating func update(bytes: UnsafeRawBufferPointer)
|
||||
/// finalize hash function and return digest
|
||||
mutating func finalize() -> Self.Digest
|
||||
}
|
||||
|
||||
extension HashFunction {
|
||||
|
||||
/// default version of hash which call init, update and finalize
|
||||
public static func hash(bytes: UnsafeRawBufferPointer) -> Self.Digest {
|
||||
var function = Self()
|
||||
function.update(bytes: bytes)
|
||||
return function.finalize()
|
||||
}
|
||||
|
||||
/// version of hash that takes data in any form that complies with DataProtocol
|
||||
public static func hash<D: DataProtocol>(data: D) -> Self.Digest {
|
||||
if let digest = data.withContiguousStorageIfAvailable({ bytes in
|
||||
return self.hash(bytes: .init(bytes))
|
||||
}) {
|
||||
return digest
|
||||
} else {
|
||||
var buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: data.count)
|
||||
data.copyBytes(to: buffer)
|
||||
defer { buffer.deallocate() }
|
||||
return self.hash(bytes: .init(buffer))
|
||||
}
|
||||
}
|
||||
|
||||
/// version of update that takes data in any form that complies with DataProtocol
|
||||
public mutating func update<D: DataProtocol>(data: D) {
|
||||
if let digest = data.withContiguousStorageIfAvailable({ bytes in
|
||||
return self.update(bytes: .init(bytes))
|
||||
}) {
|
||||
return digest
|
||||
} else {
|
||||
var buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: data.count)
|
||||
data.copyBytes(to: buffer)
|
||||
defer { buffer.deallocate() }
|
||||
self.update(bytes: .init(buffer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(CommonCrypto)
|
||||
|
||||
import CommonCrypto
|
||||
|
||||
/// public protocol for Common Crypto hash functions
|
||||
public protocol CCHashFunction: HashFunction {
|
||||
static var algorithm: CCHmacAlgorithm { get }
|
||||
}
|
||||
|
||||
/// internal protocol for Common Crypto hash functions that hides implementation details
|
||||
protocol _CCHashFunction: CCHashFunction where Digest: ByteDigest {
|
||||
/// Context type that holds the hash function context
|
||||
associatedtype Context
|
||||
|
||||
/// hashing context instance
|
||||
var context: Context { get set }
|
||||
|
||||
/// creates a hashing context
|
||||
static func createContext() -> Context
|
||||
|
||||
/// initialization with context, required by init()
|
||||
init(context: Context)
|
||||
|
||||
/// init hash function
|
||||
func initFunction(_ :UnsafeMutablePointer<Context>)
|
||||
/// update hash function
|
||||
func updateFunction(_ :UnsafeMutablePointer<Context>, _ :UnsafeRawPointer, _ :CC_LONG)
|
||||
/// finalize hash function
|
||||
func finalFunction(_ :UnsafeMutablePointer<UInt8>, _ :UnsafeMutablePointer<Context>) -> Int32
|
||||
}
|
||||
|
||||
#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(bytes: UnsafeRawBufferPointer) {
|
||||
EVP_DigestUpdate(context, bytes.baseAddress, bytes.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
|
|
@ -0,0 +1,55 @@
|
|||
// MD5.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 struct MD5Digest : ByteDigest {
|
||||
public static var byteCount: Int { return Int(CC_MD5_DIGEST_LENGTH) }
|
||||
public var bytes: [UInt8]
|
||||
}
|
||||
|
||||
public struct MD5: CCHashFunction {
|
||||
public typealias Digest = MD5Digest
|
||||
public static var algorithm: CCHmacAlgorithm { return CCHmacAlgorithm(kCCHmacAlgMD5) }
|
||||
var context: CC_MD5_CTX
|
||||
|
||||
public static func hash(bytes: UnsafeRawBufferPointer) -> Self.Digest {
|
||||
var digest: [UInt8] = .init(repeating: 0, count: Digest.byteCount)
|
||||
CC_MD5(bytes.baseAddress, CC_LONG(bytes.count), &digest)
|
||||
return .init(bytes: digest)
|
||||
}
|
||||
|
||||
public init() {
|
||||
context = CC_MD5_CTX()
|
||||
CC_MD5_Init(&context)
|
||||
}
|
||||
|
||||
public mutating func update(bytes: UnsafeRawBufferPointer) {
|
||||
CC_MD5_Update(&context, bytes.baseAddress, CC_LONG(bytes.count))
|
||||
}
|
||||
|
||||
public mutating func finalize() -> Self.Digest {
|
||||
var digest: [UInt8] = .init(repeating: 0, count: Digest.byteCount)
|
||||
CC_MD5_Final(&digest, &context)
|
||||
return .init(bytes: digest)
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
import CAWSCrypto
|
||||
|
||||
public struct MD5Digest : ByteDigest {
|
||||
public static var byteCount: Int { return Int(MD5_DIGEST_LENGTH) }
|
||||
public var bytes: [UInt8]
|
||||
}
|
||||
|
||||
public struct MD5: _OpenSSLHashFunction {
|
||||
public typealias Digest = MD5Digest
|
||||
public static var algorithm: OpaquePointer { return EVP_md5() }
|
||||
var context: OpaquePointer
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,55 @@
|
|||
// SHA2.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)
|
||||
|
||||
public struct SHA256Digest : ByteDigest {
|
||||
public static var byteCount: Int { return Int(CC_SHA256_DIGEST_LENGTH) }
|
||||
public var bytes: [UInt8]
|
||||
}
|
||||
|
||||
import CommonCrypto
|
||||
|
||||
public struct SHA256: CCHashFunction {
|
||||
public typealias Digest = SHA256Digest
|
||||
public static var algorithm: CCHmacAlgorithm { return CCHmacAlgorithm(kCCHmacAlgSHA256) }
|
||||
var context: CC_SHA256_CTX
|
||||
|
||||
public static func hash(bytes: UnsafeRawBufferPointer) -> Self.Digest {
|
||||
var digest: [UInt8] = .init(repeating: 0, count: Digest.byteCount)
|
||||
CC_SHA256(bytes.baseAddress, CC_LONG(bytes.count), &digest)
|
||||
return .init(bytes: digest)
|
||||
}
|
||||
|
||||
public init() {
|
||||
context = CC_SHA256_CTX()
|
||||
CC_SHA256_Init(&context)
|
||||
}
|
||||
|
||||
public mutating func update(bytes: UnsafeRawBufferPointer) {
|
||||
CC_SHA256_Update(&context, bytes.baseAddress, CC_LONG(bytes.count))
|
||||
}
|
||||
|
||||
public mutating func finalize() -> Self.Digest {
|
||||
var digest: [UInt8] = .init(repeating: 0, count: Digest.byteCount)
|
||||
CC_SHA256_Final(&digest, &context)
|
||||
return .init(bytes: digest)
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
#endif
|
|
@ -7,14 +7,14 @@
|
|||
import Foundation
|
||||
|
||||
/// Protocol for providing credential details for accessing AWS services
|
||||
public protocol CredentialProvider {
|
||||
public protocol Credential {
|
||||
var accessKeyId: String {get}
|
||||
var secretAccessKey: String {get}
|
||||
var sessionToken: String? {get}
|
||||
}
|
||||
|
||||
/// basic version of CredentialProvider where you supply the credentials
|
||||
public struct Credential : CredentialProvider {
|
||||
/// basic version of Credential where you supply the credentials
|
||||
public struct StaticCredential: Credential {
|
||||
public let accessKeyId: String
|
||||
public let secretAccessKey: String
|
||||
public let sessionToken: String?
|
||||
|
@ -26,8 +26,8 @@ public struct Credential : CredentialProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/// environment variable version of credential provider that uses system environment variables to get credential details
|
||||
public struct EnvironmentCredential: CredentialProvider {
|
||||
/// environment variable version of credential that uses system environment variables to get credential details
|
||||
public struct EnvironmentCredential: Credential {
|
||||
public let accessKeyId: String
|
||||
public let secretAccessKey: String
|
||||
public let sessionToken: String?
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
//
|
||||
// hash.swift
|
||||
// AsyncHTTPClient
|
||||
//
|
||||
// Created by Adam Fowler on 2019/08/29.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// use CAWSSigner if available, otherwise use CommonCrypto
|
||||
// Package.swift includes CAWSSigner target if we are running on Linux
|
||||
#if canImport(CAWSSigner)
|
||||
|
||||
import CAWSSigner
|
||||
|
||||
public func sha256(_ string: String) -> [UInt8] {
|
||||
let bytes = Array(string.utf8)
|
||||
return sha256(bytes)
|
||||
}
|
||||
|
||||
public func sha256(_ bytes: [UInt8]) -> [UInt8] {
|
||||
var hash = [UInt8](repeating: 0, count: Int(SHA256_DIGEST_LENGTH))
|
||||
SHA256(bytes, bytes.count, &hash)
|
||||
return hash
|
||||
}
|
||||
|
||||
public func sha256(_ buffer: UnsafeBufferPointer<UInt8>) -> [UInt8] {
|
||||
var hash = [UInt8](repeating: 0, count: Int(SHA256_DIGEST_LENGTH))
|
||||
SHA256(buffer.baseAddress, buffer.count, &hash)
|
||||
return hash
|
||||
}
|
||||
|
||||
func hmac(string: String, key: [UInt8]) -> [UInt8] {
|
||||
let context = AWS_SIGNER_HMAC_CTX_new()
|
||||
HMAC_Init_ex(context, key, Int32(key.count), EVP_sha256(), nil)
|
||||
|
||||
let bytes = Array(string.utf8)
|
||||
HMAC_Update(context, bytes, bytes.count)
|
||||
var digest = [UInt8](repeating: 0, count: Int(EVP_MAX_MD_SIZE))
|
||||
var length: UInt32 = 0
|
||||
HMAC_Final(context, &digest, &length)
|
||||
AWS_SIGNER_HMAC_CTX_free(context)
|
||||
|
||||
return Array(digest[0..<Int(length)])
|
||||
}
|
||||
|
||||
#elseif canImport(CommonCrypto)
|
||||
|
||||
import CommonCrypto
|
||||
|
||||
public func sha256(_ string: String) -> [UInt8] {
|
||||
let bytes = Array(string.utf8)
|
||||
return sha256(bytes)
|
||||
}
|
||||
|
||||
public func sha256(_ bytes: [UInt8]) -> [UInt8] {
|
||||
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
||||
CC_SHA256(bytes, CC_LONG(bytes.count), &hash)
|
||||
return hash
|
||||
}
|
||||
|
||||
public func sha256(_ buffer: UnsafeBufferPointer<UInt8>) -> [UInt8] {
|
||||
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
||||
CC_SHA256(buffer.baseAddress, CC_LONG(buffer.count), &hash)
|
||||
return hash
|
||||
}
|
||||
|
||||
public func hmac(string: String, key: [UInt8]) -> [UInt8] {
|
||||
var context = CCHmacContext()
|
||||
CCHmacInit(&context, CCHmacAlgorithm(kCCHmacAlgSHA256), key, key.count)
|
||||
|
||||
let bytes = Array(string.utf8)
|
||||
CCHmacUpdate(&context, bytes, bytes.count)
|
||||
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
||||
CCHmacFinal(&context, &digest)
|
||||
|
||||
return digest
|
||||
}
|
||||
|
||||
#endif
|
|
@ -8,22 +8,23 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import AWSCrypto
|
||||
import NIO
|
||||
import NIOHTTP1
|
||||
|
||||
/// Amazon Web Services V4 Signer
|
||||
public final class AWSSigner {
|
||||
public struct AWSSigner {
|
||||
/// security credentials for accessing AWS services
|
||||
public let credentials: CredentialProvider
|
||||
public let credentials: Credential
|
||||
/// service signing name. In general this is the same as the service name
|
||||
public let name: String
|
||||
/// AWS region you are working in
|
||||
public let region: String
|
||||
|
||||
static let hashedEmptyBody = AWSSigner.hexEncoded(sha256([UInt8]()))
|
||||
static let hashedEmptyBody = SHA256.hash(data: [UInt8]()).description
|
||||
|
||||
/// Initialise the Signer class with AWS credentials
|
||||
public init(credentials: CredentialProvider, name: String, region: String) {
|
||||
public init(credentials: Credential, name: String, region: String) {
|
||||
self.credentials = credentials
|
||||
self.name = name
|
||||
self.region = region
|
||||
|
@ -144,25 +145,25 @@ public final class AWSSigner {
|
|||
|
||||
// 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(string:signingData.date, key:Array("AWS4\(credentials.secretAccessKey)".utf8))
|
||||
let kRegion = hmac(string: region, key: kDate)
|
||||
let kService = hmac(string: name, key: kRegion)
|
||||
let kSigning = hmac(string: "aws4_request", key: kService)
|
||||
let kSignature = hmac(string: stringToSign(signingData: signingData), key: kSigning)
|
||||
return AWSSigner.hexEncoded(kSignature)
|
||||
let kDate = HMAC<SHA256>.authenticationCode(for: Data(signingData.date.utf8), using: Array("AWS4\(credentials.secretAccessKey)".utf8))
|
||||
let kRegion = HMAC<SHA256>.authenticationCode(for: Data(region.utf8), using: kDate.bytes)
|
||||
let kService = HMAC<SHA256>.authenticationCode(for: Data(name.utf8), using: kRegion.bytes)
|
||||
let kSigning = HMAC<SHA256>.authenticationCode(for: Data("aws4_request".utf8), using: kService.bytes)
|
||||
let kSignature = HMAC<SHA256>.authenticationCode(for: stringToSign(signingData: signingData), using: kSigning.bytes)
|
||||
return kSignature.description
|
||||
}
|
||||
|
||||
/// 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) -> String {
|
||||
func stringToSign(signingData: SigningData) -> Data {
|
||||
let stringToSign = "AWS4-HMAC-SHA256\n" +
|
||||
"\(signingData.datetime)\n" +
|
||||
"\(signingData.date)/\(region)/\(name)/aws4_request\n" +
|
||||
AWSSigner.hexEncoded(sha256(canonicalRequest(signingData: signingData)))
|
||||
return stringToSign
|
||||
SHA256.hash(data: canonicalRequest(signingData: signingData)).description
|
||||
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) -> String {
|
||||
func canonicalRequest(signingData: SigningData) -> Data {
|
||||
let canonicalHeaders = signingData.headersToSign.map { return "\($0.key.lowercased()):\($0.value.trimmingCharacters(in: CharacterSet.whitespaces))" }
|
||||
.sorted()
|
||||
.joined(separator: "\n")
|
||||
|
@ -172,28 +173,26 @@ public final class AWSSigner {
|
|||
"\(canonicalHeaders)\n\n" +
|
||||
"\(signingData.signedHeaders)\n" +
|
||||
signingData.hashedPayload
|
||||
return canonicalRequest
|
||||
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 : [UInt8]?
|
||||
let hash : String?
|
||||
switch payload {
|
||||
case .string(let string):
|
||||
hash = sha256(string)
|
||||
hash = SHA256.hash(data: Data(string.utf8)).description
|
||||
case .data(let data):
|
||||
hash = data.withUnsafeBytes { bytes in
|
||||
return sha256(bytes.bindMemory(to: UInt8.self))
|
||||
}
|
||||
hash = SHA256.hash(data: data).description
|
||||
case .byteBuffer(let byteBuffer):
|
||||
let byteBufferView = byteBuffer.readableBytesView
|
||||
hash = byteBufferView.withContiguousStorageIfAvailable { bytes in
|
||||
return sha256(bytes)
|
||||
return SHA256.hash(bytes: .init(bytes)).description
|
||||
}
|
||||
}
|
||||
if let hash = hash {
|
||||
return AWSSigner.hexEncoded(hash)
|
||||
return hash
|
||||
} else {
|
||||
return hashedEmptyBody
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// c_awssdk_openssl.h
|
||||
// c_awscrypto.h
|
||||
// AWSSDKSwiftCore
|
||||
//
|
||||
// Created by Adam Fowler on 2019/08/08.
|
||||
|
@ -8,11 +8,18 @@
|
|||
#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 *AWS_SIGNER_HMAC_CTX_new();
|
||||
void AWS_SIGNER_HMAC_CTX_free(HMAC_CTX* ctx);
|
||||
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
|
|
@ -5,12 +5,14 @@
|
|||
// 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_aws_signer.h"
|
||||
#include "c_awscrypto.h"
|
||||
#include <string.h>
|
||||
|
||||
HMAC_CTX *AWS_SIGNER_HMAC_CTX_new() {
|
||||
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) {
|
||||
|
@ -22,7 +24,7 @@ HMAC_CTX *AWS_SIGNER_HMAC_CTX_new() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void AWS_SIGNER_HMAC_CTX_free(HMAC_CTX* ctx) {
|
||||
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);
|
||||
|
@ -32,3 +34,23 @@ void AWS_SIGNER_HMAC_CTX_free(HMAC_CTX* ctx) {
|
|||
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__
|
|
@ -0,0 +1,45 @@
|
|||
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 = MD5.hash(data: data)
|
||||
print(digest)
|
||||
XCTAssertEqual(digest.description, "3abdd8d79be09bc250d60ada1f000912")
|
||||
}
|
||||
|
||||
func testSHA256() {
|
||||
let data = createRandomBuffer(872, 12489, size: 562741)
|
||||
let digest = SHA256.hash(data: data)
|
||||
print(digest)
|
||||
XCTAssertEqual(digest.description, "3cff070559024d8652d1257e5f455787e95ebd8e95378d62df1a466f78860f74")
|
||||
}
|
||||
|
||||
func testHMAC() {
|
||||
let data = createRandomBuffer(1, 91, size: 347237)
|
||||
let key = createRandomBuffer(102, 3, size: 32)
|
||||
let authenticationKey = HMAC<SHA256>.authenticationCode(for: data, using: key)
|
||||
print(authenticationKey)
|
||||
XCTAssertEqual(authenticationKey.description, "ddec250211f1b546254bab3fb027af1acc4842898e8af6eeadcdbf8e2c6c1ff5")
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import NIO
|
|||
@testable import AWSSigner
|
||||
|
||||
final class AWSSignerTests: XCTestCase {
|
||||
let credentials : CredentialProvider = Credential(accessKeyId: "MYACCESSKEY", secretAccessKey: "MYSECRETACCESSKEY")
|
||||
let credentials : Credential = StaticCredential(accessKeyId: "MYACCESSKEY", secretAccessKey: "MYSECRETACCESSKEY")
|
||||
|
||||
func testSignGetHeaders() {
|
||||
let signer = AWSSigner(credentials: credentials, name: "glacier", region:"us-east-1")
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
FROM swift:5.1.3 as builder
|
||||
|
||||
RUN apt-get -qq update && apt-get install -y \
|
||||
libssl-dev zlib1g-dev \
|
||||
&& rm -r /var/lib/apt/lists/*
|
|
@ -0,0 +1,17 @@
|
|||
# run this with docker-compose -f docker/docker-compose.yml run test
|
||||
version: "3.3"
|
||||
|
||||
services:
|
||||
common: &common
|
||||
build: .
|
||||
volumes:
|
||||
- ..:/working
|
||||
working_dir: /working
|
||||
|
||||
test:
|
||||
<<: *common
|
||||
command: /bin/bash -xcl "swift test"
|
||||
|
||||
shell:
|
||||
<<: *common
|
||||
entrypoint: /bin/bash
|
Loading…
Reference in New Issue