diff --git a/Package.swift b/Package.swift index a66e212..4841abf 100644 --- a/Package.swift +++ b/Package.swift @@ -6,10 +6,10 @@ import PackageDescription let package = Package( name: "jose-swift", platforms: [ - .iOS(.v14), + .iOS(.v15), .macOS(.v12), - .macCatalyst(.v14), - .tvOS(.v12), + .macCatalyst(.v15), + .tvOS(.v15), .watchOS(.v5) ], products: [ diff --git a/Sources/JSONWebAlgorithms/CryptoError.swift b/Sources/JSONWebAlgorithms/CryptoError.swift index 2a6feea..78fdc13 100644 --- a/Sources/JSONWebAlgorithms/CryptoError.swift +++ b/Sources/JSONWebAlgorithms/CryptoError.swift @@ -14,6 +14,7 @@ * limitations under the License. */ +import CommonCrypto import Foundation /// `CryptoError` is an enumeration representing various errors that can occur in cryptographic operations. @@ -79,4 +80,6 @@ public enum CryptoError: LocalizedError { case cannotGenerateKeyForTypeAndCurve(type: String, curve: String?) case keyFormatNotSupported(format: String, supportedFormats: [String]) + + case commonCryptoError(status: CCStatus) } diff --git a/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyUnwrap+KeyUnwrap.swift b/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyUnwrap+KeyUnwrap.swift index 98985f0..e92c9e2 100644 --- a/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyUnwrap+KeyUnwrap.swift +++ b/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyUnwrap+KeyUnwrap.swift @@ -37,10 +37,14 @@ struct AESKeyUnwrap: KeyUnwrapping { guard let key = using.key else { throw CryptoError.missingOctetSequenceKey } - - return try AES.KeyWrap.unwrap( - encryptedKey, - using: .init(data: key) - ).withUnsafeBytes { Data($0) } + + if #available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *) { + return try AES.KeyWrap.unwrap( + encryptedKey, + using: .init(data: key) + ).withUnsafeBytes { Data($0) } + } else { + return try AESKeyWrapperCommonCrypto().unwrap(key: encryptedKey, encryptionKey: key) + } } } diff --git a/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyWrap+KeyEncryption.swift b/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyWrap+KeyEncryption.swift index 57c9342..3fcd144 100644 --- a/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyWrap+KeyEncryption.swift +++ b/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyWrap+KeyEncryption.swift @@ -45,10 +45,15 @@ struct AESKeyWrap: KeyWrapping { throw CryptoError.notValidPrivateKey } - let encryptedKey = try AES.KeyWrap.wrap( - .init(data: cek), - using: .init(data: key) - ) + let encryptedKey: Data + if #available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *) { + encryptedKey = try AES.KeyWrap.wrap( + .init(data: cek), + using: .init(data: key) + ) + } else { + encryptedKey = try AESKeyWrapperCommonCrypto().wrap(key: cek, encryptionKey: key) + } return .init( encryptedKey: encryptedKey, diff --git a/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyWrapCommonCrypto.swift b/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyWrapCommonCrypto.swift new file mode 100644 index 0000000..30b6435 --- /dev/null +++ b/Sources/JSONWebAlgorithms/KeyManagement/KeyEncryption/AES/AESKeyWrapCommonCrypto.swift @@ -0,0 +1,88 @@ +/* + * Copyright 2024 Gonçalo Frade + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import CommonCrypto +import Foundation + +class AESKeyWrapperCommonCrypto { + private let algorithm: CCWrappingAlgorithm = UInt32(kCCWRAPAES) + + func wrap(key: Data, encryptionKey: Data) throws -> Data { + let outputSize = CCSymmetricWrappedSize(algorithm, key.count) + var buffer = Data(count: outputSize) + var wrappedKeyLength = outputSize + + let result = buffer.write(withPointerTo: encryptionKey, key) { bufferPtr, ptrs in + CCSymmetricKeyWrap(self.algorithm, + CCrfc3394_iv, + CCrfc3394_ivLen, + ptrs[0], + encryptionKey.count, + ptrs[1], + key.count, + bufferPtr, + &wrappedKeyLength) + } + + if result != kCCSuccess { + throw CryptoError.commonCryptoError(status: result) + } + + return buffer.prefix(upTo: wrappedKeyLength) + } + + func unwrap(key: Data, encryptionKey: Data) throws -> Data { + let outputSize = CCSymmetricUnwrappedSize(algorithm, key.count) + var buffer = Data(count: outputSize) + var unwrappedKeyLength = outputSize + + let result = buffer.write(withPointerTo: encryptionKey, key) { bufferPtr, ptrs in + CCSymmetricKeyUnwrap(self.algorithm, + CCrfc3394_iv, + CCrfc3394_ivLen, + ptrs[0], + encryptionKey.count, + ptrs[1], + key.count, + bufferPtr, + &unwrappedKeyLength) + } + + if result != kCCSuccess { + throw CryptoError.commonCryptoError(status: result) + } + + return buffer.prefix(upTo: unwrappedKeyLength) + } +} + +fileprivate extension Data { + func write(withPointerTo args: Data..., body: (UnsafeMutablePointer, [UnsafePointer]) -> T) -> T { + return self.withUnsafeBytes { bufferRawPtr -> T in + let bufferPtr = bufferRawPtr.bindMemory(to: UInt8.self).baseAddress! + var ptrs: [UnsafePointer] = [] + for arg in args { + arg.withUnsafeBytes { argRawPtr in + let argPtr = argRawPtr.bindMemory(to: UInt8.self).baseAddress! + ptrs.append(argPtr) + } + } + return bufferPtr.withMemoryRebound(to: UInt8.self, capacity: count) { reboundBufferPtr in + body(UnsafeMutablePointer(mutating: reboundBufferPtr), ptrs) + } + } + } +} diff --git a/Tests/JWSTests/Mocks/JWK+Testing.swift b/Tests/JWSTests/Mocks/JWK+Testing.swift index 3446b9e..a6fd06a 100644 --- a/Tests/JWSTests/Mocks/JWK+Testing.swift +++ b/Tests/JWSTests/Mocks/JWK+Testing.swift @@ -33,7 +33,7 @@ extension JWK { static var testingES256PairSecKey: SecKey { let p256Data = P256.Signing.PrivateKey().x963Representation let attributes: [String: Any] = [ - kSecAttrKeyType as String: kSecAttrKeyTypeECDSA, + kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecAttrKeySizeInBits as String: 256 ]