Skip to content

Commit

Permalink
fix: PBES2 encryption/decryption failed
Browse files Browse the repository at this point in the history
fix: JWE's AAD authentication
fix: Decoding issue when JWE protected header is empty
fix: Find appropriate JWE key for ECDH-ES
tests: RFC 7920 Encryption tests for ECDH-ES
  • Loading branch information
amosavian committed May 2, 2024
1 parent 8f03e99 commit 3f8327a
Show file tree
Hide file tree
Showing 13 changed files with 826 additions and 53 deletions.
6 changes: 5 additions & 1 deletion Sources/JWSETKit/Cryptography/Algorithms/Compression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ extension JSONWebCompressionAlgorithm {
private static let compressors: PthreadReadWriteLockedValue<[Self: any JSONWebCompressor.Type]> = [
.deflate: AppleCompressor<DeflateCompressionCodec>.self,
]
#else
#elseif canImport(SWCompression)
private static let compressors: PthreadReadWriteLockedValue<[Self: any JSONWebCompressor.Type]> = [
.deflate: Compressor<DeflateCompressionCodec>.self,
]
#else
// This should never happen as Compression is available on Darwin platforms
// and SWCompression is used on non-Darwin platform.
private static let compressors: PthreadReadWriteLockedValue<[Self: any JSONWebCompressor.Type]> = [:]
#endif

/// Returns provided compressor for this algorithm.
Expand Down
22 changes: 10 additions & 12 deletions Sources/JWSETKit/Cryptography/Algorithms/KeyEncryption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ extension JSONWebKeyEncryptionAlgorithm {
.pbes2hmac384: .symmetric,
.pbes2hmac512: .symmetric,
.ecdhEphemeralStatic: .ellipticCurve,
.ecdhEphemeralStaticAESKeyWrap128: .symmetric,
.ecdhEphemeralStaticAESKeyWrap192: .symmetric,
.ecdhEphemeralStaticAESKeyWrap256: .symmetric,
.ecdhEphemeralStaticAESKeyWrap128: .ellipticCurve,
.ecdhEphemeralStaticAESKeyWrap192: .ellipticCurve,
.ecdhEphemeralStaticAESKeyWrap256: .ellipticCurve,
]

private static let keyLengths: PthreadReadWriteLockedValue<[Self: Int]> = [
Expand All @@ -92,9 +92,9 @@ extension JSONWebKeyEncryptionAlgorithm {
.rsaEncryptionOAEPSHA256: JSONWebRSAPrivateKey.KeySize.defaultKeyLength,
.rsaEncryptionOAEPSHA384: JSONWebRSAPrivateKey.KeySize.defaultKeyLength,
.rsaEncryptionOAEPSHA512: JSONWebRSAPrivateKey.KeySize.defaultKeyLength,
.pbes2hmac256: 256,
.pbes2hmac384: 384,
.pbes2hmac512: 512,
.pbes2hmac256: 128,
.pbes2hmac384: 192,
.pbes2hmac512: 256,
.ecdhEphemeralStaticAESKeyWrap128: SymmetricKeySize.bits128.bitCount,
.ecdhEphemeralStaticAESKeyWrap192: SymmetricKeySize.bits192.bitCount,
.ecdhEphemeralStaticAESKeyWrap256: SymmetricKeySize.bits256.bitCount,
Expand Down Expand Up @@ -268,9 +268,8 @@ extension JSONWebKeyEncryptionAlgorithm {
}
let salt = Data(keyEncryptingAlgorithm.rawValue.utf8) + [0x00] + (header.pbes2Salt ?? .init())
let key = try SymmetricKey.paswordBased2DerivedSymmetricKey(
password: password, salt: salt,
hashFunction: keyEncryptingAlgorithm.hashFunction.unsafelyUnwrapped,
iterations: iterations
password: password, salt: salt, iterations: iterations,
length: keyEncryptingAlgorithm.keyLength.map { $0 / 8 }, hashFunction: keyEncryptingAlgorithm.hashFunction.unsafelyUnwrapped
)
return try key.encrypt(cekData, using: keyEncryptingAlgorithm)
}
Expand Down Expand Up @@ -353,9 +352,8 @@ extension JSONWebKeyEncryptionAlgorithm {
}
let salt = Data(algorithm.rawValue.utf8) + [0x00] + (header.pbes2Salt ?? .init())
kek = try SymmetricKey.paswordBased2DerivedSymmetricKey(
password: password, salt: salt,
hashFunction: hashFunction,
iterations: iterations
password: password, salt: salt, iterations: iterations, length: algorithm.keyLength.map { $0 / 8 },
hashFunction: hashFunction
)
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/JWSETKit/Cryptography/Compression/Compressor.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// File.swift
//
// Compressor.swift
//
//
// Created by Amir Abbas Mousavian on 5/1/24.
//
Expand Down
4 changes: 2 additions & 2 deletions Sources/JWSETKit/Cryptography/Symmetric/AES.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public struct JSONWebKeyAESGCM: MutableJSONWebKey, JSONWebSealingKey, JSONWebSym
guard let keyValue = self.keyValue else {
throw CryptoKitError.incorrectKeySize
}
return SymmetricKey(data: keyValue)
return keyValue
}
}

Expand Down Expand Up @@ -103,7 +103,7 @@ public struct JSONWebKeyAESKW: MutableJSONWebKey, JSONWebSymmetricDecryptingKey,
return SymmetricKey(data: keyValue)
}
}

public init(algorithm: any JSONWebAlgorithm) throws {
switch algorithm {
case .aesKeyWrap128:
Expand Down
4 changes: 2 additions & 2 deletions Sources/JWSETKit/Cryptography/Symmetric/CommonCrypto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ extension SymmetricKey {
}

static func ccPbkdf2<PD, SD, H>(
pbkdf2Password password: PD, salt: SD, hashFunction: H.Type, iterations: Int
pbkdf2Password password: PD, salt: SD, iterations: Int, length: Int? = nil, hashFunction: H.Type
) throws -> SymmetricKey where PD: DataProtocol, SD: DataProtocol, H: HashFunction {
let hash = try CCPseudoRandomAlgorithm(hashFunction)
var derivedKeyData = Data(repeating: 0, count: hashFunction.Digest.byteCount)
var derivedKeyData = Data(repeating: 0, count: length ?? hashFunction.Digest.byteCount)
let derivedCount = derivedKeyData.count

let derivationStatus: OSStatus = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
Expand Down
6 changes: 3 additions & 3 deletions Sources/JWSETKit/Cryptography/Symmetric/PBKDF2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ extension SymmetricKey {
///
/// - Returns: A symmetric key derived from parameters.
public static func paswordBased2DerivedSymmetricKey<PD, SD, H>(
password: PD, salt: SD, hashFunction: H.Type, iterations: Int
password: PD, salt: SD, iterations: Int, length: Int? = nil, hashFunction: H.Type
) throws -> SymmetricKey where PD: DataProtocol, SD: DataProtocol, H: HashFunction {
#if canImport(CommonCrypto)
return try ccPbkdf2(pbkdf2Password: password, salt: salt, hashFunction: hashFunction, iterations: iterations)
return try ccPbkdf2(pbkdf2Password: password, salt: salt, iterations: iterations, length: length, hashFunction: hashFunction)
#elseif canImport(CryptoSwift)
let variant = try CryptoSwift.HMAC.Variant(hashFunction)
let key = try PKCS5.PBKDF2(password: [UInt8](password), salt: [UInt8](salt), iterations: iterations, variant: variant).calculate()
let key = try PKCS5.PBKDF2(password: [UInt8](password), salt: [UInt8](salt), iterations: iterations, keyLength: length, variant: variant).calculate()
return .init(data: key)
#else
// This should never happen as CommonCrypto is available on Darwin platforms
Expand Down
25 changes: 22 additions & 3 deletions Sources/JWSETKit/Entities/JWE/JWE.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ public struct JSONWebEncryption: Hashable, Sendable {
try self.init(from: Data(string.utf8))
}

private func autenticating(header: ProtectedJSONWebContainer<JOSEHeader>, additionalAuthenticatedData: Data?) -> Data {
let suffix: Data
if let additionalAuthenticatedData, !additionalAuthenticatedData.isEmpty {
suffix = Data(".".utf8) + additionalAuthenticatedData.urlBase64EncodedData()
} else {
suffix = .init()
}
return header.encoded.urlBase64EncodedData() + suffix
}

/// Decrypts encrypted data, using given private key.
///
/// - Important: For `PBES2` algorithms, provide password using
Expand All @@ -185,7 +195,7 @@ public struct JSONWebEncryption: Hashable, Sendable {
let combinedHeader = header.protected.value
.merging(header.unprotected ?? .init(), uniquingKeysWith: { p, _ in p })
.merging(recipient?.header ?? .init(), uniquingKeysWith: { p, _ in p })
guard let contentEncAlgorithm = header.protected.value.encryptionAlgorithm else {
guard let contentEncAlgorithm = combinedHeader.encryptionAlgorithm else {
throw JSONWebKeyError.unknownAlgorithm
}

Expand All @@ -201,15 +211,24 @@ public struct JSONWebEncryption: Hashable, Sendable {
throw JSONWebKeyError.keyNotFound
}
let cek = try SymmetricKey(data: decryptingKey.decrypt(encryptedKey, using: algorithm))
let authenticating = header.protected.encoded.urlBase64EncodedData() + (additionalAuthenticatedData ?? .init())
let content = try cek.open(sealed, authenticating: authenticating, using: contentEncAlgorithm)
let authenticatingData = autenticating(header: header.protected, additionalAuthenticatedData: additionalAuthenticatedData)
let content = try cek.open(sealed, authenticating: authenticatingData, using: contentEncAlgorithm)

if let compressor = combinedHeader.compressionAlgorithm?.compressor {
return try compressor.decompress(content)
} else {
return content
}
}

public func decrypt(using keys: [any JSONWebKey], keyId: String? = nil) throws -> Data {
for key in keys {
if let data = try? decrypt(using: key, keyId: keyId) {
return data
}
}
throw JSONWebKeyError.keyNotFound
}
}

extension String {
Expand Down
2 changes: 1 addition & 1 deletion Sources/JWSETKit/Entities/JWE/JWEHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public struct JSONWebEncryptionHeader: Hashable, Sendable, Codable {
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.protected = try container.decode(ProtectedJSONWebContainer<JOSEHeader>.self, forKey: .protected)
self.protected = try container.decodeIfPresent(ProtectedJSONWebContainer<JOSEHeader>.self, forKey: .protected) ?? .init(encoded: .init())
self.unprotected = try container.decodeIfPresent(JOSEHeader.self, forKey: .unprotected)
}

Expand Down
3 changes: 2 additions & 1 deletion Sources/JWSETKit/Entities/JWE/JWERecipient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ extension [JSONWebEncryptionRecipient] {
}) {
return recipient
} else if let recipient = first(where: {
$0.header?.algorithm.keyType == key.keyType && $0.header?.algorithm.curve == key.curve
($0.header?.algorithm.keyType == key.keyType && $0.header?.algorithm.curve == key.curve) ||
($0.header?.ephemeralPublicKey?.keyType == key.keyType && $0.header?.ephemeralPublicKey?.curve == key.curve)
}) {
return recipient
} else if let recipient = first {
Expand Down
6 changes: 3 additions & 3 deletions Sources/JWSETKit/Extensions/LockValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ public final class PthreadReadWriteLock: Locking, @unchecked Sendable {
result = pthread_rwlock_wrlock(lock)
}
if result != 0 {
#if canImport(Darwin)
#if canImport(Darwin)
throw POSIXError(.init(rawValue: result) ?? .ELAST)
#else
#else
throw NSError(domain: NSPOSIXErrorDomain, code: Int(result))
#endif
#endif
}
}

Expand Down

0 comments on commit 3f8327a

Please sign in to comment.