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 = [
|
||||
"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"]),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -14,4 +14,5 @@
|
|||
|
||||
add_subdirectory(CCryptoBoringSSL)
|
||||
add_subdirectory(CCryptoBoringSSLShims)
|
||||
add_subdirectory(CryptoBoringWrapper)
|
||||
add_subdirectory(Crypto)
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
@_exported import CryptoKit
|
||||
#else
|
||||
@_implementationOnly import CCryptoBoringSSL
|
||||
@_implementationOnly import CryptoBoringWrapper
|
||||
import Foundation
|
||||
|
||||
enum OpenSSLAESGCMImpl {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -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
|
||||
import Crypto
|
||||
|
||||
|
@ -23,5 +22,3 @@ extension CryptoKitError {
|
|||
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 )"
|
||||
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... "
|
||||
|
|
Loading…
Reference in New Issue