Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ECDH support for JWE Key Management continuation of #268 #314

Merged
merged 13 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ disabled_rules:
- non_optional_string_data_conversion

large_tuple: 5
function_parameter_count: 6

opt_in_rules:
- force_unwrapping
Expand Down
20 changes: 20 additions & 0 deletions JOSESwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
objects = {

/* Begin PBXBuildFile section */
396DD6D1257FB068008353B9 /* ECDHKeyAgreement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DD6D0257FB068008353B9 /* ECDHKeyAgreement.swift */; };
396DD6D5257FB0E8008353B9 /* ECKeyEncryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DD6D4257FB0E8008353B9 /* ECKeyEncryption.swift */; };
396DD6DF257FB13C008353B9 /* ECKeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DD6DE257FB13C008353B9 /* ECKeyPair.swift */; };
396DD6E3257FB260008353B9 /* ECDHTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DD6E2257FB260008353B9 /* ECDHTests.swift */; };
396DD6E7257FB2B5008353B9 /* JWEECTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396DD6E6257FB2B5008353B9 /* JWEECTests.swift */; };
5FB760497BB7711EFB470B5A /* ECKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FB76A41AECF99F3673C1C40 /* ECKeys.swift */; };
5FB76093CE81BF1F8E7C254A /* JWKECDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FB7625CF5EF5D8F2135967F /* JWKECDecodingTests.swift */; };
5FB7628EC6EA2C4263853DE9 /* DataECPrivateKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FB7604267B94DC5091D7105 /* DataECPrivateKey.swift */; };
Expand Down Expand Up @@ -142,6 +147,11 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
396DD6D0257FB068008353B9 /* ECDHKeyAgreement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECDHKeyAgreement.swift; sourceTree = "<group>"; };
396DD6D4257FB0E8008353B9 /* ECKeyEncryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECKeyEncryption.swift; sourceTree = "<group>"; };
396DD6DE257FB13C008353B9 /* ECKeyPair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECKeyPair.swift; sourceTree = "<group>"; };
396DD6E2257FB260008353B9 /* ECDHTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECDHTests.swift; sourceTree = "<group>"; };
396DD6E6257FB2B5008353B9 /* JWEECTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWEECTests.swift; sourceTree = "<group>"; };
5FB7604267B94DC5091D7105 /* DataECPrivateKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataECPrivateKey.swift; sourceTree = "<group>"; };
5FB760DB390F90F91102DB74 /* EC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EC.swift; sourceTree = "<group>"; };
5FB7625CF5EF5D8F2135967F /* JWKECDecodingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JWKECDecodingTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -309,6 +319,7 @@
8A30B8F922118FE6001834E3 /* JWECompressionTests.swift */,
65676D8A1FC220C70031B26D /* JWEDeserializationTests.swift */,
C803EFEC1FA8849C00B71335 /* JWERSATests.swift */,
396DD6E6257FB2B5008353B9 /* JWEECTests.swift */,
65F2558D23FBE6E000A3FC44 /* JWEAESKeyWrapTests.swift */,
C803EFEE1FA884C100B71335 /* JWEHeaderTests.swift */,
653365E420ECCB71002630D7 /* JWEDirectEncryptionTests.swift */,
Expand Down Expand Up @@ -340,6 +351,7 @@
5FB768A267E1A15571CC58AA /* ECVerifierTests.swift */,
5FB76B5896AAA87ACD56D0D0 /* ECSignerTests.swift */,
65F2558F23FBE75300A3FC44 /* AESKeyWrapTests.swift */,
396DD6E2257FB260008353B9 /* ECDHTests.swift */,
);
name = Crypto;
sourceTree = "<group>";
Expand Down Expand Up @@ -370,6 +382,7 @@
isa = PBXGroup;
children = (
65617FCC1F90FB7600D8C743 /* RSAKeyEncryptionMode.swift */,
396DD6D4257FB0E8008353B9 /* ECKeyEncryption.swift */,
);
name = KeyEncryption;
sourceTree = "<group>";
Expand Down Expand Up @@ -599,6 +612,8 @@
5FB7604267B94DC5091D7105 /* DataECPrivateKey.swift */,
5FB76E4A3A52AAC72B1F33F0 /* SecKeyECPrivateKey.swift */,
612B0C93248E1C83009F1929 /* Thumbprint.swift */,
396DD6D0257FB068008353B9 /* ECDHKeyAgreement.swift */,
396DD6DE257FB13C008353B9 /* ECKeyPair.swift */,
);
path = CryptoImplementation;
sourceTree = "<group>";
Expand Down Expand Up @@ -759,6 +774,7 @@
6501503723FBDB42000D7D0B /* AESKeyWrapKeyManagementModeTests.swift in Sources */,
65684A4D2031935200E56C68 /* RSAPublicKeyToDataTests.swift in Sources */,
6575696D203EF9CE004A0EFD /* JWSValidationTests.swift in Sources */,
396DD6E3257FB260008353B9 /* ECDHTests.swift in Sources */,
65F2559023FBE75300A3FC44 /* AESKeyWrapTests.swift in Sources */,
65A103A1202B03BB00D22BF5 /* ASN1DERParsingTests.swift in Sources */,
7402BEAF26274DA40012801E /* HMACCryptoTestCase.swift in Sources */,
Expand Down Expand Up @@ -792,6 +808,7 @@
65F2558E23FBE6E000A3FC44 /* JWEAESKeyWrapTests.swift in Sources */,
5FB7692ED087062737C589E2 /* SecKeyECPublicKeyTests.swift in Sources */,
5FB76834DC4275C5B7D2F742 /* JWSECTests.swift in Sources */,
396DD6E7257FB2B5008353B9 /* JWEECTests.swift in Sources */,
5FB76E7A42B44E4F4416CB7F /* JWKECKeysTests.swift in Sources */,
5FB76093CE81BF1F8E7C254A /* JWKECDecodingTests.swift in Sources */,
5FB765B20A256B93440E79E2 /* JWKECEncodingTests.swift in Sources */,
Expand All @@ -812,6 +829,7 @@
65617FCD1F90FB7600D8C743 /* RSAKeyEncryptionMode.swift in Sources */,
65617FCB1F90F8C700D8C743 /* Encrypter.swift in Sources */,
65E733CC1FEBE8320009EAC6 /* JWKExtensions.swift in Sources */,
396DD6DF257FB13C008353B9 /* ECKeyPair.swift in Sources */,
6536560D2035DF8300A3AC3B /* JWKSetCodable.swift in Sources */,
6546FB0F2029DD10002E421F /* SecKeyRSAPublicKey.swift in Sources */,
6572C2F21F96428800D4186D /* Decrypter.swift in Sources */,
Expand All @@ -838,6 +856,7 @@
C8610F092029B15600859FCC /* Algorithms.swift in Sources */,
658EE1AF23F4067D00B6E967 /* AlgorithmExtensions.swift in Sources */,
65D8E8E820F499EF0059506A /* SymmetricKeys.swift in Sources */,
396DD6D5257FB0E8008353B9 /* ECKeyEncryption.swift in Sources */,
6558164423F44E8D00EA5FEC /* DirectEncryptionMode.swift in Sources */,
65D1D0651F7A4DB3006377CD /* DataConvertible.swift in Sources */,
6533552E1F8FB61000A660C6 /* JWE.swift in Sources */,
Expand Down Expand Up @@ -867,6 +886,7 @@
5FB76B8D7F1214FF01FA1A8B /* ECVerifier.swift in Sources */,
5FB763AFF6BE04E8A6AE4DFC /* DataECPublicKey.swift in Sources */,
5FB7628EC6EA2C4263853DE9 /* DataECPrivateKey.swift in Sources */,
396DD6D1257FB068008353B9 /* ECDHKeyAgreement.swift in Sources */,
5FB76BA2D0344083B8F79D53 /* SecKeyECPrivateKey.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
4 changes: 2 additions & 2 deletions JOSESwift/Sources/AESCBCEncryption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ struct AESCBCEncryption {
}

extension AESCBCEncryption: ContentEncrypter {
func encrypt(header: JWEHeader, payload: Payload) throws -> ContentEncryptionContext {
func encrypt(headerData: Data, payload: Payload) throws -> ContentEncryptionContext {
let plaintext = payload.data()
let additionalAuthenticatedData = header.data().base64URLEncodedData()
let additionalAuthenticatedData = headerData.base64URLEncodedData()

return try encrypt(plaintext, additionalAuthenticatedData: additionalAuthenticatedData)
}
Expand Down
4 changes: 2 additions & 2 deletions JOSESwift/Sources/AESGCMEncryption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ struct AESGCMEncryption {
}

extension AESGCMEncryption: ContentEncrypter {
func encrypt(header: JWEHeader, payload: Payload) throws -> ContentEncryptionContext {
func encrypt(headerData: Data, payload: Payload) throws -> ContentEncryptionContext {
let plaintext = payload.data()
let additionalAuthenticatedData = header.data().base64URLEncodedData()
let additionalAuthenticatedData = headerData.base64URLEncodedData()
return try encrypt(plaintext, additionalAuthenticatedData: additionalAuthenticatedData)
}
}
Expand Down
50 changes: 50 additions & 0 deletions JOSESwift/Sources/Algorithms.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,36 @@ public enum KeyManagementAlgorithm: String, CaseIterable {
case A256KW
/// Direct encryption using a shared symmetric key as the content encryption key
case direct = "dir"
/// Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF
case ECDH_ES = "ECDH-ES"
/// ECDH-ES using Concat KDF and CEK wrapped with "A128KW"
case ECDH_ES_A128KW = "ECDH-ES+A128KW"
/// ECDH-ES using Concat KDF and CEK wrapped with "A192KW"
case ECDH_ES_A192KW = "ECDH-ES+A192KW"
/// ECDH-ES using Concat KDF and CEK wrapped with "A256KW"
case ECDH_ES_A256KW = "ECDH-ES+A256KW"

public var keyWrapAlgorithm: KeyManagementAlgorithm? {
switch self {
case .ECDH_ES_A128KW:
return .A128KW
case .ECDH_ES_A192KW:
return .A192KW
case .ECDH_ES_A256KW:
return .A256KW
default:
return nil
}
}

var shouldContainEphemeralPublicKey: Bool {
switch self {
case .ECDH_ES, .ECDH_ES_A128KW, .ECDH_ES_A192KW, .ECDH_ES_A256KW:
return true
default:
return false
}
}
}

/// Cryptographic algorithms for content encryption.
Expand All @@ -86,6 +116,26 @@ public enum ContentEncryptionAlgorithm: String {
case A256GCM = "A256GCM"
/// Content encryption using AES GCM with 128-bit key
case A128GCM = "A128GCM"

var keyBitSize: Int {
switch self {
case .A128GCM:
return 128
case .A128CBCHS256, .A256GCM:
return 256
case .A256CBCHS512:
return 512
}
}

var tagLength: Int {
switch self {
case .A128CBCHS256, .A128GCM, .A256GCM:
return 16
case .A256CBCHS512:
return 32
}
}
}

/// An algorithm for HMAC calculation.
Expand Down
2 changes: 1 addition & 1 deletion JOSESwift/Sources/ContentEncryption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct ContentDecryptionContext {
}

protocol ContentEncrypter {
func encrypt(header: JWEHeader, payload: Payload) throws -> ContentEncryptionContext
func encrypt(headerData: Data, payload: Payload) throws -> ContentEncryptionContext
}

protocol ContentDecrypter {
Expand Down
108 changes: 54 additions & 54 deletions JOSESwift/Sources/CryptoImplementation/AES.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fileprivate extension ContentEncryptionAlgorithm {
}
}

fileprivate extension KeyManagementAlgorithm {
extension KeyManagementAlgorithm {
func checkAESKeyLength(for key: Data) -> Bool? {
switch self {
case .A128KW:
Expand All @@ -67,6 +67,19 @@ fileprivate extension KeyManagementAlgorithm {
return nil
}
}

var keyBitSize: Int? {
switch self {
case .A128KW:
return 128
case .A192KW:
return 192
case .A256KW:
return 256
default:
return nil
}
}
}

enum AES {
Expand Down Expand Up @@ -180,11 +193,7 @@ enum AES {
throw AESError.keyLengthNotSatisfied
}

// See https://tools.ietf.org/html/rfc3394#section-2.2.3.1
// The default iv is defined to be the hexadecimal constant A6A6A6A6A6A6A6A6
let iv = Data(bytes: CCrfc3394_iv, count: CCrfc3394_ivLen)

let wrapped = ccAESKeyWrap(rawKey: rawKey, keyEncryptionKey: keyEncryptionKey, iv: iv)
let wrapped = ccAESKeyWrap(rawKey: rawKey, keyEncryptionKey: keyEncryptionKey)

guard let wrappedKey = wrapped.data, wrapped.status == kCCSuccess else {
throw AESError.encryptingFailed(description: "Key wrap failed with status: \(wrapped.status).")
Expand All @@ -209,11 +218,7 @@ enum AES {
throw AESError.keyLengthNotSatisfied
}

// See https://tools.ietf.org/html/rfc3394#section-2.2.3.1
// The default iv is defined to be the hexadecimal constant A6A6A6A6A6A6A6A6
let iv = Data(bytes: CCrfc3394_iv, count: CCrfc3394_ivLen)

let unwrapped = ccAESKeyUnwrap(wrappedKey: wrappedKey, keyEncryptionKey: keyEncryptionKey, iv: iv)
let unwrapped = ccAESKeyUnwrap(wrappedKey: wrappedKey, keyEncryptionKey: keyEncryptionKey)

guard let unwrappedKey = unwrapped.data, unwrapped.status == kCCSuccess else {
throw AESError.decryptingFailed(description: "Key unwrap failed with status: \(unwrapped.status).")
Expand All @@ -224,7 +229,6 @@ enum AES {
}

extension AES {
// swiftlint:disable:next function_parameter_count
private static func ccAESCBCCrypt(
operation: CCOperation,
data: Data,
Expand Down Expand Up @@ -281,34 +285,30 @@ extension AES {
extension AES {
private static func ccAESKeyWrap(
rawKey: Data,
keyEncryptionKey: Data,
iv: Data
) -> (data: Data?, status: Int32) {
let alg = CCWrappingAlgorithm(kCCWRAPAES)
keyEncryptionKey: Data) -> (data: Data?, status: Int32) {

let alg = CCWrappingAlgorithm(kCCWRAPAES)
var wrappedKeyLength: size_t = CCSymmetricWrappedSize(alg, rawKey.count)
var wrappedKey = Data(count: wrappedKeyLength)

let status = wrappedKey.withUnsafeMutableBytes { wrappedKeyBytes in
rawKey.withUnsafeBytes { rawKeyBytes in
iv.withUnsafeBytes { ivBytes in
keyEncryptionKey.withUnsafeBytes { keyEncryptionKeyBytes -> Int32 in
guard
let wrappedKeyBytes = wrappedKeyBytes.bindMemory(to: UInt8.self).baseAddress,
let rawKeyBytes = rawKeyBytes.bindMemory(to: UInt8.self).baseAddress,
let ivBytes = ivBytes.bindMemory(to: UInt8.self).baseAddress,
let keyEncryptionKeyBytes = keyEncryptionKeyBytes.bindMemory(to: UInt8.self).baseAddress
else {
return Int32(kCCMemoryFailure)
}
return CCSymmetricKeyWrap(
alg,
ivBytes, iv.count,
keyEncryptionKeyBytes, keyEncryptionKey.count,
rawKeyBytes, rawKey.count,
wrappedKeyBytes, &wrappedKeyLength
)
keyEncryptionKey.withUnsafeBytes { keyEncryptionKeyBytes -> Int32 in
guard
let wrappedKeyBytes = wrappedKeyBytes.bindMemory(to: UInt8.self).baseAddress,
let rawKeyBytes = rawKeyBytes.bindMemory(to: UInt8.self).baseAddress,
let keyEncryptionKeyBytes = keyEncryptionKeyBytes.bindMemory(to: UInt8.self).baseAddress
else {
return Int32(kCCMemoryFailure)
}
return CCSymmetricKeyWrap(
alg,
CCrfc3394_iv,
CCrfc3394_ivLen,
keyEncryptionKeyBytes,
keyEncryptionKey.count,
rawKeyBytes, rawKey.count,
wrappedKeyBytes, &wrappedKeyLength)
}
}
}
Expand All @@ -323,40 +323,40 @@ extension AES {

private static func ccAESKeyUnwrap(
wrappedKey: Data,
keyEncryptionKey: Data,
iv: Data
) -> (data: Data?, status: Int32) {
keyEncryptionKey: Data) -> (data: Data?, status: Int32) {

let alg = CCWrappingAlgorithm(kCCWRAPAES)

var rawKeyLength: size_t = CCSymmetricUnwrappedSize(alg, wrappedKey.count)
var rawKey = Data(count: rawKeyLength)

let status = rawKey.withUnsafeMutableBytes { rawKeyBytes in
wrappedKey.withUnsafeBytes { wrappedKeyBytes in
iv.withUnsafeBytes { ivBytes in
keyEncryptionKey.withUnsafeBytes { keyEncryptionKeyBytes -> Int32 in
guard
let rawKeyBytes = rawKeyBytes.bindMemory(to: UInt8.self).baseAddress,
let wrappedKeyBytes = wrappedKeyBytes.bindMemory(to: UInt8.self).baseAddress,
let ivBytes = ivBytes.bindMemory(to: UInt8.self).baseAddress,
let keyEncryptionKeyBytes = keyEncryptionKeyBytes.bindMemory(to: UInt8.self).baseAddress
else {
return Int32(kCCMemoryFailure)
}
return CCSymmetricKeyUnwrap(
alg,
ivBytes, iv.count,
keyEncryptionKeyBytes, keyEncryptionKey.count,
wrappedKeyBytes, wrappedKey.count,
rawKeyBytes, &rawKeyLength
)
keyEncryptionKey.withUnsafeBytes { keyEncryptionKeyBytes -> Int32 in
guard
let rawKeyBytes = rawKeyBytes.bindMemory(to: UInt8.self).baseAddress,
let wrappedKeyBytes = wrappedKeyBytes.bindMemory(to: UInt8.self).baseAddress,
let keyEncryptionKeyBytes = keyEncryptionKeyBytes.bindMemory(to: UInt8.self).baseAddress
else {
return Int32(kCCMemoryFailure)
}
return CCSymmetricKeyUnwrap(
alg,
CCrfc3394_iv,
CCrfc3394_ivLen,
keyEncryptionKeyBytes,
keyEncryptionKey.count,
wrappedKeyBytes,
wrappedKey.count,
rawKeyBytes,
&rawKeyLength
)
}
}
}

guard status == kCCSuccess else {
return (nil, status)
return (nil, status) // kCCDecodeError
}

rawKey.removeSubrange(rawKeyLength..<rawKey.count)
Expand Down
Loading