Skip to content

Commit

Permalink
feat(jwe): add pbes2 variants for jwe
Browse files Browse the repository at this point in the history
  • Loading branch information
beatt83 committed Jan 21, 2024
1 parent 29f6120 commit c064caa
Show file tree
Hide file tree
Showing 40 changed files with 1,473 additions and 614 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ This library provides comprehensive support for the Jose suite of standards, inc
| A128GCMKW |:white_check_mark:|
| A192GCMKW |:white_check_mark:|
| A256GCMKW |:white_check_mark:|
| PBES2-HS256+A128KW | |
| PBES2-HS384+A192KW | |
| PBES2-HS512+A256KW | |
| PBES2-HS256+A128KW |:white_check_mark:|
| PBES2-HS384+A192KW |:white_check_mark:|
| PBES2-HS512+A256KW |:white_check_mark:|

</td><td valign="top">

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,18 @@

import Foundation

struct SecureRandom {
static func secureRandomData(count: Int) throws -> Data {
/// `SecureRandom` provides functionalities to generate cryptographically secure random data.
public struct SecureRandom {

/// Generates cryptographically secure random data of a specified size.
///
/// This method uses the system's secure random number generator to produce random data, which is suitable for cryptographic operations such as key generation, nonces, or any other use where strong randomness is required.
///
/// - Parameter count: The number of random bytes to generate.
/// - Returns: A `Data` object containing the generated random bytes.
/// - Throws: `CryptoError.securityLayerError` if the random number generation fails. This includes an error status code for debugging purposes.
/// - Note: The function relies on `SecRandomCopyBytes` from Apple's Security framework, ensuring high-quality randomness.
public static func secureRandomData(count: Int) throws -> Data {
var bytes = [Int8](repeating: 0, count: count)

let status = SecRandomCopyBytes(
Expand Down
2 changes: 1 addition & 1 deletion Sources/JSONWebAlgorithms/JWARegisteredFieldsHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@ public protocol JWARegisteredFieldsHeader: Codable {

/// The iteration count for the PBES2 salt input, determining how many times the password is hashed during the key derivation process.
/// Higher counts provide better security but require more computational resources.
var pbes2SaltCount: Data? { get set }
var pbes2SaltCount: Int? { get set }
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ import Foundation
import Tools

extension ECDH1PU: KeyDerivation {
func deriveKey(
key: Data,
keyLengthInBits: Int,
algorithmId: Data,
partyUInfo: Data,
partyVInfo: Data,
tag: Data,
other: [String : Data]
) throws -> Data {

func deriveKey(arguments: [KeyDerivationArguments]) throws -> Data {
guard let key = arguments.key else {
throw CryptoError.missingArguments(["key"])
}
let algorithmId = arguments.algorithmId ?? .init()
let partyUInfo = arguments.partyUInfo ?? .init()
let partyVInfo = arguments.partyVInfo ?? .init()
let tag = arguments.tag ?? .init()
let keyLengthInBits = arguments.keyLengthInBits ?? 0

let algorithmIDData = UInt32(algorithmId.count).bigEndian.dataRepresentation + algorithmId
let partyUInfoData = UInt32(partyUInfo.count).bigEndian.dataRepresentation + partyUInfo
let partyVInfoData = UInt32(partyVInfo.count).bigEndian.dataRepresentation + partyVInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ import Foundation
import Tools

extension ECDHES: KeyDerivation {
func deriveKey(
key: Data,
keyLengthInBits: Int,
algorithmId: Data,
partyUInfo: Data,
partyVInfo: Data,
tag: Data,
other: [String : Data]
) throws -> Data {

func deriveKey(arguments: [KeyDerivationArguments]) throws -> Data {
guard let key = arguments.key else {
throw CryptoError.missingArguments(["key"])
}
let algorithmId = arguments.algorithmId ?? .init()
let partyUInfo = arguments.partyUInfo ?? .init()
let partyVInfo = arguments.partyVInfo ?? .init()
let keyLengthInBits = arguments.keyLengthInBits ?? 0

let algorithmIDData = UInt32(algorithmId.count).bigEndian.dataRepresentation + algorithmId
let partyUInfoData = UInt32(partyUInfo.count).bigEndian.dataRepresentation + partyUInfo
let partyVInfoData = UInt32(partyVInfo.count).bigEndian.dataRepresentation + partyVInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,60 +15,134 @@
*/

import Foundation
import JSONWebKey

/// Enumerates possible arguments for key derivation processes.
public enum KeyDerivationArguments {
/// Specifies the initial key material for the key derivation.
case key(Data)

/// Specifies the length in bits of the key to be derived.
case keyLengthInBits(Int)

/// Specifies the algorithm identifier data used in key derivation.
case algorithmId(Data)

/// Contains data specific to party U involved in the key agreement.
case partyUInfo(Data)

/// Contains data specific to party V involved in the key agreement.
case partyVInfo(Data)

/// A tag that can be used to ensure the integrity of the derived key.
case tag(Data)

/// Password or passphrase used in the derivation process.
case password(Data)

/// Salt input for the derivation process.
case saltInput(Data)

/// The iteration count for key derivation algorithms that use a salt.
case saltCount(Int)

/// Allows for custom data to be included, identified by a key.
case customData(key: String, value: Data)

/// Allows for a custom JSON Web Key (JWK) to be included, identified by a key.
case customJWK(key: String, value: JWK)
}

/// `KeyDerivation` is a protocol defining functionality for deriving cryptographic keys.
/// It is used in scenarios where keys need to be derived from existing material, such as passwords or shared secrets.
public protocol KeyDerivation {
/// Derives a key from the given input parameters using a specified key derivation function.
/// - Parameters:
/// - key: The input key material used for derivation.
/// - keyLengthInBits: The desired length of the derived key in bits.
/// - algorithmId: An identifier for the key derivation algorithm.
/// - partyUInfo: Data specific to one party involved in the key derivation (usually the initiator).
/// - partyVInfo: Data specific to the other party involved in the key derivation (usually the responder).
/// - tag: A tag used in the key derivation process, providing additional context or information.
/// - other: A dictionary containing other relevant data for key derivation.
/// - Returns: The derived key as `Data`.
/// - Throws: An error if key derivation fails. This could be due to incorrect input parameters, unsupported algorithm specifications, or other cryptographic issues.
func deriveKey(
key: Data,
keyLengthInBits: Int,
algorithmId: Data,
partyUInfo: Data,
partyVInfo: Data,
tag: Data,
other: [String: Data]
) throws -> Data
/// Derives a cryptographic key based on the provided arguments.
///
/// - Parameter arguments: An array of `KeyDerivationArguments` that specify the parameters for the key derivation.
/// - Returns: The derived key as a `Data` object.
/// - Throws: An error if the key derivation process fails.
func deriveKey(arguments: [KeyDerivationArguments]) throws -> Data
}

extension KeyDerivation {
/// Provides a default implementation of `deriveKey` with optional parameters set to their default values.
/// - Parameters:
/// - key: The input key material used for derivation.
/// - keyLengthInBits: The desired length of the derived key in bits.
/// - algorithmId: An optional identifier for the key derivation algorithm (default is empty).
/// - partyUInfo: Optional data specific to one party involved in the key derivation (default is empty).
/// - partyVInfo: Optional data specific to the other party involved in the key derivation (default is empty).
/// - tag: An optional tag used in the key derivation process (default is empty).
/// - other: An optional dictionary containing other relevant data for key derivation (default is empty).
/// - Returns: The derived key as `Data`.
/// - Throws: An error if key derivation fails.
public func deriveKey(
key: Data,
keyLengthInBits: Int,
algorithmId: Data = Data(),
partyUInfo: Data = Data(),
partyVInfo: Data = Data(),
tag: Data = Data(),
other: [String: Data] = [:]
) throws -> Data {
try self.deriveKey(
key: key,
keyLengthInBits: keyLengthInBits,
algorithmId: algorithmId,
partyUInfo: partyUInfo,
partyVInfo: partyVInfo,
tag: tag,
other: other
)
extension Array where Element == KeyDerivationArguments {
var key: Data? {
return self.compactMap {
if case .key(let data) = $0 {
return data
}
return nil
}.first
}

var keyLengthInBits: Int? {
return self.compactMap {
if case .keyLengthInBits(let data) = $0 {
return data
}
return nil
}.first
}

var algorithmId: Data? {
return self.compactMap {
if case .algorithmId(let data) = $0 {
return data
}
return nil
}.first
}

var partyUInfo: Data? {
return self.compactMap {
if case .partyUInfo(let data) = $0 {
return data
}
return nil
}.first
}

var partyVInfo: Data? {
return self.compactMap {
if case .partyVInfo(let data) = $0 {
return data
}
return nil
}.first
}

var tag: Data? {
return self.compactMap {
if case .tag(let data) = $0 {
return data
}
return nil
}.first
}

var password: Data? {
return self.compactMap {
if case .password(let data) = $0 {
return data
}
return nil
}.first
}

var saltInput: Data? {
return self.compactMap {
if case .saltInput(let data) = $0 {
return data
}
return nil
}.first
}

var saltCount: Int? {
return self.compactMap {
if case .saltCount(let data) = $0 {
return data
}
return nil
}.first
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,7 @@
import Foundation

struct MockKeyDerivation: KeyDerivation {
func deriveKey(
key: Data,
keyLengthInBits: Int,
algorithmId: Data,
partyUInfo: Data,
partyVInfo: Data,
suppPubInfo: Data,
suppPrivInfo: Data,
tag: Data,
other: [String : Data]
) throws -> Data {
func deriveKey(arguments: [KeyDerivationArguments]) throws -> Data {
Data()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 Foundation
import JSONWebKey

struct PBE2_SHA256_A128KW: KeyDerivation {

func deriveKey(arguments: [KeyDerivationArguments]) throws -> Data {
guard
let password = arguments.password
else {
throw CryptoError.missingArguments(["password"])
}
guard
let salt = arguments.saltInput,
let count = arguments.saltCount
else {
throw CryptoError.missingPBS2SaltInputOrCount
}

return try PBES2SHAKeyDerivation.derive(
password: password,
saltInput: salt,
saltCount: count,
variant: .sha2(.sha256)
).derivedKey
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 Foundation
import JSONWebKey

struct PBE2_SHA384_A192KW: KeyDerivation {

func deriveKey(arguments: [KeyDerivationArguments]) throws -> Data {
guard
let password = arguments.password
else {
throw CryptoError.missingArguments(["password"])
}
guard
let salt = arguments.saltInput,
let count = arguments.saltCount
else {
throw CryptoError.missingPBS2SaltInputOrCount
}

return try PBES2SHAKeyDerivation.derive(
password: password,
saltInput: salt,
saltCount: count,
variant: .sha2(.sha384)
).derivedKey
}
}
Loading

0 comments on commit c064caa

Please sign in to comment.