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:
Franz Busch 2022-10-14 09:50:36 +01:00 committed by GitHub
parent 1c59986eaf
commit d11194ad26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 2856 additions and 213 deletions

View File

@ -36,7 +36,8 @@ if development {
]
dependencies = [
"CCryptoBoringSSL",
"CCryptoBoringSSLShims"
"CCryptoBoringSSLShims",
"CryptoBoringWrapper"
]
} else {
swiftSettings = [
@ -49,7 +50,8 @@ if development {
]
dependencies = [
.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
),
.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"]),
.testTarget(name: "CryptoTests", dependencies: ["Crypto"], swiftSettings: swiftSettings),
.testTarget(name: "_CryptoExtrasTests", dependencies: ["_CryptoExtras"]),

View File

@ -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 *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_from_seed(void *out_public_key,

View File

@ -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);
}
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) {
CCryptoBoringSSL_ED25519_keypair(out_public_key, out_private_key);
}

View File

@ -14,4 +14,5 @@
add_subdirectory(CCryptoBoringSSL)
add_subdirectory(CCryptoBoringSSLShims)
add_subdirectory(CryptoBoringWrapper)
add_subdirectory(Crypto)

View File

@ -15,6 +15,7 @@
@_exported import CryptoKit
#else
@_implementationOnly import CCryptoBoringSSL
@_implementationOnly import CryptoBoringWrapper
import Foundation
enum OpenSSLAESGCMImpl {

View File

@ -16,8 +16,31 @@
#else
@_implementationOnly import CCryptoBoringSSL
@_implementationOnly import CCryptoBoringSSLShims
@_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>(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 {
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 {
@ -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

View File

@ -85,12 +85,14 @@ target_compile_definitions(Crypto PRIVATE
CRYPTO_IN_SWIFTPM_FORCE_BUILD_API)
target_include_directories(Crypto PRIVATE
$<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
$<$<NOT:$<PLATFORM_ID:Darwin>>:dispatch>
$<$<NOT:$<PLATFORM_ID:Darwin>>:Foundation>
CCryptoBoringSSL
CCryptoBoringSSLShims)
CCryptoBoringSSLShims
CryptoBoringWrapper)
set_target_properties(Crypto PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})

View File

@ -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()
}
}
}

View File

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

View File

@ -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()))
}
}

View File

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

View File

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

View File

@ -12,7 +12,6 @@
//
//===----------------------------------------------------------------------===//
#if !canImport(Security)
@_implementationOnly import CCryptoBoringSSL
import Crypto
@ -23,5 +22,3 @@ extension CryptoKitError {
return .underlyingCoreCryptoError(error: Int32(bitPattern: CCryptoBoringSSL_ERR_get_error()))
}
}
#endif

View File

@ -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...])
}
}
}

View File

@ -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")
}
}
}
}

View File

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

View File

@ -17,7 +17,7 @@ set -eu
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
function replace_acceptable_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... "