Provide AES-GCM-SIV in CryptoExtras (#133)
# Motivation BoringSSL exposes AES-GCM-SIV algorithms which are in general useful to have and provide a nonce-misuse resistant mode of AES-GCM. Since, `CryptoKit` is not exposing AES-GCM-SIV we need to add this to `_CryptoExtras` # Modification Exposes `AES-GCM-SIV` through `_CryptoExtras`. # Result We can now use `AES-GCM-SIV` through `_CryptoExtras`.
This commit is contained in:
parent
1c59986eaf
commit
d11194ad26
|
@ -36,7 +36,8 @@ if development {
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"CCryptoBoringSSL",
|
"CCryptoBoringSSL",
|
||||||
"CCryptoBoringSSLShims"
|
"CCryptoBoringSSLShims",
|
||||||
|
"CryptoBoringWrapper"
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
swiftSettings = [
|
swiftSettings = [
|
||||||
|
@ -49,7 +50,8 @@ if development {
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
.target(name: "CCryptoBoringSSL", condition: .when(platforms: platforms)),
|
.target(name: "CCryptoBoringSSL", condition: .when(platforms: platforms)),
|
||||||
.target(name: "CCryptoBoringSSLShims", condition: .when(platforms: platforms))
|
.target(name: "CCryptoBoringSSLShims", condition: .when(platforms: platforms)),
|
||||||
|
.target(name: "CryptoBoringWrapper", condition: .when(platforms: platforms))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +107,17 @@ let package = Package(
|
||||||
],
|
],
|
||||||
swiftSettings: swiftSettings
|
swiftSettings: swiftSettings
|
||||||
),
|
),
|
||||||
.target(name: "_CryptoExtras", dependencies: ["CCryptoBoringSSL", "CCryptoBoringSSLShims", "Crypto"]),
|
.target(name: "_CryptoExtras", dependencies: ["CCryptoBoringSSL", "CCryptoBoringSSLShims", "CryptoBoringWrapper", "Crypto"]),
|
||||||
|
.target(
|
||||||
|
name: "CryptoBoringWrapper",
|
||||||
|
dependencies: [
|
||||||
|
"CCryptoBoringSSL",
|
||||||
|
"CCryptoBoringSSLShims"
|
||||||
|
],
|
||||||
|
exclude: [
|
||||||
|
"CMakeLists.txt",
|
||||||
|
]
|
||||||
|
),
|
||||||
.executableTarget(name: "crypto-shasum", dependencies: ["Crypto"]),
|
.executableTarget(name: "crypto-shasum", dependencies: ["Crypto"]),
|
||||||
.testTarget(name: "CryptoTests", dependencies: ["Crypto"], swiftSettings: swiftSettings),
|
.testTarget(name: "CryptoTests", dependencies: ["Crypto"], swiftSettings: swiftSettings),
|
||||||
.testTarget(name: "_CryptoExtrasTests", dependencies: ["_CryptoExtras"]),
|
.testTarget(name: "_CryptoExtrasTests", dependencies: ["_CryptoExtras"]),
|
||||||
|
|
|
@ -58,6 +58,13 @@ int CCryptoBoringSSLShims_EVP_AEAD_CTX_open_gather(const EVP_AEAD_CTX *ctx, void
|
||||||
const void *in_tag, size_t in_tag_len,
|
const void *in_tag, size_t in_tag_len,
|
||||||
const void *ad, size_t ad_len);
|
const void *ad, size_t ad_len);
|
||||||
|
|
||||||
|
|
||||||
|
int CCryptoBoringSSLShims_EVP_AEAD_CTX_open(const EVP_AEAD_CTX *ctx, void *out, size_t *out_len, size_t max_out_len,
|
||||||
|
const void *nonce, size_t nonce_len,
|
||||||
|
const void *in, size_t in_len,
|
||||||
|
const void *ad, size_t ad_len);
|
||||||
|
|
||||||
|
|
||||||
void CCryptoBoringSSLShims_ED25519_keypair(void *out_public_key, void *out_private_key);
|
void CCryptoBoringSSLShims_ED25519_keypair(void *out_public_key, void *out_private_key);
|
||||||
|
|
||||||
void CCryptoBoringSSLShims_ED25519_keypair_from_seed(void *out_public_key,
|
void CCryptoBoringSSLShims_ED25519_keypair_from_seed(void *out_public_key,
|
||||||
|
|
|
@ -55,6 +55,13 @@ int CCryptoBoringSSLShims_EVP_AEAD_CTX_open_gather(const EVP_AEAD_CTX *ctx, void
|
||||||
return CCryptoBoringSSL_EVP_AEAD_CTX_open_gather(ctx, out, nonce, nonce_len, in, in_len, in_tag, in_tag_len, ad, ad_len);
|
return CCryptoBoringSSL_EVP_AEAD_CTX_open_gather(ctx, out, nonce, nonce_len, in, in_len, in_tag, in_tag_len, ad, ad_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int CCryptoBoringSSLShims_EVP_AEAD_CTX_open(const EVP_AEAD_CTX *ctx, void *out, size_t *out_len, size_t max_out_len,
|
||||||
|
const void *nonce, size_t nonce_len,
|
||||||
|
const void *in, size_t in_len,
|
||||||
|
const void *ad, size_t ad_len) {
|
||||||
|
return CCryptoBoringSSL_EVP_AEAD_CTX_open(ctx, out, out_len, max_out_len, nonce, nonce_len, in, in_len, ad, ad_len);
|
||||||
|
}
|
||||||
|
|
||||||
void CCryptoBoringSSLShims_ED25519_keypair(void *out_public_key, void *out_private_key) {
|
void CCryptoBoringSSLShims_ED25519_keypair(void *out_public_key, void *out_private_key) {
|
||||||
CCryptoBoringSSL_ED25519_keypair(out_public_key, out_private_key);
|
CCryptoBoringSSL_ED25519_keypair(out_public_key, out_private_key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,4 +14,5 @@
|
||||||
|
|
||||||
add_subdirectory(CCryptoBoringSSL)
|
add_subdirectory(CCryptoBoringSSL)
|
||||||
add_subdirectory(CCryptoBoringSSLShims)
|
add_subdirectory(CCryptoBoringSSLShims)
|
||||||
|
add_subdirectory(CryptoBoringWrapper)
|
||||||
add_subdirectory(Crypto)
|
add_subdirectory(Crypto)
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
@_exported import CryptoKit
|
@_exported import CryptoKit
|
||||||
#else
|
#else
|
||||||
@_implementationOnly import CCryptoBoringSSL
|
@_implementationOnly import CCryptoBoringSSL
|
||||||
|
@_implementationOnly import CryptoBoringWrapper
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum OpenSSLAESGCMImpl {
|
enum OpenSSLAESGCMImpl {
|
||||||
|
|
|
@ -16,8 +16,31 @@
|
||||||
#else
|
#else
|
||||||
@_implementationOnly import CCryptoBoringSSL
|
@_implementationOnly import CCryptoBoringSSL
|
||||||
@_implementationOnly import CCryptoBoringSSLShims
|
@_implementationOnly import CCryptoBoringSSLShims
|
||||||
|
@_implementationOnly import CryptoBoringWrapper
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
extension BoringSSLAEAD {
|
||||||
|
/// Seal a given message.
|
||||||
|
func seal<Plaintext: DataProtocol, Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(message: Plaintext, key: SymmetricKey, nonce: Nonce, authenticatedData: AuthenticatedData) throws -> (ciphertext: Data, tag: Data) {
|
||||||
|
do {
|
||||||
|
let context = try AEADContext(cipher: self, key: key)
|
||||||
|
return try context.seal(message: message, nonce: nonce, authenticatedData: authenticatedData)
|
||||||
|
} catch CryptoBoringWrapperError.underlyingCoreCryptoError(let errorCode) {
|
||||||
|
throw CryptoKitError.underlyingCoreCryptoError(error: errorCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a given message.
|
||||||
|
func open<Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(ciphertext: Data, key: SymmetricKey, nonce: Nonce, tag: Data, authenticatedData: AuthenticatedData) throws -> Data {
|
||||||
|
do {
|
||||||
|
let context = try AEADContext(cipher: self, key: key)
|
||||||
|
return try context.open(ciphertext: ciphertext, nonce: nonce, tag: tag, authenticatedData: authenticatedData)
|
||||||
|
} catch CryptoBoringWrapperError.underlyingCoreCryptoError(let errorCode) {
|
||||||
|
throw CryptoKitError.underlyingCoreCryptoError(error: errorCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum OpenSSLChaChaPolyImpl {
|
enum OpenSSLChaChaPolyImpl {
|
||||||
static func encrypt<M: DataProtocol, AD: DataProtocol>(key: SymmetricKey, message: M, nonce: ChaChaPoly.Nonce?, authenticatedData: AD?) throws -> ChaChaPoly.SealedBox {
|
static func encrypt<M: DataProtocol, AD: DataProtocol>(key: SymmetricKey, message: M, nonce: ChaChaPoly.Nonce?, authenticatedData: AD?) throws -> ChaChaPoly.SealedBox {
|
||||||
guard key.bitCount == ChaChaPoly.keyBitsCount else {
|
guard key.bitCount == ChaChaPoly.keyBitsCount else {
|
||||||
|
@ -48,208 +71,4 @@ enum OpenSSLChaChaPolyImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An abstraction over a BoringSSL AEAD
|
|
||||||
@usableFromInline
|
|
||||||
enum BoringSSLAEAD {
|
|
||||||
/// The supported AEAD ciphers for BoringSSL.
|
|
||||||
case aes128gcm
|
|
||||||
case aes192gcm
|
|
||||||
case aes256gcm
|
|
||||||
case chacha20
|
|
||||||
|
|
||||||
/// Seal a given message.
|
|
||||||
func seal<Plaintext: DataProtocol, Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(message: Plaintext, key: SymmetricKey, nonce: Nonce, authenticatedData: AuthenticatedData) throws -> (ciphertext: Data, tag: Data) {
|
|
||||||
let context = try AEADContext(cipher: self, key: key)
|
|
||||||
return try context.seal(message: message, nonce: nonce, authenticatedData: authenticatedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open a given message.
|
|
||||||
func open<Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(ciphertext: Data, key: SymmetricKey, nonce: Nonce, tag: Data, authenticatedData: AuthenticatedData) throws -> Data {
|
|
||||||
let context = try AEADContext(cipher: self, key: key)
|
|
||||||
return try context.open(ciphertext: ciphertext, nonce: nonce, tag: tag, authenticatedData: authenticatedData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension BoringSSLAEAD {
|
|
||||||
// Arguably this class is excessive, but it's probably better for this API to be as safe as possible
|
|
||||||
// rather than rely on defer statements for our cleanup.
|
|
||||||
class AEADContext {
|
|
||||||
private var context: EVP_AEAD_CTX
|
|
||||||
|
|
||||||
@usableFromInline
|
|
||||||
init(cipher: BoringSSLAEAD, key: SymmetricKey) throws {
|
|
||||||
self.context = EVP_AEAD_CTX()
|
|
||||||
let rc: CInt = key.withUnsafeBytes { keyPointer in
|
|
||||||
withUnsafeMutablePointer(to: &self.context) { contextPointer in
|
|
||||||
// Create the AEAD context with a default tag length using the given key.
|
|
||||||
CCryptoBoringSSLShims_EVP_AEAD_CTX_init(contextPointer, cipher.boringSSLCipher, keyPointer.baseAddress, keyPointer.count, 0, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard rc == 1 else {
|
|
||||||
throw CryptoKitError.internalBoringSSLError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
withUnsafeMutablePointer(to: &self.context) { contextPointer in
|
|
||||||
CCryptoBoringSSL_EVP_AEAD_CTX_cleanup(contextPointer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Sealing
|
|
||||||
|
|
||||||
extension BoringSSLAEAD.AEADContext {
|
|
||||||
/// The main entry point for sealing data. Covers the full gamut of types, including discontiguous data types. This must be inlinable.
|
|
||||||
@inlinable
|
|
||||||
func seal<Plaintext: DataProtocol, Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(message: Plaintext, nonce: Nonce, authenticatedData: AuthenticatedData) throws -> (ciphertext: Data, tag: Data) {
|
|
||||||
// Seal is a somewhat awkward function. As it returns a Data, we are going to need to initialize a Data large enough to write into. Data does not provide us an
|
|
||||||
// initializer that gives us access to its uninitialized memory, so the cost of creating this Data is the cost of allocating the data + the cost of initializing
|
|
||||||
// it. For smaller plaintexts this isn't too big a deal, but for larger ones the initialization cost can really get hairy.
|
|
||||||
//
|
|
||||||
// We can avoid this by using Data(bytesNoCopy:deallocator:), so that's what we do. In principle we can do slightly better in the case where we have a discontiguous Plaintext
|
|
||||||
// type, but it's honestly not worth it enough to justify the code complexity.
|
|
||||||
switch (message.regions.count, authenticatedData.regions.count) {
|
|
||||||
case (1, 1):
|
|
||||||
// We can use a nice fast-path here.
|
|
||||||
return try self._sealContiguous(message: message.regions.first!, nonce: nonce, authenticatedData: authenticatedData.regions.first!)
|
|
||||||
case (1, _):
|
|
||||||
let contiguousAD = Array(authenticatedData)
|
|
||||||
return try self._sealContiguous(message: message.regions.first!, nonce: nonce, authenticatedData: contiguousAD)
|
|
||||||
case (_, 1):
|
|
||||||
let contiguousMessage = Array(message)
|
|
||||||
return try self._sealContiguous(message: contiguousMessage, nonce: nonce, authenticatedData: authenticatedData.regions.first!)
|
|
||||||
case (_, _):
|
|
||||||
let contiguousMessage = Array(message)
|
|
||||||
let contiguousAD = Array(authenticatedData)
|
|
||||||
return try self._sealContiguous(message: contiguousMessage, nonce: nonce, authenticatedData: contiguousAD)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A fast-path for sealing contiguous data. Also inlinable to gain specialization information.
|
|
||||||
@inlinable
|
|
||||||
func _sealContiguous<Plaintext: ContiguousBytes, Nonce: ContiguousBytes, AuthenticatedData: ContiguousBytes>(message: Plaintext, nonce: Nonce, authenticatedData: AuthenticatedData) throws -> (ciphertext: Data, tag: Data) {
|
|
||||||
return try message.withUnsafeBytes { messagePointer in
|
|
||||||
try nonce.withUnsafeBytes { noncePointer in
|
|
||||||
try authenticatedData.withUnsafeBytes { authenticatedDataPointer in
|
|
||||||
try self._sealContiguous(plaintext: messagePointer, noncePointer: noncePointer, authenticatedData: authenticatedDataPointer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The unsafe base call: not inlinable so that it can touch private variables.
|
|
||||||
@usableFromInline
|
|
||||||
func _sealContiguous(plaintext: UnsafeRawBufferPointer, noncePointer: UnsafeRawBufferPointer, authenticatedData: UnsafeRawBufferPointer) throws -> (ciphertext: Data, tag: Data) {
|
|
||||||
let tagByteCount = CCryptoBoringSSL_EVP_AEAD_max_overhead(self.context.aead)
|
|
||||||
|
|
||||||
// We use malloc here because we are going to call free later. We force unwrap to trigger crashes if the allocation
|
|
||||||
// fails.
|
|
||||||
let outputBuffer = UnsafeMutableRawBufferPointer(start: malloc(plaintext.count)!, count: plaintext.count)
|
|
||||||
let tagBuffer = UnsafeMutableRawBufferPointer(start: malloc(tagByteCount)!, count: tagByteCount)
|
|
||||||
var actualTagSize = tagBuffer.count
|
|
||||||
|
|
||||||
let rc = withUnsafeMutablePointer(to: &self.context) { contextPointer in
|
|
||||||
CCryptoBoringSSLShims_EVP_AEAD_CTX_seal_scatter(contextPointer,
|
|
||||||
outputBuffer.baseAddress,
|
|
||||||
tagBuffer.baseAddress, &actualTagSize, tagBuffer.count,
|
|
||||||
noncePointer.baseAddress, noncePointer.count,
|
|
||||||
plaintext.baseAddress, plaintext.count,
|
|
||||||
nil, 0,
|
|
||||||
authenticatedData.baseAddress, authenticatedData.count)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard rc == 1 else {
|
|
||||||
// Ooops, error. Free the memory we allocated before we throw.
|
|
||||||
free(outputBuffer.baseAddress)
|
|
||||||
free(tagBuffer.baseAddress)
|
|
||||||
throw CryptoKitError.internalBoringSSLError()
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = Data(bytesNoCopy: outputBuffer.baseAddress!, count: outputBuffer.count, deallocator: .free)
|
|
||||||
let tag = Data(bytesNoCopy: tagBuffer.baseAddress!, count: actualTagSize, deallocator: .free)
|
|
||||||
return (ciphertext: output, tag: tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Opening
|
|
||||||
|
|
||||||
extension BoringSSLAEAD.AEADContext {
|
|
||||||
/// The main entry point for opening data. Covers the full gamut of types, including discontiguous data types. This must be inlinable.
|
|
||||||
@inlinable
|
|
||||||
func open<Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(ciphertext: Data, nonce: Nonce, tag: Data, authenticatedData: AuthenticatedData) throws -> Data {
|
|
||||||
// Open is a somewhat awkward function. As it returns a Data, we are going to need to initialize a Data large enough to write into. Data does not provide us an
|
|
||||||
// initializer that gives us access to its uninitialized memory, so the cost of creating this Data is the cost of allocating the data + the cost of initializing
|
|
||||||
// it. For smaller plaintexts this isn't too big a deal, but for larger ones the initialization cost can really get hairy.
|
|
||||||
//
|
|
||||||
// We can avoid this by using Data(bytesNoCopy:deallocator:), so that's what we do. In principle we can do slightly better in the case where we have a discontiguous Plaintext
|
|
||||||
// type, but it's honestly not worth it enough to justify the code complexity.
|
|
||||||
if authenticatedData.regions.count == 1 {
|
|
||||||
// We can use a nice fast-path here.
|
|
||||||
return try self._openContiguous(ciphertext: ciphertext, nonce: nonce, tag: tag, authenticatedData: authenticatedData.regions.first!)
|
|
||||||
} else {
|
|
||||||
let contiguousAD = Array(authenticatedData)
|
|
||||||
return try self._openContiguous(ciphertext: ciphertext, nonce: nonce, tag: tag, authenticatedData: contiguousAD)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A fast-path for opening contiguous data. Also inlinable to gain specialization information.
|
|
||||||
@inlinable
|
|
||||||
func _openContiguous<Nonce: ContiguousBytes, AuthenticatedData: ContiguousBytes>(ciphertext: Data, nonce: Nonce, tag: Data, authenticatedData: AuthenticatedData) throws -> Data {
|
|
||||||
try ciphertext.withUnsafeBytes { ciphertextPointer in
|
|
||||||
try nonce.withUnsafeBytes { nonceBytes in
|
|
||||||
try tag.withUnsafeBytes { tagBytes in
|
|
||||||
try authenticatedData.withUnsafeBytes { authenticatedDataBytes in
|
|
||||||
try self._openContiguous(ciphertext: ciphertextPointer, nonceBytes: nonceBytes, tagBytes: tagBytes, authenticatedData: authenticatedDataBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The unsafe base call: not inlinable so that it can touch private variables.
|
|
||||||
@usableFromInline
|
|
||||||
func _openContiguous(ciphertext: UnsafeRawBufferPointer, nonceBytes: UnsafeRawBufferPointer, tagBytes: UnsafeRawBufferPointer, authenticatedData: UnsafeRawBufferPointer) throws -> Data {
|
|
||||||
// We use malloc here because we are going to call free later. We force unwrap to trigger crashes if the allocation
|
|
||||||
// fails.
|
|
||||||
let outputBuffer = UnsafeMutableRawBufferPointer(start: malloc(ciphertext.count)!, count: ciphertext.count)
|
|
||||||
|
|
||||||
let rc = withUnsafePointer(to: &self.context) { contextPointer in
|
|
||||||
CCryptoBoringSSLShims_EVP_AEAD_CTX_open_gather(contextPointer,
|
|
||||||
outputBuffer.baseAddress,
|
|
||||||
nonceBytes.baseAddress, nonceBytes.count,
|
|
||||||
ciphertext.baseAddress, ciphertext.count,
|
|
||||||
tagBytes.baseAddress, tagBytes.count,
|
|
||||||
authenticatedData.baseAddress, authenticatedData.count)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard rc == 1 else {
|
|
||||||
// Ooops, error. Free the memory we allocated before we throw.
|
|
||||||
free(outputBuffer.baseAddress)
|
|
||||||
throw CryptoKitError.internalBoringSSLError()
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = Data(bytesNoCopy: outputBuffer.baseAddress!, count: outputBuffer.count, deallocator: .free)
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Supported ciphers
|
|
||||||
|
|
||||||
extension BoringSSLAEAD {
|
|
||||||
var boringSSLCipher: OpaquePointer {
|
|
||||||
switch self {
|
|
||||||
case .aes128gcm:
|
|
||||||
return CCryptoBoringSSL_EVP_aead_aes_128_gcm()
|
|
||||||
case .aes192gcm:
|
|
||||||
return CCryptoBoringSSL_EVP_aead_aes_192_gcm()
|
|
||||||
case .aes256gcm:
|
|
||||||
return CCryptoBoringSSL_EVP_aead_aes_256_gcm()
|
|
||||||
case .chacha20:
|
|
||||||
return CCryptoBoringSSL_EVP_aead_chacha20_poly1305()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
|
#endif // (os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) && CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
|
||||||
|
|
|
@ -85,12 +85,14 @@ target_compile_definitions(Crypto PRIVATE
|
||||||
CRYPTO_IN_SWIFTPM_FORCE_BUILD_API)
|
CRYPTO_IN_SWIFTPM_FORCE_BUILD_API)
|
||||||
target_include_directories(Crypto PRIVATE
|
target_include_directories(Crypto PRIVATE
|
||||||
$<TARGET_PROPERTY:CCryptoBoringSSL,INCLUDE_DIRECTORIES>
|
$<TARGET_PROPERTY:CCryptoBoringSSL,INCLUDE_DIRECTORIES>
|
||||||
$<TARGET_PROPERTY:CCryptoBoringSSLShims,INCLUDE_DIRECTORIES>)
|
$<TARGET_PROPERTY:CCryptoBoringSSLShims,INCLUDE_DIRECTORIES>
|
||||||
|
$<TARGET_PROPERTY:CryptoBoringWrapper,INCLUDE_DIRECTORIES>)
|
||||||
target_link_libraries(Crypto PUBLIC
|
target_link_libraries(Crypto PUBLIC
|
||||||
$<$<NOT:$<PLATFORM_ID:Darwin>>:dispatch>
|
$<$<NOT:$<PLATFORM_ID:Darwin>>:dispatch>
|
||||||
$<$<NOT:$<PLATFORM_ID:Darwin>>:Foundation>
|
$<$<NOT:$<PLATFORM_ID:Darwin>>:Foundation>
|
||||||
CCryptoBoringSSL
|
CCryptoBoringSSL
|
||||||
CCryptoBoringSSLShims)
|
CCryptoBoringSSLShims
|
||||||
|
CryptoBoringWrapper)
|
||||||
set_target_properties(Crypto PROPERTIES
|
set_target_properties(Crypto PROPERTIES
|
||||||
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
|
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,271 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This source file is part of the SwiftCrypto open source project
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019-2022 Apple Inc. and the SwiftCrypto project authors
|
||||||
|
// Licensed under Apache License v2.0
|
||||||
|
//
|
||||||
|
// See LICENSE.txt for license information
|
||||||
|
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
@_implementationOnly import CCryptoBoringSSL
|
||||||
|
@_implementationOnly import CCryptoBoringSSLShims
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// An abstraction over a BoringSSL AEAD
|
||||||
|
public enum BoringSSLAEAD {
|
||||||
|
/// The supported AEAD ciphers for BoringSSL.
|
||||||
|
case aes128gcm
|
||||||
|
case aes192gcm
|
||||||
|
case aes256gcm
|
||||||
|
case aes128gcmsiv
|
||||||
|
case aes256gcmsiv
|
||||||
|
case chacha20
|
||||||
|
}
|
||||||
|
|
||||||
|
extension BoringSSLAEAD {
|
||||||
|
// Arguably this class is excessive, but it's probably better for this API to be as safe as possible
|
||||||
|
// rather than rely on defer statements for our cleanup.
|
||||||
|
public class AEADContext {
|
||||||
|
private var context: EVP_AEAD_CTX
|
||||||
|
|
||||||
|
public init<Key: ContiguousBytes>(cipher: BoringSSLAEAD, key: Key) throws {
|
||||||
|
self.context = EVP_AEAD_CTX()
|
||||||
|
|
||||||
|
let rc: CInt = key.withUnsafeBytes { keyPointer in
|
||||||
|
withUnsafeMutablePointer(to: &self.context) { contextPointer in
|
||||||
|
// Create the AEAD context with a default tag length using the given key.
|
||||||
|
CCryptoBoringSSLShims_EVP_AEAD_CTX_init(contextPointer, cipher.boringSSLCipher, keyPointer.baseAddress, keyPointer.count, 0, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard rc == 1 else {
|
||||||
|
throw CryptoBoringWrapperError.internalBoringSSLError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
withUnsafeMutablePointer(to: &self.context) { contextPointer in
|
||||||
|
CCryptoBoringSSL_EVP_AEAD_CTX_cleanup(contextPointer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Sealing
|
||||||
|
|
||||||
|
extension BoringSSLAEAD.AEADContext {
|
||||||
|
/// The main entry point for sealing data. Covers the full gamut of types, including discontiguous data types. This must be inlinable.
|
||||||
|
public func seal<Plaintext: DataProtocol, Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(message: Plaintext, nonce: Nonce, authenticatedData: AuthenticatedData) throws -> (ciphertext: Data, tag: Data) {
|
||||||
|
// Seal is a somewhat awkward function. As it returns a Data, we are going to need to initialize a Data large enough to write into. Data does not provide us an
|
||||||
|
// initializer that gives us access to its uninitialized memory, so the cost of creating this Data is the cost of allocating the data + the cost of initializing
|
||||||
|
// it. For smaller plaintexts this isn't too big a deal, but for larger ones the initialization cost can really get hairy.
|
||||||
|
//
|
||||||
|
// We can avoid this by using Data(bytesNoCopy:deallocator:), so that's what we do. In principle we can do slightly better in the case where we have a discontiguous Plaintext
|
||||||
|
// type, but it's honestly not worth it enough to justify the code complexity.
|
||||||
|
switch (message.regions.count, authenticatedData.regions.count) {
|
||||||
|
case (1, 1):
|
||||||
|
// We can use a nice fast-path here.
|
||||||
|
return try self._sealContiguous(message: message.regions.first!, nonce: nonce, authenticatedData: authenticatedData.regions.first!)
|
||||||
|
case (1, _):
|
||||||
|
let contiguousAD = Array(authenticatedData)
|
||||||
|
return try self._sealContiguous(message: message.regions.first!, nonce: nonce, authenticatedData: contiguousAD)
|
||||||
|
case (_, 1):
|
||||||
|
let contiguousMessage = Array(message)
|
||||||
|
return try self._sealContiguous(message: contiguousMessage, nonce: nonce, authenticatedData: authenticatedData.regions.first!)
|
||||||
|
case (_, _):
|
||||||
|
let contiguousMessage = Array(message)
|
||||||
|
let contiguousAD = Array(authenticatedData)
|
||||||
|
return try self._sealContiguous(message: contiguousMessage, nonce: nonce, authenticatedData: contiguousAD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fast-path for sealing contiguous data. Also inlinable to gain specialization information.
|
||||||
|
@inlinable
|
||||||
|
func _sealContiguous<Plaintext: ContiguousBytes, Nonce: ContiguousBytes, AuthenticatedData: ContiguousBytes>(message: Plaintext, nonce: Nonce, authenticatedData: AuthenticatedData) throws -> (ciphertext: Data, tag: Data) {
|
||||||
|
return try message.withUnsafeBytes { messagePointer in
|
||||||
|
try nonce.withUnsafeBytes { noncePointer in
|
||||||
|
try authenticatedData.withUnsafeBytes { authenticatedDataPointer in
|
||||||
|
try self._sealContiguous(plaintext: messagePointer, noncePointer: noncePointer, authenticatedData: authenticatedDataPointer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The unsafe base call: not inlinable so that it can touch private variables.
|
||||||
|
@usableFromInline
|
||||||
|
func _sealContiguous(plaintext: UnsafeRawBufferPointer, noncePointer: UnsafeRawBufferPointer, authenticatedData: UnsafeRawBufferPointer) throws -> (ciphertext: Data, tag: Data) {
|
||||||
|
let tagByteCount = CCryptoBoringSSL_EVP_AEAD_max_overhead(self.context.aead)
|
||||||
|
|
||||||
|
// We use malloc here because we are going to call free later. We force unwrap to trigger crashes if the allocation
|
||||||
|
// fails.
|
||||||
|
let outputBuffer = UnsafeMutableRawBufferPointer(start: malloc(plaintext.count)!, count: plaintext.count)
|
||||||
|
let tagBuffer = UnsafeMutableRawBufferPointer(start: malloc(tagByteCount)!, count: tagByteCount)
|
||||||
|
var actualTagSize = tagBuffer.count
|
||||||
|
|
||||||
|
let rc = withUnsafeMutablePointer(to: &self.context) { contextPointer in
|
||||||
|
CCryptoBoringSSLShims_EVP_AEAD_CTX_seal_scatter(contextPointer,
|
||||||
|
outputBuffer.baseAddress,
|
||||||
|
tagBuffer.baseAddress, &actualTagSize, tagBuffer.count,
|
||||||
|
noncePointer.baseAddress, noncePointer.count,
|
||||||
|
plaintext.baseAddress, plaintext.count,
|
||||||
|
nil, 0,
|
||||||
|
authenticatedData.baseAddress, authenticatedData.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard rc == 1 else {
|
||||||
|
// Ooops, error. Free the memory we allocated before we throw.
|
||||||
|
free(outputBuffer.baseAddress)
|
||||||
|
free(tagBuffer.baseAddress)
|
||||||
|
throw CryptoBoringWrapperError.internalBoringSSLError()
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Data(bytesNoCopy: outputBuffer.baseAddress!, count: outputBuffer.count, deallocator: .free)
|
||||||
|
let tag = Data(bytesNoCopy: tagBuffer.baseAddress!, count: actualTagSize, deallocator: .free)
|
||||||
|
return (ciphertext: output, tag: tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Opening
|
||||||
|
|
||||||
|
extension BoringSSLAEAD.AEADContext {
|
||||||
|
/// The main entry point for opening data. Covers the full gamut of types, including discontiguous data types. This must be inlinable.
|
||||||
|
@inlinable
|
||||||
|
public func open<Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(ciphertext: Data, nonce: Nonce, tag: Data, authenticatedData: AuthenticatedData) throws -> Data {
|
||||||
|
// Open is a somewhat awkward function. As it returns a Data, we are going to need to initialize a Data large enough to write into. Data does not provide us an
|
||||||
|
// initializer that gives us access to its uninitialized memory, so the cost of creating this Data is the cost of allocating the data + the cost of initializing
|
||||||
|
// it. For smaller plaintexts this isn't too big a deal, but for larger ones the initialization cost can really get hairy.
|
||||||
|
//
|
||||||
|
// We can avoid this by using Data(bytesNoCopy:deallocator:), so that's what we do. In principle we can do slightly better in the case where we have a discontiguous Plaintext
|
||||||
|
// type, but it's honestly not worth it enough to justify the code complexity.
|
||||||
|
if authenticatedData.regions.count == 1 {
|
||||||
|
// We can use a nice fast-path here.
|
||||||
|
return try self._openContiguous(ciphertext: ciphertext, nonce: nonce, tag: tag, authenticatedData: authenticatedData.regions.first!)
|
||||||
|
} else {
|
||||||
|
let contiguousAD = Array(authenticatedData)
|
||||||
|
return try self._openContiguous(ciphertext: ciphertext, nonce: nonce, tag: tag, authenticatedData: contiguousAD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fast-path for opening contiguous data. Also inlinable to gain specialization information.
|
||||||
|
@inlinable
|
||||||
|
func _openContiguous<Nonce: ContiguousBytes, AuthenticatedData: ContiguousBytes>(ciphertext: Data, nonce: Nonce, tag: Data, authenticatedData: AuthenticatedData) throws -> Data {
|
||||||
|
try ciphertext.withUnsafeBytes { ciphertextPointer in
|
||||||
|
try nonce.withUnsafeBytes { nonceBytes in
|
||||||
|
try tag.withUnsafeBytes { tagBytes in
|
||||||
|
try authenticatedData.withUnsafeBytes { authenticatedDataBytes in
|
||||||
|
try self._openContiguous(ciphertext: ciphertextPointer, nonceBytes: nonceBytes, tagBytes: tagBytes, authenticatedData: authenticatedDataBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The unsafe base call: not inlinable so that it can touch private variables.
|
||||||
|
@usableFromInline
|
||||||
|
func _openContiguous(ciphertext: UnsafeRawBufferPointer, nonceBytes: UnsafeRawBufferPointer, tagBytes: UnsafeRawBufferPointer, authenticatedData: UnsafeRawBufferPointer) throws -> Data {
|
||||||
|
// We use malloc here because we are going to call free later. We force unwrap to trigger crashes if the allocation
|
||||||
|
// fails.
|
||||||
|
let outputBuffer = UnsafeMutableRawBufferPointer(start: malloc(ciphertext.count)!, count: ciphertext.count)
|
||||||
|
|
||||||
|
let rc = withUnsafePointer(to: &self.context) { contextPointer in
|
||||||
|
return CCryptoBoringSSLShims_EVP_AEAD_CTX_open_gather(contextPointer,
|
||||||
|
outputBuffer.baseAddress,
|
||||||
|
nonceBytes.baseAddress, nonceBytes.count,
|
||||||
|
ciphertext.baseAddress, ciphertext.count,
|
||||||
|
tagBytes.baseAddress, tagBytes.count,
|
||||||
|
authenticatedData.baseAddress, authenticatedData.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard rc == 1 else {
|
||||||
|
// Ooops, error. Free the memory we allocated before we throw.
|
||||||
|
free(outputBuffer.baseAddress)
|
||||||
|
throw CryptoBoringWrapperError.internalBoringSSLError()
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Data(bytesNoCopy: outputBuffer.baseAddress!, count: outputBuffer.count, deallocator: .free)
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An additional entry point for opening data where the ciphertext and the tag can be provided as one combined data . Covers the full gamut of types, including discontiguous data types. This must be inlinable.
|
||||||
|
@inlinable
|
||||||
|
public func open<Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(combinedCiphertextAndTag: Data, nonce: Nonce, authenticatedData: AuthenticatedData) throws -> Data {
|
||||||
|
// Open is a somewhat awkward function. As it returns a Data, we are going to need to initialize a Data large enough to write into. Data does not provide us an
|
||||||
|
// initializer that gives us access to its uninitialized memory, so the cost of creating this Data is the cost of allocating the data + the cost of initializing
|
||||||
|
// it. For smaller plaintexts this isn't too big a deal, but for larger ones the initialization cost can really get hairy.
|
||||||
|
//
|
||||||
|
// We can avoid this by using Data(bytesNoCopy:deallocator:), so that's what we do. In principle we can do slightly better in the case where we have a discontiguous Plaintext
|
||||||
|
// type, but it's honestly not worth it enough to justify the code complexity.
|
||||||
|
if authenticatedData.regions.count == 1 {
|
||||||
|
// We can use a nice fast-path here.
|
||||||
|
return try self._openContiguous(combinedCiphertextAndTag: combinedCiphertextAndTag, nonce: nonce, authenticatedData: authenticatedData.regions.first!)
|
||||||
|
} else {
|
||||||
|
let contiguousAD = Array(authenticatedData)
|
||||||
|
return try self._openContiguous(combinedCiphertextAndTag: combinedCiphertextAndTag, nonce: nonce, authenticatedData: contiguousAD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fast-path for opening contiguous data. Also inlinable to gain specialization information.
|
||||||
|
@inlinable
|
||||||
|
func _openContiguous<Nonce: ContiguousBytes, AuthenticatedData: ContiguousBytes>(combinedCiphertextAndTag: Data, nonce: Nonce, authenticatedData: AuthenticatedData) throws -> Data {
|
||||||
|
try combinedCiphertextAndTag.withUnsafeBytes { combinedCiphertextAndTagPointer in
|
||||||
|
try nonce.withUnsafeBytes { nonceBytes in
|
||||||
|
try authenticatedData.withUnsafeBytes { authenticatedDataBytes in
|
||||||
|
try self._openContiguous(combinedCiphertextAndTag: combinedCiphertextAndTagPointer, nonceBytes: nonceBytes, authenticatedData: authenticatedDataBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The unsafe base call: not inlinable so that it can touch private variables.
|
||||||
|
@usableFromInline
|
||||||
|
func _openContiguous(combinedCiphertextAndTag: UnsafeRawBufferPointer, nonceBytes: UnsafeRawBufferPointer, authenticatedData: UnsafeRawBufferPointer) throws -> Data {
|
||||||
|
// We use malloc here because we are going to call free later. We force unwrap to trigger crashes if the allocation
|
||||||
|
// fails.
|
||||||
|
let outputBuffer = UnsafeMutableRawBufferPointer(start: malloc(combinedCiphertextAndTag.count)!, count: combinedCiphertextAndTag.count)
|
||||||
|
|
||||||
|
var writtenBytes = 0
|
||||||
|
let rc = withUnsafePointer(to: &self.context) { contextPointer in
|
||||||
|
return CCryptoBoringSSLShims_EVP_AEAD_CTX_open(contextPointer,
|
||||||
|
outputBuffer.baseAddress, &writtenBytes, outputBuffer.count,
|
||||||
|
nonceBytes.baseAddress, nonceBytes.count,
|
||||||
|
combinedCiphertextAndTag.baseAddress, combinedCiphertextAndTag.count,
|
||||||
|
authenticatedData.baseAddress, authenticatedData.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard rc == 1 else {
|
||||||
|
// Ooops, error. Free the memory we allocated before we throw.
|
||||||
|
free(outputBuffer.baseAddress)
|
||||||
|
throw CryptoBoringWrapperError.internalBoringSSLError()
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Data(bytesNoCopy: outputBuffer.baseAddress!, count: outputBuffer.count, deallocator: .free).prefix(writtenBytes)
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Supported ciphers
|
||||||
|
|
||||||
|
extension BoringSSLAEAD {
|
||||||
|
var boringSSLCipher: OpaquePointer {
|
||||||
|
switch self {
|
||||||
|
case .aes128gcm:
|
||||||
|
return CCryptoBoringSSL_EVP_aead_aes_128_gcm()
|
||||||
|
case .aes192gcm:
|
||||||
|
return CCryptoBoringSSL_EVP_aead_aes_192_gcm()
|
||||||
|
case .aes256gcm:
|
||||||
|
return CCryptoBoringSSL_EVP_aead_aes_256_gcm()
|
||||||
|
case .aes128gcmsiv:
|
||||||
|
return CCryptoBoringSSL_EVP_aead_aes_128_gcm_siv()
|
||||||
|
case .aes256gcmsiv:
|
||||||
|
return CCryptoBoringSSL_EVP_aead_aes_256_gcm_siv()
|
||||||
|
case .chacha20:
|
||||||
|
return CCryptoBoringSSL_EVP_aead_chacha20_poly1305()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
##===----------------------------------------------------------------------===##
|
||||||
|
##
|
||||||
|
## This source file is part of the SwiftCrypto open source project
|
||||||
|
##
|
||||||
|
## Copyright (c) 2022 Apple Inc. and the SwiftCrypto project authors
|
||||||
|
## Licensed under Apache License v2.0
|
||||||
|
##
|
||||||
|
## See LICENSE.txt for license information
|
||||||
|
## See CONTRIBUTORS.md for the list of SwiftCrypto project authors
|
||||||
|
##
|
||||||
|
## SPDX-License-Identifier: Apache-2.0
|
||||||
|
##
|
||||||
|
##===----------------------------------------------------------------------===##
|
||||||
|
|
||||||
|
add_library(CryptoBoringWrapper
|
||||||
|
"AEAD/BoringSSLAEAD.swift"
|
||||||
|
"CryptoKitErrors_boring.swift")
|
||||||
|
|
||||||
|
target_include_directories(CryptoBoringWrapper PUBLIC
|
||||||
|
$<TARGET_PROPERTY:CCryptoBoringSSL,INCLUDE_DIRECTORIES>
|
||||||
|
$<TARGET_PROPERTY:CCryptoBoringSSLShims,INCLUDE_DIRECTORIES>)
|
||||||
|
|
||||||
|
target_link_libraries(CryptoBoringWrapper PUBLIC
|
||||||
|
CCryptoBoringSSL
|
||||||
|
CCryptoBoringSSLShims)
|
||||||
|
|
||||||
|
set_target_properties(CryptoBoringWrapper PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
|
||||||
|
|
||||||
|
set_property(GLOBAL APPEND PROPERTY SWIFT_CRYPTO_EXPORTS CryptoBoringWrapper)
|
|
@ -0,0 +1,27 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This source file is part of the SwiftCrypto open source project
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019-2021 Apple Inc. and the SwiftCrypto project authors
|
||||||
|
// Licensed under Apache License v2.0
|
||||||
|
//
|
||||||
|
// See LICENSE.txt for license information
|
||||||
|
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
@_implementationOnly import CCryptoBoringSSL
|
||||||
|
|
||||||
|
public enum CryptoBoringWrapperError: Error {
|
||||||
|
case underlyingCoreCryptoError(error: Int32)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CryptoBoringWrapperError {
|
||||||
|
/// A helper function that packs the value of `ERR_get_error` into the internal error field.
|
||||||
|
@usableFromInline
|
||||||
|
static func internalBoringSSLError() -> CryptoBoringWrapperError {
|
||||||
|
return .underlyingCoreCryptoError(error: Int32(bitPattern: CCryptoBoringSSL_ERR_get_error()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This source file is part of the SwiftCrypto open source project
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Apple Inc. and the SwiftCrypto project authors
|
||||||
|
// Licensed under Apache License v2.0
|
||||||
|
//
|
||||||
|
// See LICENSE.txt for license information
|
||||||
|
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
import Crypto
|
||||||
|
@_implementationOnly import CCryptoBoringSSL
|
||||||
|
@_implementationOnly import CCryptoBoringSSLShims
|
||||||
|
@_implementationOnly import CryptoBoringWrapper
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Types associated with the AES GCM SIV algorithm
|
||||||
|
extension AES.GCM {
|
||||||
|
/// AES in GCM SIV mode with 128-bit tags.
|
||||||
|
public enum _SIV {
|
||||||
|
static let tagByteCount = 16
|
||||||
|
static let nonceByteCount = 12
|
||||||
|
|
||||||
|
/// Encrypts and authenticates data using AES-GCM-SIV.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to encrypt and authenticate
|
||||||
|
/// - key: An encryption key of 128 or 256 bits
|
||||||
|
/// - nonce: An Nonce for AES-GCM-SIV encryption. The nonce must be unique for every use of the key to seal data. It can be safely generated with AES.GCM.Nonce()
|
||||||
|
/// - authenticatedData: Data to authenticate as part of the seal
|
||||||
|
/// - Returns: A sealed box returning the authentication tag (seal) and the ciphertext
|
||||||
|
/// - Throws: CipherError errors
|
||||||
|
public static func seal<Plaintext: DataProtocol, AuthenticatedData: DataProtocol>
|
||||||
|
(_ message: Plaintext, using key: SymmetricKey, nonce: Nonce? = nil, authenticating authenticatedData: AuthenticatedData) throws -> SealedBox {
|
||||||
|
return try OpenSSLAESGCMSIVImpl.seal(key: key, message: message, nonce: nonce, authenticatedData: authenticatedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypts and authenticates data using AES-GCM-SIV.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - message: The message to encrypt and authenticate
|
||||||
|
/// - key: An encryption key of 128 or 256 bits
|
||||||
|
/// - nonce: An Nonce for AES-GCM-SIV encryption. The nonce must be unique for every use of the key to seal data. It can be safely generated with AES.GCM.Nonce()
|
||||||
|
/// - Returns: A sealed box returning the authentication tag (seal) and the ciphertext
|
||||||
|
/// - Throws: CipherError errors
|
||||||
|
public static func seal<Plaintext: DataProtocol>
|
||||||
|
(_ message: Plaintext, using key: SymmetricKey, nonce: Nonce? = nil) throws -> SealedBox {
|
||||||
|
return try OpenSSLAESGCMSIVImpl.seal(key: key, message: message, nonce: nonce, authenticatedData: Data?.none)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Authenticates and decrypts data using AES-GCM-SIV.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - sealedBox: The sealed box to authenticate and decrypt
|
||||||
|
/// - key: An encryption key of 128 or 256 bits
|
||||||
|
/// - nonce: An Nonce for AES-GCM-SIV encryption. The nonce must be unique for every use of the key to seal data. It can be safely generated with AES.GCM.Nonce().
|
||||||
|
/// - authenticatedData: Data that was authenticated as part of the seal
|
||||||
|
/// - Returns: The ciphertext if opening was successful
|
||||||
|
/// - Throws: CipherError errors. If the authentication of the sealedbox failed, incorrectTag is thrown.
|
||||||
|
public static func open<AuthenticatedData: DataProtocol>
|
||||||
|
(_ sealedBox: SealedBox, using key: SymmetricKey, authenticating authenticatedData: AuthenticatedData) throws -> Data {
|
||||||
|
return try OpenSSLAESGCMSIVImpl.open(key: key, sealedBox: sealedBox, authenticatedData: authenticatedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Authenticates and decrypts data using AES-GCM-SIV.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - sealedBox: The sealed box to authenticate and decrypt
|
||||||
|
/// - key: An encryption key of 128 or 256 bits
|
||||||
|
/// - nonce: An Nonce for AES-GCM-SIV encryption. The nonce must be unique for every use of the key to seal data. It can be safely generated with AES.GCM.Nonce().
|
||||||
|
/// - Returns: The ciphertext if opening was successful
|
||||||
|
/// - Throws: CipherError errors. If the authentication of the sealedbox failed, incorrectTag is thrown.
|
||||||
|
public static func open(_ sealedBox: SealedBox, using key: SymmetricKey) throws -> Data {
|
||||||
|
return try OpenSSLAESGCMSIVImpl.open(key: key, sealedBox: sealedBox, authenticatedData: Data?.none)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AES.GCM._SIV {
|
||||||
|
public struct Nonce: ContiguousBytes, Sequence {
|
||||||
|
let bytes: Data
|
||||||
|
|
||||||
|
/// Generates a fresh random Nonce. Unless required by a specification to provide a specific Nonce, this is the recommended initializer.
|
||||||
|
public init() {
|
||||||
|
var data = Data(repeating: 0, count: AES.GCM._SIV.nonceByteCount)
|
||||||
|
data.withUnsafeMutableBytes {
|
||||||
|
assert($0.count == AES.GCM._SIV.nonceByteCount)
|
||||||
|
$0.initializeWithRandomBytes(count: AES.GCM._SIV.nonceByteCount)
|
||||||
|
}
|
||||||
|
self.bytes = data
|
||||||
|
}
|
||||||
|
|
||||||
|
public init<D: DataProtocol>(data: D) throws {
|
||||||
|
if data.count != AES.GCM._SIV.nonceByteCount {
|
||||||
|
throw CryptoKitError.incorrectParameterSize
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bytes = Data(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
|
||||||
|
return try self.bytes.withUnsafeBytes(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeIterator() -> Array<UInt8>.Iterator {
|
||||||
|
self.withUnsafeBytes({ (buffPtr) in
|
||||||
|
return Array(buffPtr).makeIterator()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AES.GCM._SIV {
|
||||||
|
public struct SealedBox {
|
||||||
|
/// The combined representation ( nonce || ciphertext || tag)
|
||||||
|
public let combined: Data
|
||||||
|
/// The authentication tag
|
||||||
|
public var tag: Data {
|
||||||
|
return combined.suffix(AES.GCM._SIV.tagByteCount)
|
||||||
|
}
|
||||||
|
/// The ciphertext
|
||||||
|
public var ciphertext: Data {
|
||||||
|
return combined.dropFirst(AES.GCM._SIV.nonceByteCount).dropLast(AES.GCM._SIV.tagByteCount)
|
||||||
|
}
|
||||||
|
/// The Nonce
|
||||||
|
public var nonce: AES.GCM._SIV.Nonce {
|
||||||
|
return try! AES.GCM._SIV.Nonce(data: combined.prefix(AES.GCM._SIV.nonceByteCount))
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
public init<D: DataProtocol>(combined: D) throws {
|
||||||
|
// AES minimum nonce (12 bytes) + AES tag (16 bytes)
|
||||||
|
// While we have these values in the internal APIs, we can't use it in inlinable code.
|
||||||
|
let aesGCMOverhead = 12 + 16
|
||||||
|
|
||||||
|
if combined.count < aesGCMOverhead {
|
||||||
|
throw CryptoKitError.incorrectParameterSize
|
||||||
|
}
|
||||||
|
|
||||||
|
self.combined = Data(combined)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init<C: DataProtocol, T: DataProtocol>(nonce: AES.GCM._SIV.Nonce, ciphertext: C, tag: T) throws {
|
||||||
|
guard tag.count == AES.GCM._SIV.tagByteCount else {
|
||||||
|
throw CryptoKitError.incorrectParameterSize
|
||||||
|
}
|
||||||
|
|
||||||
|
self.combined = Data(nonce) + ciphertext + tag
|
||||||
|
}
|
||||||
|
|
||||||
|
internal init(combined: Data, nonceByteCount: Int) {
|
||||||
|
assert(nonceByteCount == AES.GCM._SIV.nonceByteCount)
|
||||||
|
self.combined = combined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This source file is part of the SwiftCrypto open source project
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Apple Inc. and the SwiftCrypto project authors
|
||||||
|
// Licensed under Apache License v2.0
|
||||||
|
//
|
||||||
|
// See LICENSE.txt for license information
|
||||||
|
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
// This is a copy ChaChaPoly_boring just with a different set aes algos
|
||||||
|
|
||||||
|
@_implementationOnly import CCryptoBoringSSL
|
||||||
|
@_implementationOnly import CCryptoBoringSSLShims
|
||||||
|
import Crypto
|
||||||
|
@_implementationOnly import CryptoBoringWrapper
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension BoringSSLAEAD {
|
||||||
|
/// Seal a given message.
|
||||||
|
func seal<Plaintext: DataProtocol, Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(message: Plaintext, key: SymmetricKey, nonce: Nonce, authenticatedData: AuthenticatedData) throws -> (ciphertext: Data, tag: Data) {
|
||||||
|
do {
|
||||||
|
let context = try AEADContext(cipher: self, key: key)
|
||||||
|
return try context.seal(message: message, nonce: nonce, authenticatedData: authenticatedData)
|
||||||
|
} catch CryptoBoringWrapperError.underlyingCoreCryptoError(let errorCode) {
|
||||||
|
throw CryptoKitError.underlyingCoreCryptoError(error: errorCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a given message.
|
||||||
|
func open<Nonce: ContiguousBytes, AuthenticatedData: DataProtocol>(combinedCiphertextAndTag: Data, key: SymmetricKey, nonce: Nonce, authenticatedData: AuthenticatedData) throws -> Data {
|
||||||
|
do {
|
||||||
|
let context = try AEADContext(cipher: self, key: key)
|
||||||
|
return try context.open(combinedCiphertextAndTag: combinedCiphertextAndTag, nonce: nonce, authenticatedData: authenticatedData)
|
||||||
|
} catch CryptoBoringWrapperError.underlyingCoreCryptoError(let errorCode) {
|
||||||
|
throw CryptoKitError.underlyingCoreCryptoError(error: errorCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OpenSSLAESGCMSIVImpl {
|
||||||
|
@inlinable
|
||||||
|
static func seal<Plaintext: DataProtocol, AuthenticatedData: DataProtocol>
|
||||||
|
(key: SymmetricKey, message: Plaintext, nonce: AES.GCM._SIV.Nonce?, authenticatedData: AuthenticatedData? = nil) throws -> AES.GCM._SIV.SealedBox {
|
||||||
|
let nonce = nonce ?? AES.GCM._SIV.Nonce()
|
||||||
|
|
||||||
|
let aead = try Self._backingAEAD(key: key)
|
||||||
|
|
||||||
|
let ciphertext: Data
|
||||||
|
let tag: Data
|
||||||
|
if let ad = authenticatedData {
|
||||||
|
(ciphertext, tag) = try aead.seal(message: message, key: key, nonce: nonce, authenticatedData: ad)
|
||||||
|
} else {
|
||||||
|
(ciphertext, tag) = try aead.seal(message: message, key: key, nonce: nonce, authenticatedData: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
return try AES.GCM._SIV.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
static func open<AuthenticatedData: DataProtocol>
|
||||||
|
(key: SymmetricKey, sealedBox: AES.GCM._SIV.SealedBox, authenticatedData: AuthenticatedData? = nil) throws -> Data {
|
||||||
|
let aead = try Self._backingAEAD(key: key)
|
||||||
|
|
||||||
|
if let ad = authenticatedData {
|
||||||
|
return try aead.open(combinedCiphertextAndTag: sealedBox.combined.dropFirst(AES.GCM._SIV.nonceByteCount), key: key, nonce: sealedBox.nonce, authenticatedData: ad)
|
||||||
|
} else {
|
||||||
|
return try aead.open(combinedCiphertextAndTag: sealedBox.combined.dropFirst(AES.GCM._SIV.nonceByteCount), key: key, nonce: sealedBox.nonce, authenticatedData: [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
static func _backingAEAD(key: SymmetricKey) throws -> BoringSSLAEAD {
|
||||||
|
switch key.bitCount {
|
||||||
|
case 128:
|
||||||
|
return .aes128gcmsiv
|
||||||
|
case 256:
|
||||||
|
return .aes256gcmsiv
|
||||||
|
default:
|
||||||
|
throw CryptoKitError.incorrectKeySize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,6 @@
|
||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
#if !canImport(Security)
|
|
||||||
@_implementationOnly import CCryptoBoringSSL
|
@_implementationOnly import CCryptoBoringSSL
|
||||||
import Crypto
|
import Crypto
|
||||||
|
|
||||||
|
@ -23,5 +22,3 @@ extension CryptoKitError {
|
||||||
return .underlyingCoreCryptoError(error: Int32(bitPattern: CCryptoBoringSSL_ERR_get_error()))
|
return .underlyingCoreCryptoError(error: Int32(bitPattern: CCryptoBoringSSL_ERR_get_error()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This source file is part of the SwiftCrypto open source project
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019 Apple Inc. and the SwiftCrypto project authors
|
||||||
|
// Licensed under Apache License v2.0
|
||||||
|
//
|
||||||
|
// See LICENSE.txt for license information
|
||||||
|
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
extension UnsafeMutableRawBufferPointer {
|
||||||
|
@inlinable
|
||||||
|
func initializeWithRandomBytes(count: Int) {
|
||||||
|
guard count > 0 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
precondition(count <= self.count)
|
||||||
|
var rng = SystemRandomNumberGenerator()
|
||||||
|
|
||||||
|
// We store bytes 64-bits at a time until we can't anymore.
|
||||||
|
var targetPtr = self
|
||||||
|
while targetPtr.count > 8 {
|
||||||
|
targetPtr.storeBytes(of: rng.next(), as: UInt64.self)
|
||||||
|
targetPtr = UnsafeMutableRawBufferPointer(rebasing: targetPtr[8...])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we're down to having to store things an integer at a time. We do this by shifting and
|
||||||
|
// masking.
|
||||||
|
var remainingWord: UInt64 = rng.next()
|
||||||
|
while targetPtr.count > 0 {
|
||||||
|
targetPtr.storeBytes(of: UInt8(remainingWord & 0xFF), as: UInt8.self)
|
||||||
|
remainingWord >>= 8
|
||||||
|
targetPtr = UnsafeMutableRawBufferPointer(rebasing: targetPtr[1...])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,232 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This source file is part of the SwiftCrypto open source project
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Apple Inc. and the SwiftCrypto project authors
|
||||||
|
// Licensed under Apache License v2.0
|
||||||
|
//
|
||||||
|
// See LICENSE.txt for license information
|
||||||
|
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
import Foundation
|
||||||
|
import XCTest
|
||||||
|
import Crypto
|
||||||
|
import _CryptoExtras
|
||||||
|
|
||||||
|
struct AEADTestGroup: Codable {
|
||||||
|
let ivSize: Int
|
||||||
|
let keySize: UInt16
|
||||||
|
let tagSize: UInt16
|
||||||
|
let type: String
|
||||||
|
let tests: [AESGCMTestVector]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AESGCMTestVector: Codable {
|
||||||
|
let key: String
|
||||||
|
let iv: String
|
||||||
|
let aad: String
|
||||||
|
let msg: String
|
||||||
|
let ct: String
|
||||||
|
let tag: String
|
||||||
|
let result: String
|
||||||
|
}
|
||||||
|
|
||||||
|
class AESGCMSIVTests: XCTestCase {
|
||||||
|
func testPropertiesStayTheSameAfterFailedOpening() throws {
|
||||||
|
let message = Data("this is a message".utf8)
|
||||||
|
let sealed = try AES.GCM._SIV.seal(message, using: SymmetricKey(size: .bits128))
|
||||||
|
|
||||||
|
// We copy the bytes of these fields out here to ensure they're saved.
|
||||||
|
let originalCiphertext = Array(sealed.ciphertext)
|
||||||
|
let originalNonce = Array(sealed.nonce)
|
||||||
|
let originalTag = Array(sealed.tag)
|
||||||
|
|
||||||
|
XCTAssertThrowsError(try AES.GCM._SIV.open(sealed, using: SymmetricKey(size: .bits128)))
|
||||||
|
|
||||||
|
// The fields must all be unchanged.
|
||||||
|
XCTAssertEqual(originalCiphertext, Array(sealed.ciphertext))
|
||||||
|
XCTAssertEqual(originalNonce, Array(sealed.nonce))
|
||||||
|
XCTAssertEqual(originalTag, Array(sealed.tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBadKeySize() {
|
||||||
|
let plaintext: Data = "Some Super Secret Message".data(using: String.Encoding.utf8)!
|
||||||
|
let key = SymmetricKey(size: .init(bitCount: 304))
|
||||||
|
let nonce = AES.GCM._SIV.Nonce()
|
||||||
|
|
||||||
|
XCTAssertThrowsError(try AES.GCM._SIV.seal(plaintext, using: key, nonce: nonce))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEncryptDecrypt() throws {
|
||||||
|
let plaintext: Data = "Some Super Secret Message".data(using: String.Encoding.utf8)!
|
||||||
|
|
||||||
|
let key = SymmetricKey(size: .bits256)
|
||||||
|
let nonce = AES.GCM._SIV.Nonce()
|
||||||
|
|
||||||
|
let ciphertext = try AES.GCM._SIV.seal(plaintext, using: key, nonce: nonce)
|
||||||
|
let recoveredPlaintext = try AES.GCM._SIV.open(ciphertext, using: key, authenticating: Data())
|
||||||
|
let recoveredPlaintextWithoutAAD = try AES.GCM._SIV.open(ciphertext, using: key)
|
||||||
|
|
||||||
|
XCTAssertEqual(recoveredPlaintext, plaintext)
|
||||||
|
XCTAssertEqual(recoveredPlaintextWithoutAAD, plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExtractingBytesFromNonce() throws {
|
||||||
|
let nonce = AES.GCM._SIV.Nonce()
|
||||||
|
XCTAssertEqual(Array(nonce), nonce.withUnsafeBytes { Array($0) })
|
||||||
|
|
||||||
|
let testNonceBytes = Array(UInt8(0)..<UInt8(12))
|
||||||
|
let (contiguousNonceBytes, discontiguousNonceBytes) = testNonceBytes.asDataProtocols()
|
||||||
|
let nonceFromContiguous = try AES.GCM._SIV.Nonce(data: contiguousNonceBytes)
|
||||||
|
let nonceFromDiscontiguous = try AES.GCM._SIV.Nonce(data: discontiguousNonceBytes)
|
||||||
|
|
||||||
|
XCTAssertEqual(Array(nonceFromContiguous), testNonceBytes)
|
||||||
|
XCTAssertEqual(Array(nonceFromDiscontiguous), testNonceBytes)
|
||||||
|
|
||||||
|
XCTAssertThrowsError(try AES.GCM._SIV.Nonce(data: DispatchData.empty)) { error in
|
||||||
|
guard case .some(.incorrectParameterSize) = error as? CryptoKitError else {
|
||||||
|
XCTFail("Unexpected error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUserConstructedSealedBoxesCombined() throws {
|
||||||
|
let ciphertext = Array("This pretty clearly isn't ciphertext, but sure why not".utf8)
|
||||||
|
let (contiguousCiphertext, discontiguousCiphertext) = ciphertext.asDataProtocols()
|
||||||
|
|
||||||
|
let contiguousSB = try AES.GCM._SIV.SealedBox(combined: contiguousCiphertext)
|
||||||
|
let discontiguousSB = try AES.GCM._SIV.SealedBox(combined: discontiguousCiphertext)
|
||||||
|
XCTAssertEqual(contiguousSB.combined, discontiguousSB.combined)
|
||||||
|
XCTAssertEqual(Array(contiguousSB.nonce), Array(discontiguousSB.nonce))
|
||||||
|
XCTAssertEqual(contiguousSB.ciphertext, discontiguousSB.ciphertext)
|
||||||
|
XCTAssertEqual(contiguousSB.tag, discontiguousSB.tag)
|
||||||
|
|
||||||
|
// Empty dispatchdatas don't work, they are too small.
|
||||||
|
XCTAssertThrowsError(try AES.GCM._SIV.SealedBox(combined: DispatchData.empty)) { error in
|
||||||
|
guard case .some(.incorrectParameterSize) = error as? CryptoKitError else {
|
||||||
|
XCTFail("Unexpected error: \(error)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUserConstructedSealedBoxesSplit() throws {
|
||||||
|
let tag = Array(repeating: UInt8(0), count: 16)
|
||||||
|
let ciphertext = Array("This pretty clearly isn't ciphertext, but sure why not".utf8)
|
||||||
|
let nonce = AES.GCM._SIV.Nonce()
|
||||||
|
|
||||||
|
let (contiguousCiphertext, discontiguousCiphertext) = ciphertext.asDataProtocols()
|
||||||
|
let (contiguousTag, discontiguousTag) = tag.asDataProtocols()
|
||||||
|
|
||||||
|
// Two separate data protocol inputs means we end up with 4 boxes.
|
||||||
|
let contiguousContiguous = try AES.GCM._SIV.SealedBox(nonce: nonce, ciphertext: contiguousCiphertext, tag: contiguousTag)
|
||||||
|
let discontiguousContiguous = try AES.GCM._SIV.SealedBox(nonce: nonce, ciphertext: discontiguousCiphertext, tag: contiguousTag)
|
||||||
|
let contiguousDiscontiguous = try AES.GCM._SIV.SealedBox(nonce: nonce, ciphertext: contiguousCiphertext, tag: discontiguousTag)
|
||||||
|
let discontiguousDiscontiguous = try AES.GCM._SIV.SealedBox(nonce: nonce, ciphertext: discontiguousCiphertext, tag: discontiguousTag)
|
||||||
|
|
||||||
|
// To avoid the comparison count getting too nuts, we use the combined representation. By the transitive
|
||||||
|
// property we only need three comparisons.
|
||||||
|
XCTAssertEqual(contiguousContiguous.combined, discontiguousContiguous.combined)
|
||||||
|
XCTAssertEqual(discontiguousContiguous.combined, contiguousDiscontiguous.combined)
|
||||||
|
XCTAssertEqual(contiguousDiscontiguous.combined, discontiguousDiscontiguous.combined)
|
||||||
|
|
||||||
|
// Empty dispatchdatas for the tag don't work, they are too small.
|
||||||
|
XCTAssertThrowsError(try AES.GCM._SIV.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: DispatchData.empty)) { error in
|
||||||
|
guard case .some(.incorrectParameterSize) = error as? CryptoKitError else {
|
||||||
|
XCTFail("Unexpected error: \(error)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// They work fine for the ciphertext though.
|
||||||
|
let weirdBox = try AES.GCM._SIV.SealedBox(nonce: nonce, ciphertext: DispatchData.empty, tag: tag)
|
||||||
|
XCTAssertEqual(weirdBox.ciphertext, Data())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRoundTripDataProtocols() throws {
|
||||||
|
func roundTrip<Message: DataProtocol, AAD: DataProtocol>(message: Message, aad: AAD, file: StaticString = (#file), line: UInt = #line) throws {
|
||||||
|
let key = SymmetricKey(size: .bits256)
|
||||||
|
let nonce = AES.GCM._SIV.Nonce()
|
||||||
|
let ciphertext = try AES.GCM._SIV.seal(message, using: key, nonce: nonce, authenticating: aad)
|
||||||
|
let recoveredPlaintext = try AES.GCM._SIV.open(ciphertext, using: key, authenticating: aad)
|
||||||
|
|
||||||
|
XCTAssertEqual(Array(recoveredPlaintext), Array(message), file: file, line: line)
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = Array("Hello, world, it's AES-GCM!".utf8)
|
||||||
|
let aad = Array("I heard you like Counter Mode, so I put a Galois on it".utf8)
|
||||||
|
let (contiguousMessage, discontiguousMessage) = message.asDataProtocols()
|
||||||
|
let (contiguousAad, discontiguousAad) = aad.asDataProtocols()
|
||||||
|
|
||||||
|
_ = try roundTrip(message: contiguousMessage, aad: contiguousAad)
|
||||||
|
_ = try roundTrip(message: discontiguousMessage, aad: contiguousAad)
|
||||||
|
_ = try roundTrip(message: contiguousMessage, aad: discontiguousAad)
|
||||||
|
_ = try roundTrip(message: discontiguousMessage, aad: discontiguousAad)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testWycheproof() throws {
|
||||||
|
try wycheproofTest(
|
||||||
|
jsonName: "aes_gcm_siv_test",
|
||||||
|
testFunction: { (group: AEADTestGroup) in
|
||||||
|
_ = try testGroup(group: group)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGroup(group: AEADTestGroup) throws {
|
||||||
|
for testVector in group.tests {
|
||||||
|
var msg: Data = Data()
|
||||||
|
var aad: Data = Data()
|
||||||
|
var ct: [UInt8] = []
|
||||||
|
var tag: [UInt8] = []
|
||||||
|
|
||||||
|
var nonce: AES.GCM._SIV.Nonce
|
||||||
|
|
||||||
|
do {
|
||||||
|
nonce = try AES.GCM._SIV.Nonce(data: Array(hexString: testVector.iv))
|
||||||
|
} catch {
|
||||||
|
XCTAssertEqual(testVector.result, "invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if testVector.ct.count > 0 {
|
||||||
|
ct = try Array(hexString: testVector.ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testVector.msg.count > 0 {
|
||||||
|
msg = try Data(hexString: testVector.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testVector.aad.count > 0 {
|
||||||
|
aad = try Data(hexString: testVector.aad)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testVector.tag.count > 0 {
|
||||||
|
tag = try Array(hexString: testVector.tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = try SymmetricKey(data: Array(hexString: testVector.key))
|
||||||
|
XCTAssertNotNil(key)
|
||||||
|
|
||||||
|
let sb = try AES.GCM._SIV.seal(msg, using: key, nonce: nonce, authenticating: aad)
|
||||||
|
|
||||||
|
if testVector.result == "valid" {
|
||||||
|
XCTAssertEqual(Data(ct), sb.ciphertext)
|
||||||
|
XCTAssertEqual(Data(tag), sb.tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let recovered_pt = try AES.GCM._SIV.open(AES.GCM._SIV.SealedBox(nonce: nonce, ciphertext: ct, tag: tag), using: key, authenticating: aad)
|
||||||
|
|
||||||
|
if testVector.result == "valid" || testVector.result == "acceptable" {
|
||||||
|
XCTAssertEqual(recovered_pt, msg)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
XCTAssertEqual(testVector.result, "invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// This source file is part of the SwiftCrypto open source project
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019-2022 Apple Inc. and the SwiftCrypto project authors
|
||||||
|
// Licensed under Apache License v2.0
|
||||||
|
//
|
||||||
|
// See LICENSE.txt for license information
|
||||||
|
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
import Foundation
|
||||||
|
import Dispatch
|
||||||
|
|
||||||
|
// A testing utility that creates one contiguous and one discontiguous representation of the given Data.
|
||||||
|
extension Array where Element == UInt8 {
|
||||||
|
func asDataProtocols() -> (contiguous: Data, discontiguous: DispatchData) {
|
||||||
|
guard self.count > 0 else {
|
||||||
|
// We can't really have discontiguous options here, so we just return empty versions
|
||||||
|
// of both.
|
||||||
|
return (Data(), DispatchData.empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
let contiguous = Data(self)
|
||||||
|
let discontiguous: DispatchData = self.withUnsafeBytes { bytesPointer in
|
||||||
|
let pivot = bytesPointer.count / 2
|
||||||
|
var data = DispatchData.empty
|
||||||
|
data.append(DispatchData(bytes: UnsafeRawBufferPointer(rebasing: bytesPointer[..<pivot])))
|
||||||
|
data.append(DispatchData(bytes: UnsafeRawBufferPointer(rebasing: bytesPointer[pivot...])))
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
return (contiguous: contiguous, discontiguous: discontiguous)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -17,7 +17,7 @@ set -eu
|
||||||
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
function replace_acceptable_years() {
|
function replace_acceptable_years() {
|
||||||
# this needs to replace all acceptable forms with 'YEARS'
|
# this needs to replace all acceptable forms with 'YEARS'
|
||||||
sed -e 's/20[12][890]-20[12][1290]/YEARS/' -e 's/2019/YEARS/' -e 's/2020/YEARS/' -e 's/2021/YEARS/'
|
sed -e 's/20[12][890]-20[12][1290]/YEARS/' -e 's/2019/YEARS/' -e 's/2020/YEARS/' -e 's/2021/YEARS/' -e 's/2022/YEARS/'
|
||||||
}
|
}
|
||||||
|
|
||||||
printf "=> Checking for unacceptable language... "
|
printf "=> Checking for unacceptable language... "
|
||||||
|
|
Loading…
Reference in New Issue