Skip to content

Commit

Permalink
feat: PBKDF2 initialzer
Browse files Browse the repository at this point in the history
chore: JWE string convertible
  • Loading branch information
amosavian committed Oct 11, 2023
1 parent 79e8f50 commit 14bbe92
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 11 deletions.
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ let package = Package(
.package(url: "https://github.com/apple/swift-asn1.git", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/apple/swift-crypto.git", .upToNextMajor(from: "3.1.0")),
.package(url: "https://github.com/apple/swift-certificates", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMajor(from: "1.8.0")),
// Plugins
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.50.4"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"),
Expand All @@ -40,7 +41,8 @@ let package = Package(
"AnyCodable",
.product(name: "SwiftASN1", package: "swift-asn1"),
.product(name: "Crypto", package: "swift-crypto", condition: .when(platforms: .nonDarwin)),
.product(name: "_CryptoExtras", package: "swift-crypto", condition: .when(platforms: .nonDarwin)),
.product(name: "CryptoSwift", package: "CryptoSwift"),
.product(name: "_CryptoExtras", package: "swift-crypto"),
.product(name: "X509", package: "swift-certificates"),
]),
.testTarget(
Expand Down
2 changes: 1 addition & 1 deletion Sources/JWSETKit/Base/Storage.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// JSONWebValueStorage.swift
// Storage.swift
//
//
// Created by Amir Abbas Mousavian on 9/6/23.
Expand Down
2 changes: 1 addition & 1 deletion Sources/JWSETKit/Cryptography/Algorithms.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ extension JSONWebKeyEncryptionAlgorithm {
return SHA256.byteCount
case .aesKeyWrap192, .aesGCM192KeyWrap, .pbes2hmac384:
return SHA384.byteCount
case .aesKeyWrap256, .aesGCM192KeyWrap, .pbes2hmac512:
case .aesKeyWrap256, .aesGCM256KeyWrap, .pbes2hmac512:
return SHA512.byteCount
default:
return 0
Expand Down
2 changes: 1 addition & 1 deletion Sources/JWSETKit/Cryptography/KeyGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ extension JSONWebSignatureAlgorithm {
return P521.Signing.PrivateKey()
case .rsaSignaturePKCS1v15SHA256, .rsaSignaturePSSSHA256,
.rsaSignaturePKCS1v15SHA384, .rsaSignaturePSSSHA384,
.rsaSignaturePKCS1v15SHA512, .rsaSignaturePKCS1v15SHA512:
.rsaSignaturePKCS1v15SHA512, .rsaSignaturePSSSHA512:
return try _RSA.Signing.PrivateKey(keySize: .bits2048)
default:
throw JSONWebKeyError.unknownAlgorithm
Expand Down
1 change: 0 additions & 1 deletion Sources/JWSETKit/Cryptography/Symmetric/AES-CBC-HMAC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ public struct JSONWebKeyAESCBCHMAC: MutableJSONWebKey, JSONWebSealingKey, Sendab
}

public func seal<D, IV, AAD, JWA>(_ data: D, iv: IV?, authenticating: AAD?, using _: JWA) throws -> SealedData where D: DataProtocol, IV: DataProtocol, AAD: DataProtocol, JWA: JSONWebAlgorithm {
var generator = SystemRandomNumberGenerator()
let iv = iv.map { Data($0) } ?? SymmetricKey(size: .init(bitCount: ivLength * 8)).data
guard iv.count == ivLength else {
throw CryptoKitError.incorrectParameterSize
Expand Down
95 changes: 95 additions & 0 deletions Sources/JWSETKit/Cryptography/Symmetric/PBKDF2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// PBKDF2.swift
//
//
// Created by Amir Abbas Mousavian on 10/11/23.
//

import Foundation
#if canImport(CryptoKit)
import CryptoKit
#else
import Crypto
import CryptoSwift
#endif
#if canImport(CommonCrypto)
import CommonCrypto
#endif

extension SymmetricKey {
public static func pbkdf2<PD, SD, H>(
pbkdf2Password password: PD, salt: SD, hashFunction: H.Type, iterations: Int
) throws -> SymmetricKey where PD: DataProtocol, SD: DataProtocol, H: HashFunction {
#if canImport(CommonCrypto)
let hash: CCPseudoRandomAlgorithm

switch hashFunction.Digest.byteCount {
case SHA256.byteCount:
hash = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256)
case SHA384.byteCount:
hash = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA384)
case SHA512.byteCount:
hash = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512)
case Insecure.SHA1.byteCount:
hash = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1)
default:
throw CryptoKitError.incorrectKeySize
}

var derivedKeyData = Data(repeating: 0, count: hashFunction.Digest.byteCount)
let derivedCount = derivedKeyData.count

let derivationStatus: OSStatus = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
Data(salt).withUnsafeBytes { saltBytes in
Data(password).withUnsafeBytes {
let saltBytes = saltBytes.bindMemory(to: UInt8.self).baseAddress
let derivedKeyRawBytes = derivedKeyBytes.bindMemory(to: UInt8.self).baseAddress
let passwordBytes = $0.bindMemory(to: UInt8.self).baseAddress
return CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
passwordBytes,
password.count,
saltBytes,
salt.count,
hash,
UInt32(iterations),
derivedKeyRawBytes,
derivedCount
)
}
}
}

switch Int(derivationStatus) {
case kCCSuccess:
return .init(data: derivedKeyData)
case kCCParamError:
throw CryptoKitError.incorrectParameterSize
case kCCBufferTooSmall, kCCMemoryFailure, kCCAlignmentError,
kCCDecodeError, kCCUnimplemented, kCCOverflow,
kCCRNGFailure, kCCUnspecifiedError, kCCCallSequenceError:
throw CryptoKitError.underlyingCoreCryptoError(error: Int32(derivationStatus))
case kCCKeySizeError, kCCInvalidKey:
throw CryptoKitError.incorrectKeySize
default:
throw CryptoKitError.incorrectKeySize
}
#else
let variant: CryptoSwift.HMAC.Variant
switch hashFunction.Digest.byteCount {
case SHA256.byteCount:
variant = .sha2(.sha256)
case SHA384.byteCount:
variant = .sha2(.sha384)
case SHA512.byteCount:
variant = .sha2(.sha512)
case Insecure.SHA1.byteCount:
variant = .sha1
default:
throw CryptoKitError.incorrectKeySize
}
let key = try PKCS5.PBKDF2(password: [UInt8](password), salt: [UInt8](salt), iterations: iterations, variant: variant).calculate()
return .init(data: key)
#endif
}
}
34 changes: 32 additions & 2 deletions Sources/JWSETKit/Entities/JWE/JWE.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Crypto

/// The JWE cryptographic mechanisms encrypt and provide integrity protection
/// for an arbitrary sequence of octets.
public struct JSONWebEncryption: Hashable {
public struct JSONWebEncryption: Hashable, Sendable {
/// Contains JWE Protected Header and JWE Shared Unprotected Header.
public var header: JSONWebEncryptionHeader

Expand Down Expand Up @@ -144,7 +144,7 @@ public struct JSONWebEncryption: Hashable {
} else if data.starts(with: Data("{".utf8)) {
self = try JSONDecoder().decode(JSONWebEncryption.self, from: Data(data))
} else {
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Invalid JWS."))
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Invalid JWE."))
}
}

Expand Down Expand Up @@ -201,3 +201,33 @@ public struct JSONWebEncryption: Hashable {
return try cek.open(sealed, authenticating: authenticating, using: contentEncAlgorithm)
}
}

extension String {
public init(jwe: JSONWebEncryption) throws {
self = try String(String(decoding: JSONEncoder().encode(jwe), as: UTF8.self).dropFirst().dropLast())
}
}

extension JSONWebEncryption: LosslessStringConvertible, CustomDebugStringConvertible {
public init?(_ description: String) {
guard let jws = try? JSONWebEncryption(from: description) else {
return nil
}
self = jws
}

public var description: String {
(try? String(jwe: self)) ?? ""
}

public var debugDescription: String {
"""
Protected Header: \(header.protected.value)
Unprotected Header: \(String(describing: header.unprotected))
Recipients: \(recipients)
IV: \(String(decoding: sealed.iv.urlBase64EncodedData(), as: UTF8.self))
CipherText: \(String(decoding: sealed.ciphertext.urlBase64EncodedData(), as: UTF8.self))
Tag: \(String(decoding: sealed.tag.urlBase64EncodedData(), as: UTF8.self))
"""
}
}
4 changes: 2 additions & 2 deletions Sources/JWSETKit/Entities/JWE/JWECodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
import Foundation

/// JWSs use one of two serializations: the JWE Compact Serialization or the JWEJSON Serialization.
///
///
/// Applications using this specification need to specify what serialization and serialization features are
/// used for that application.
///
///
/// To change the representation of JWE during encoding to flattened JSON:
/// ```swift
/// do {
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 @@ -13,7 +13,7 @@ import Crypto
#endif

/// Represents a signature or MAC over the JWS Payload and the JWS Protected Header.
public struct JSONWebEncryptionHeader: Hashable, Codable {
public struct JSONWebEncryptionHeader: Hashable, Sendable, Codable {
enum CodingKeys: CodingKey {
case protected
case unprotected
Expand Down
2 changes: 1 addition & 1 deletion Sources/JWSETKit/Entities/JWE/JWERecipient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public struct JSONWebEncryptionRecipient: Hashable, Codable {
public struct JSONWebEncryptionRecipient: Hashable, Sendable, Codable {
enum CodingKeys: String, CodingKey {
case header
case encrypedKey = "encrypted_key"
Expand Down

0 comments on commit 14bbe92

Please sign in to comment.