Add support for password-encrypted, file-based import and export of verifiable credentials (#128)

Co-authored-by: Sydney Morton <symorton@microsoft.com>
This commit is contained in:
Stephen Higgins 2022-07-19 09:38:11 +01:00 коммит произвёл GitHub
Родитель c7ebcbbcb5
Коммит fa1a27768d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
65 изменённых файлов: 18612 добавлений и 178 удалений

Просмотреть файл

@ -7,6 +7,13 @@
objects = {
/* Begin PBXBuildFile section */
2F848B5B282D50D5005D3176 /* Pbkdf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B57282D50D5005D3176 /* Pbkdf.swift */; };
2F848B5C282D50D5005D3176 /* Aes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B58282D50D5005D3176 /* Aes.swift */; };
2F848B5D282D50D5005D3176 /* HmacSha2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B59282D50D5005D3176 /* HmacSha2.swift */; };
2F848B5E282D50D5005D3176 /* HmacSha2AesCbc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B5A282D50D5005D3176 /* HmacSha2AesCbc.swift */; };
2F848B60282D514F005D3176 /* EphemeralSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B5F282D514F005D3176 /* EphemeralSecret.swift */; };
2F848B63282D51D7005D3176 /* AesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B61282D51D7005D3176 /* AesTests.swift */; };
2F848B64282D51D7005D3176 /* PbkdfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B62282D51D7005D3176 /* PbkdfTests.swift */; };
551F3059252E378E0081D5E7 /* CryptoOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551F3058252E378E0081D5E7 /* CryptoOperations.swift */; };
553D4AAF280F443A00FD39A6 /* VCSDKConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553D4AAE280F443A00FD39A6 /* VCSDKConfigurable.swift */; };
5540903D25000F6A001246DB /* Signing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5540903C25000F6A001246DB /* Signing.swift */; };
@ -60,6 +67,13 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
2F848B57282D50D5005D3176 /* Pbkdf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pbkdf.swift; sourceTree = "<group>"; };
2F848B58282D50D5005D3176 /* Aes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Aes.swift; sourceTree = "<group>"; };
2F848B59282D50D5005D3176 /* HmacSha2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HmacSha2.swift; sourceTree = "<group>"; };
2F848B5A282D50D5005D3176 /* HmacSha2AesCbc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HmacSha2AesCbc.swift; sourceTree = "<group>"; };
2F848B5F282D514F005D3176 /* EphemeralSecret.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EphemeralSecret.swift; sourceTree = "<group>"; };
2F848B61282D51D7005D3176 /* AesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AesTests.swift; sourceTree = "<group>"; };
2F848B62282D51D7005D3176 /* PbkdfTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PbkdfTests.swift; sourceTree = "<group>"; };
551F3058252E378E0081D5E7 /* CryptoOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoOperations.swift; sourceTree = "<group>"; };
553D4AAE280F443A00FD39A6 /* VCSDKConfigurable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCSDKConfigurable.swift; sourceTree = "<group>"; };
5540903C25000F6A001246DB /* Signing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signing.swift; sourceTree = "<group>"; };
@ -131,6 +145,7 @@
928AAF6F24EEDC670029A8F9 /* Keys */ = {
isa = PBXGroup;
children = (
2F848B5F282D514F005D3176 /* EphemeralSecret.swift */,
92CDE80524E1ABEA00F95B5D /* Random32BytesSecret.swift */,
92CDE80724E1F7AF00F95B5D /* Secret.swift */,
928AAF7024EEDC9D0029A8F9 /* Secp256k1PublicKey.swift */,
@ -186,6 +201,8 @@
928AAF7224EEE0AB0029A8F9 /* Secp256k1PublicKeyTests.swift */,
92CDE7E724DE1A2000F95B5D /* KeychainSecretStoreTests.swift */,
92CDE80B24E213AE00F95B5D /* Random32BytesSecretTests.swift */,
2F848B61282D51D7005D3176 /* AesTests.swift */,
2F848B62282D51D7005D3176 /* PbkdfTests.swift */,
);
path = VCCryptoTests;
sourceTree = "<group>";
@ -202,6 +219,10 @@
92CDE7D024DCA69C00F95B5D /* Algo */ = {
isa = PBXGroup;
children = (
2F848B58282D50D5005D3176 /* Aes.swift */,
2F848B59282D50D5005D3176 /* HmacSha2.swift */,
2F848B5A282D50D5005D3176 /* HmacSha2AesCbc.swift */,
2F848B57282D50D5005D3176 /* Pbkdf.swift */,
92CDE7D124DCA6CF00F95B5D /* Secp256k1.swift */,
92CDE7D824DDBA0500F95B5D /* HmacSha512.swift */,
92CDE7DA24DDC65100F95B5D /* Sha512.swift */,
@ -409,13 +430,18 @@
92CDE7CB24DCA61800F95B5D /* KeychainSecretStore.swift in Sources */,
92CDE7DE24DDCB9500F95B5D /* Data+Hex.swift in Sources */,
92CDE80624E1ABEA00F95B5D /* Random32BytesSecret.swift in Sources */,
2F848B5B282D50D5005D3176 /* Pbkdf.swift in Sources */,
92CDE7D924DDBA0500F95B5D /* HmacSha512.swift in Sources */,
2F848B5D282D50D5005D3176 /* HmacSha2.swift in Sources */,
553D4AAF280F443A00FD39A6 /* VCSDKConfigurable.swift in Sources */,
55ADE29924F5752500D9990E /* Sha256.swift in Sources */,
92CDE80824E1F7AF00F95B5D /* Secret.swift in Sources */,
92CDE7D224DCA6CF00F95B5D /* Secp256k1.swift in Sources */,
2F848B5C282D50D5005D3176 /* Aes.swift in Sources */,
928AAF7124EEDC9D0029A8F9 /* Secp256k1PublicKey.swift in Sources */,
5540903D25000F6A001246DB /* Signing.swift in Sources */,
2F848B5E282D50D5005D3176 /* HmacSha2AesCbc.swift in Sources */,
2F848B60282D514F005D3176 /* EphemeralSecret.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -426,11 +452,13 @@
928AAF7324EEE0AB0029A8F9 /* Secp256k1PublicKeyTests.swift in Sources */,
92CDE7D724DCD07D00F95B5D /* Secp256k1Tests.swift in Sources */,
92CDE7D524DCCE3C00F95B5D /* SecretStoreMock.swift in Sources */,
2F848B63282D51D7005D3176 /* AesTests.swift in Sources */,
92CDE80A24E2047900F95B5D /* SecretMock.swift in Sources */,
92CDE80C24E213AE00F95B5D /* Random32BytesSecretTests.swift in Sources */,
92CDE7E224DDF1A900F95B5D /* HmacSha512Tests.swift in Sources */,
92CDE7E424DDF42A00F95B5D /* Utf8TestData.swift in Sources */,
92CDE7E024DDD24700F95B5D /* Sha512Tests.swift in Sources */,
2F848B64282D51D7005D3176 /* PbkdfTests.swift in Sources */,
55ADE29B24F5757100D9990E /* Sha256Tests.swift in Sources */,
92CDE7E824DE1A2000F95B5D /* KeychainSecretStoreTests.swift in Sources */,
);

Просмотреть файл

@ -0,0 +1,147 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
import CommonCrypto
enum AesError : Error {
case keyWrapError
case keyUnwrapError
case cryptoError(operation:CCOperation, status:CCCryptorStatus)
}
public struct Aes {
// AES processes inputs in blocks of 128 bits (16 bytes)
internal let blockSize = size_t(16)
private let keyWrapAlg = CCWrappingAlgorithm(kCCWRAPAES)
public init() {}
public func wrap(key: VCCryptoSecret, with kek: VCCryptoSecret) throws -> Data {
// Look for an early out
var wrappedSize: Int = 0
try key.withUnsafeBytes { (keyPtr: UnsafeRawBufferPointer) in
let keySize = keyPtr.bindMemory(to: UInt8.self).count
wrappedSize = CCSymmetricWrappedSize(keyWrapAlg, keySize)
}
var wrapped = Data(repeating: 0, count: wrappedSize)
try wrapped.withUnsafeMutableBytes({ (wrappedPtr: UnsafeMutableRawBufferPointer) in
let wrappedBytes = wrappedPtr.bindMemory(to: UInt8.self)
try key.withUnsafeBytes { (keyPtr: UnsafeRawBufferPointer) in
let keyBytes = keyPtr.bindMemory(to: UInt8.self)
try kek.withUnsafeBytes { (kekPtr: UnsafeRawBufferPointer) in
let kekBytes = kekPtr.bindMemory(to: UInt8.self)
let status = CCSymmetricKeyWrap(keyWrapAlg,
CCrfc3394_iv,
CCrfc3394_ivLen,
kekBytes.baseAddress,
kekBytes.count,
keyBytes.baseAddress,
keyBytes.count,
wrappedBytes.baseAddress,
&wrappedSize)
guard status == kCCSuccess else {
throw AesError.keyWrapError
}
}
}
})
return wrapped
}
public func unwrap(wrapped: Data, using kek: VCCryptoSecret) throws -> VCCryptoSecret {
var unwrappedSize = CCSymmetricUnwrappedSize(keyWrapAlg, wrapped.count)
var unwrapped = Data(repeating: 0, count: unwrappedSize)
defer {
unwrapped.withUnsafeMutableBytes { (unwrappedPtr) in
memset_s(unwrappedPtr.baseAddress, unwrappedSize, 0, unwrappedSize)
return
}
}
try unwrapped.withUnsafeMutableBytes { (unwrappedPtr: UnsafeMutableRawBufferPointer) in
let unwrappedBytes = unwrappedPtr.bindMemory(to: UInt8.self)
try wrapped.withUnsafeBytes { (wrappedPtr: UnsafeRawBufferPointer) in
let wrappedBytes = wrappedPtr.bindMemory(to: UInt8.self)
try kek.withUnsafeBytes { (kekPtr: UnsafeRawBufferPointer) in
let kekBytes = kekPtr.bindMemory(to: UInt8.self)
let status = CCSymmetricKeyUnwrap(keyWrapAlg,
CCrfc3394_iv,
CCrfc3394_ivLen,
kekBytes.baseAddress,
kekBytes.count,
wrappedBytes.baseAddress,
wrappedBytes.count,
unwrappedBytes.baseAddress,
&unwrappedSize)
guard status == kCCSuccess else {
throw AesError.keyUnwrapError
}
}
}
}
return EphemeralSecret(with: unwrapped)
}
public func encrypt(data: Data, with key: VCCryptoSecret, iv: Data) throws -> Data {
// If the input isn't perfectly aligned to the AES block size, then padding is required
let options = data.count % blockSize == 0 ? CCOptions(0) : CCOptions(kCCOptionPKCS7Padding)
return try self.apply(operation: CCOperation(kCCEncrypt), withOptions: options, to: data, using: key, iv: iv)
}
public func decrypt(data: Data, with key: VCCryptoSecret, iv: Data) throws -> Data {
return try self.apply(operation: CCOperation(kCCDecrypt), withOptions: CCOptions(kCCOptionPKCS7Padding), to: data, using: key, iv: iv)
}
private func apply(operation: CCOperation, withOptions options: CCOptions, to data: Data, using key: VCCryptoSecret, iv: Data) throws -> Data {
// Allocate the output buffer
var outputSize = size_t(data.count)
let modulo = data.count % blockSize
if modulo > 0 {
outputSize += size_t(blockSize - modulo)
}
var output = [UInt8](repeating: 0, count: outputSize)
try key.withUnsafeBytes { (keyPtr: UnsafeRawBufferPointer) in
let keyBytes = keyPtr.bindMemory(to: UInt8.self)
try iv.withUnsafeBytes { (ivPtr: UnsafeRawBufferPointer) in
let ivBytes = ivPtr.bindMemory(to: UInt8.self)
try data.withUnsafeBytes { (dataPtr: UnsafeRawBufferPointer) in
let dataBytes = dataPtr.bindMemory(to: UInt8.self)
let status = CCCrypt(operation,
CCAlgorithm(kCCAlgorithmAES),
options,
keyBytes.baseAddress,
keyBytes.count,
ivBytes.baseAddress,
dataBytes.baseAddress,
dataBytes.count,
&output,
outputSize,
&outputSize)
if status == kCCSuccess {
output.removeSubrange(outputSize..<output.count)
} else {
throw AesError.cryptoError(operation:operation, status:status)
}
}
}
}
return Data(output)
}
}

Просмотреть файл

@ -0,0 +1,69 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
import CommonCrypto
enum HmacSha2Error: Error {
case invalidMessage
case invalidAlgorithm
}
public struct HmacSha2 {
private let macSize: Int
private let algorithm: UInt32
public init(algorithm: UInt32) throws {
switch (algorithm) {
case UInt32(kCCHmacAlgSHA256):
self.macSize = Int(CC_SHA256_DIGEST_LENGTH)
case UInt32(kCCHmacAlgSHA384):
self.macSize = Int(CC_SHA384_DIGEST_LENGTH)
case UInt32(kCCHmacAlgSHA512):
self.macSize = Int(CC_SHA512_DIGEST_LENGTH)
default:
throw HmacSha2Error.invalidAlgorithm
}
self.algorithm = algorithm
}
/// Authenticate a message
/// - Parameters:
/// - message: The message to authenticate
/// - secret: The secret used for authentication
/// - Returns: The authentication code for the message
public func authenticate(message: Data, with secret: VCCryptoSecret) throws -> Data {
// Look for an early out
guard message.count > 0 else { throw HmacSha2Error.invalidMessage }
// Apply
let ccHmacAlg = self.algorithm
var mac: [UInt8] = [UInt8](repeating: 0, count:self.macSize)
try message.withUnsafeBytes { (messagePtr: UnsafeRawBufferPointer) in
let messageBytes = messagePtr.bindMemory(to: UInt8.self)
try secret.withUnsafeBytes { (secretPtr: UnsafeRawBufferPointer) in
let secretBytes = secretPtr.bindMemory(to: UInt8.self)
CCHmac(ccHmacAlg, secretBytes.baseAddress, secretBytes.count, messageBytes.baseAddress, messageBytes.count, &mac)
}
}
return Data(mac)
}
/// Verify that the authentication code is valid
/// - Parameters:
/// - mac: The authentication code
/// - message: The message
/// - secret: The secret used
/// - Returns: True if the authentication code is valid
public func validate(_ mac: Data, authenticating message: Data, with secret: VCCryptoSecret) throws -> Bool {
let authentication = try self.authenticate(message: message, with: secret)
return mac == authentication
}
}

Просмотреть файл

@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
import CommonCrypto
enum HmacSha2AesCbcError: Error {
case unsupportedMethod(name: String?)
case invalidAuthenticationCode
}
public struct HmacSha2AesCbc {
private let aes: Aes
private let hmac: HmacSha2
public init(methodName: String) throws {
let (hmacAlg, _) = try HmacSha2AesCbc.props(for: methodName)
self.hmac = try HmacSha2(algorithm: UInt32(hmacAlg))
self.aes = Aes()
}
public func encrypt(plainText: Data, using aad: Data, iv: Data, with keys: (mac: EphemeralSecret, enc: EphemeralSecret)) throws -> (Data, Data) {
let cipherText = try aes.encrypt(data: plainText, with: keys.enc, iv: iv)
let mac = try self.authenticate(aad: aad, iv: iv, cipherText: cipherText, with: keys.mac)
return (cipherText, mac)
}
public func decrypt(_ input: (cipherText: Data, mac: Data), using aad: Data, iv: Data, with keys: (mac: EphemeralSecret, enc: EphemeralSecret)) throws -> Data {
// Validate the message authentication code
let mac = try self.authenticate(aad: aad, iv: iv, cipherText: input.cipherText, with: keys.mac)
if input.mac == mac {
return try aes.decrypt(data: input.cipherText, with: keys.enc, iv: iv)
}
throw HmacSha2AesCbcError.invalidAuthenticationCode
}
public static func props(for methodName: String) throws -> (Int, Int) {
let result: (hmacAlg: Int, keySize: Int)
switch methodName {
case "A128CBC-HS256":
result.hmacAlg = kCCHmacAlgSHA256
result.keySize = 16
case "A192CBC-HS384":
result.hmacAlg = kCCHmacAlgSHA384
result.keySize = 24
case "A256CBC-HS512":
result.hmacAlg = kCCHmacAlgSHA512
result.keySize = 32
default:
throw HmacSha2AesCbcError.unsupportedMethod(name: methodName)
}
return result
}
private func authenticate(aad: Data, iv: Data, cipherText: Data, with key: EphemeralSecret) throws -> Data {
// Compose the message from which to generate the MAC
let bitCount = UInt64(8 * aad.count).bigEndian
let al = withUnsafeBytes(of: bitCount, Array.init)
var message: Data = aad
message.append(iv)
message.append(cipherText)
message.append(contentsOf: al)
// Apply, and truncate
let mac = try hmac.authenticate(message: message, with: key)
return mac.prefix(key.value.count)
}
}

Просмотреть файл

@ -6,12 +6,10 @@
import Foundation
import CommonCrypto
enum HmacSha512Error: Error {
case invalidMessage
case invalidSecret
}
@available(*, deprecated, message: "Superceded by HmacSha2")
public struct HmacSha512 {
public init() { }
/// Authenticate a message
/// - Parameters:
@ -19,17 +17,8 @@ public struct HmacSha512 {
/// - secret: The secret used for authentication
/// - Returns: The authentication code for the message
public func authenticate(message: Data, withSecret secret: VCCryptoSecret) throws -> Data {
guard message.count > 0 else { throw HmacSha512Error.invalidMessage }
guard secret is Secret else { throw HmacSha512Error.invalidSecret }
var messageAuthCode : [UInt8] = [UInt8](repeating: 0, count:Int(CC_SHA512_DIGEST_LENGTH))
try (secret as! Secret).withUnsafeBytes { (secretPtr) in
message.withUnsafeBytes {
CCHmac(UInt32(kCCHmacAlgSHA512), secretPtr.bindMemory(to: UInt8.self).baseAddress!, secretPtr.count, $0.baseAddress, message.count, &messageAuthCode)
}
}
return Data(messageAuthCode)
let hmac = try HmacSha2(algorithm: UInt32(kCCHmacAlgSHA512))
return try hmac.authenticate(message: message, with: secret)
}
/// Verify that the authentication code is valid
@ -39,7 +28,7 @@ public struct HmacSha512 {
/// - secret: The secret used
/// - Returns: True if the authentication code is valid
public func isValidAuthenticationCode(_ mac: Data, authenticating message: Data, withSecret secret: VCCryptoSecret) throws -> Bool {
let authCode = try self.authenticate(message: message, withSecret: secret)
return mac == authCode
let hmac = try HmacSha2(algorithm: UInt32(kCCHmacAlgSHA512))
return try hmac.validate(mac, authenticating: message, with: secret)
}
}

Просмотреть файл

@ -0,0 +1,73 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
import CommonCrypto
enum PbkdfError : Error {
case keyDerivationError
case invalidAlgorithmNameError(name:String)
}
public struct Pbkdf {
public init() { }
public func derive(from password: String, withSaltInput p2s: Data, forAlgorithm algorithm: String, rounds: UInt32) throws -> VCCryptoSecret {
// Determine the algorithm, and key size
let size: size_t, pseudoRandomAlgorithm: CCPseudoRandomAlgorithm
switch (algorithm) {
case "PBES2-HS256+A128KW":
pseudoRandomAlgorithm = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256)
size = 16
case "PBES2-HS384+A192KW":
pseudoRandomAlgorithm = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA384)
size = 24
case "PBES2-HS512+A256KW":
pseudoRandomAlgorithm = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512)
size = 32
default:
throw PbkdfError.invalidAlgorithmNameError(name:algorithm)
}
// Construct the salt, c.f. https://datatracker.ietf.org/doc/html/rfc7517#appendix-C.4
guard var salt = algorithm.data(using: .utf8) else {
throw PbkdfError.invalidAlgorithmNameError(name:algorithm)
}
salt.append(UInt8(0))
salt.append(p2s)
// Derive the key
var derived = Data(count: size)
defer {
derived.withUnsafeMutableBytes { (derivedPtr) in
memset_s(derivedPtr.baseAddress, size, 0, size)
return
}
}
try derived.withUnsafeMutableBytes { (derivedPtr: UnsafeMutableRawBufferPointer) in
let derivedBytes = derivedPtr.bindMemory(to: UInt8.self)
try salt.withUnsafeBytes { (saltPtr: UnsafeRawBufferPointer) in
let saltBytes = saltPtr.bindMemory(to: UInt8.self)
let status = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2),
password,
password.utf8.count,
saltBytes.baseAddress,
saltBytes.count,
pseudoRandomAlgorithm,
rounds,
derivedBytes.baseAddress,
derivedBytes.count)
guard status == kCCSuccess else {
throw PbkdfError.keyDerivationError
}
}
}
return EphemeralSecret(with: derived)
}
}

Просмотреть файл

@ -13,7 +13,6 @@ enum Secp256k1Error: Error {
case invalidSignature
case invalidPublicKey
case publicKeyCreationFailure
case invalidSecret
}
public struct Secp256k1: Signing {
@ -28,7 +27,6 @@ public struct Secp256k1: Signing {
public func sign(messageHash: Data, withSecret secret: VCCryptoSecret) throws -> Data {
// Validate params
guard secret is Secret else { throw Secp256k1Error.invalidSecret }
guard messageHash.count == 32 else { throw Secp256k1Error.invalidMessageHash }
// Create the context and signature data structure
@ -42,7 +40,7 @@ public struct Secp256k1: Signing {
}
// Sign the message
try (secret as! Secret).withUnsafeBytes { (secretPtr) in
try secret.withUnsafeBytes { (secretPtr) in
guard secp256k1_ec_seckey_verify(context!, secretPtr.bindMemory(to: UInt8.self).baseAddress.unsafelyUnwrapped) > 0 else { throw Secp256k1Error.invalidSecretKey }
try messageHash.withUnsafeBytes { (msgPtr) in
@ -125,15 +123,35 @@ public struct Secp256k1: Signing {
/// - Parameter secret: The Secret used to generate the public key
/// - Returns: The public key
public func createPublicKey(forSecret secret: VCCryptoSecret) throws -> Secp256k1PublicKey {
// Validate params
guard secret is Secret else { throw Secp256k1Error.invalidSecret }
let (_, publicKey) = try self.createKeyPair(forSecret: secret)
return publicKey
}
/// Create a key pair from a secret
/// - Parameter secret: The Secret used to generate the public key
/// - Returns: The key pair
public func createKeyPair(forSecret secret: VCCryptoSecret) throws -> (EphemeralSecret, Secp256k1PublicKey)
{
// Get out the private key data
let privateKey = try EphemeralSecret(with: secret)
// Return with the public key
return (privateKey, try self.createPublicKey(forPrivateKey: privateKey))
}
/// Create a public key from a private key
/// - Parameter secret: The Secret used to generate the public key
/// - Returns: The public key
public func createPublicKey(forPrivateKey privateKey:EphemeralSecret) throws -> Secp256k1PublicKey {
// Create the context and public key data structure
let context = secp256k1_context_create(UInt32(SECP256K1_CONTEXT_SIGN))!
defer { secp256k1_context_destroy(context) }
let pubkey = UnsafeMutablePointer<secp256k1_pubkey>.allocate(capacity: 1)
// Create the public key
try (secret as! Secret).withUnsafeBytes { (secretPtr) in
// Populate it
try privateKey.withUnsafeBytes { (secretPtr) in
let result = secp256k1_ec_pubkey_create(
context,
pubkey,
@ -156,6 +174,7 @@ public struct Secp256k1: Signing {
return
}
// Wrap it all up
return Secp256k1PublicKey(uncompressedPublicKey: publicKey)!
}
}

Просмотреть файл

@ -6,6 +6,9 @@
public protocol CryptoOperating {
func generateKey() throws -> VCCryptoSecret
func retrieveKeyFromStorage(withId id: UUID) -> VCCryptoSecret
func save(key: Data, withId id: UUID) throws
func deleteKey(withId id: UUID) throws
func getKey(withId id: UUID) throws -> Data
}
public struct CryptoOperations: CryptoOperating {
@ -33,4 +36,45 @@ public struct CryptoOperations: CryptoOperating {
let accessGroup = sdkConfiguration.accessGroupIdentifier
return Random32BytesSecret(withStore: secretStore, andId: id, inAccessGroup: accessGroup)
}
public func save(key: Data, withId id: UUID) throws {
// Take a copy of the key to let the store dispose of it
var data = Data()
data.append(key)
// Format the item type code
let itemTypeCode = String(format: "r%02dB", data.count)
// Store down
try secretStore.saveSecret(id: id,
itemTypeCode: itemTypeCode,
accessGroup: sdkConfiguration.accessGroupIdentifier,
value: &data)
}
public func deleteKey(withId id: UUID) throws {
let itemTypeCode = Random32BytesSecret.itemTypeCode
let accessGroup = sdkConfiguration.accessGroupIdentifier
do {
let _ = try secretStore.getSecret(id: id,
itemTypeCode: itemTypeCode,
accessGroup: accessGroup)
// If we get here the key exists, so we can delete it
try secretStore.deleteSecret(id: id, itemTypeCode: itemTypeCode, accessGroup: accessGroup)
}
catch SecretStoringError.itemNotFound {
/* There's no key so nothing to delete */
}
}
public func getKey(withId id: UUID) throws -> Data {
return try secretStore.getSecret(id: id,
itemTypeCode: Random32BytesSecret.itemTypeCode,
accessGroup: sdkConfiguration.accessGroupIdentifier)
}
}

Просмотреть файл

@ -5,17 +5,7 @@
import Foundation
public enum KeychainStoreError: Error {
case deleteFromStoreError(status: OSStatus)
case saveToStoreError(status: Int32)
case itemNotFound
case readFromStoreError(status: OSStatus)
case invalidItemInStore
case itemAlreadyInStore
case invalidType
}
struct KeychainSecretStore : SecretStoring {
struct KeychainSecretStore: SecretStoring {
private let vcService = "com.microsoft.vcCrypto"
@ -48,9 +38,19 @@ struct KeychainSecretStore : SecretStoring {
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status != errSecItemNotFound else { throw KeychainStoreError.itemNotFound }
guard status == errSecSuccess else { throw KeychainStoreError.readFromStoreError(status: status as OSStatus) }
guard var value = item as? Data else { throw KeychainStoreError.invalidItemInStore }
var statusMessage: String? = nil
if #available(iOS 11.3, *) {
statusMessage = SecCopyErrorMessageString(status, nil) as? String
}
guard status != errSecItemNotFound else { throw SecretStoringError.itemNotFound }
guard status == errSecSuccess else {
throw SecretStoringError.readFromStoreError(status: status as OSStatus, message: statusMessage)
}
guard var value = item as? Data else { throw SecretStoringError.invalidItemInStore }
defer {
let secretSize = value.count
value.withUnsafeMutableBytes { (secretPtr) in
@ -78,7 +78,7 @@ struct KeychainSecretStore : SecretStoring {
}
}
guard itemTypeCode.count == 4 else { throw KeychainStoreError.invalidType }
guard itemTypeCode.count == 4 else { throw SecretStoringError.invalidType }
// kSecAttrAccount is used to store the secret Id so that we can look it up later
// kSecAttrService is always set to vcService to enable us to lookup all our secrets later if needed
@ -103,8 +103,14 @@ struct KeychainSecretStore : SecretStoring {
}
let status = SecItemAdd(query as CFDictionary, nil)
var statusMessage: String? = nil
if #available(iOS 11.3, *) {
statusMessage = SecCopyErrorMessageString(status, nil) as? String
}
guard status == errSecSuccess else {
throw KeychainStoreError.saveToStoreError(status: status)
throw SecretStoringError.saveToStoreError(status: status, message: statusMessage)
}
}
@ -116,7 +122,7 @@ struct KeychainSecretStore : SecretStoring {
/// - accessGroup: The access group of the secret.
func deleteSecret(id: UUID, itemTypeCode: String, accessGroup: String? = nil) throws {
guard itemTypeCode.count == 4 else { throw KeychainStoreError.invalidType }
guard itemTypeCode.count == 4 else { throw SecretStoringError.invalidType }
// kSecAttrAccount is used to store the secret Id so that we can look it up later
// kSecAttrService is always set to vcService to enable us to lookup all our secrets later if needed
@ -133,8 +139,14 @@ struct KeychainSecretStore : SecretStoring {
}
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess else {
throw KeychainStoreError.deleteFromStoreError(status: status)
var statusMessage: String? = nil
if #available(iOS 11.3, *) {
statusMessage = SecCopyErrorMessageString(status, nil) as? String
}
guard status == errSecSuccess || status == errSecItemNotFound else {
throw SecretStoringError.deleteFromStoreError(status: status, message: statusMessage)
}
}
}

Просмотреть файл

@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
/// An ephemeral secret is one that vanishes into thin air when you are not looking at it
/// (i.e. when it goes out of scope)
public final class EphemeralSecret: Secret {
private enum EphemeralSecretError: Error {
case secRandomCopyBytesFailed(status: OSStatus)
}
static var itemTypeCode: String = ""
public var accessGroup: String? = nil
public private(set) var id = UUID()
public private(set) var value: Data
public init(with data: Data, id: UUID? = nil, accessGroup: String? = nil) {
// Since we practively zero out the contents of the `value` buffer
// on deallocation, we make a separate copy of the input here
value = Data()
value.append(data)
if let uuid = id {
self.id = uuid
}
self.accessGroup = accessGroup
}
public init(size: Int = 32) throws {
value = Data(count:size)
let result = value.withUnsafeMutableBytes { (secretPtr) in
SecRandomCopyBytes(kSecRandomDefault, secretPtr.count, secretPtr.baseAddress!)
}
guard result == errSecSuccess else {
throw EphemeralSecretError.secRandomCopyBytesFailed(status: result)
}
}
public init(with secret: VCCryptoSecret) throws
{
value = Data()
try secret.withUnsafeBytes { secretPtr in
if let baseAddress = secretPtr.baseAddress, secretPtr.count > 0 {
let bytes = baseAddress.assumingMemoryBound(to: UInt8.self)
value.append(bytes, count: secretPtr.count)
}
}
self.id = secret.id
self.accessGroup = secret.accessGroup
}
public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> Void) throws {
try value.withUnsafeBytes { (valuePtr) in
try body(valuePtr)
}
}
public func isValidKey() throws {}
public func migrateKey(fromAccessGroup currentAccessGroup: String?) throws {
/* Do nothing */
}
public func prefix(_ maxLength: Int) -> EphemeralSecret {
return EphemeralSecret(with: value.prefix(maxLength))
}
public func suffix(_ maxLength: Int) -> EphemeralSecret {
return EphemeralSecret(with: value.suffix(maxLength))
}
deinit {
value.withUnsafeMutableBytes { (secretPtr) in
let secretBytes = secretPtr.bindMemory(to: UInt8.self)
memset_s(secretBytes.baseAddress, secretBytes.count, 0, secretBytes.count)
return
}
}
}

Просмотреть файл

@ -62,13 +62,8 @@ final class Random32BytesSecret: Secret {
}
}
func isValidKey() -> Bool {
do {
_ = try self.store.getSecret(id: id, itemTypeCode: Random32BytesSecret.itemTypeCode, accessGroup: accessGroup)
return true
} catch {
return false
}
func isValidKey() throws {
_ = try self.store.getSecret(id: id, itemTypeCode: Random32BytesSecret.itemTypeCode, accessGroup: accessGroup)
}
func migrateKey(fromAccessGroup currentAccessGroup: String?) throws {

Просмотреть файл

@ -15,16 +15,16 @@ public protocol VCCryptoSecret {
/// Access group for key.
var accessGroup: String? { get }
func isValidKey() -> Bool
func isValidKey() throws
func migrateKey(fromAccessGroup currentAccessGroup: String?) throws
/// Invokes the closure passed as a param with a buffer pointer to the raw bytes of the secret.
func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> Void) throws
}
protocol InternalSecret {
/// Invokes the closure passed as a param with a buffer pointer to the raw bytes of the secret.
func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> Void) throws
/// The 4 characters representing the secret type in the store. This correspond to kSecAttrType in keychain
static var itemTypeCode: String { get }
}

Просмотреть файл

@ -5,10 +5,21 @@
import Foundation
public enum SecretStoringError: Error {
case itemNotFound
case invalidItemInStore
case itemAlreadyInStore
case invalidType
case deleteFromStoreError(status: OSStatus, message: String? = nil)
case saveToStoreError(status: Int32, message: String? = nil)
case readFromStoreError(status: OSStatus, message: String? = nil)
}
// public until Identifier Creation is implemented.
public protocol SecretStoring {
func getSecret(id: UUID, itemTypeCode: String, accessGroup: String?) throws -> Data
func saveSecret(id: UUID, itemTypeCode: String, accessGroup: String?, value: inout Data) throws
func deleteSecret(id: UUID, itemTypeCode: String, accessGroup: String?) throws
}

Просмотреть файл

@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import XCTest
import Foundation
@testable import VCCrypto
class AesTests: XCTestCase {
private var fixture: Aes!
override func setUpWithError() throws {
self.fixture = Aes()
}
/// A sample 'content encryption key' (c.f. rfc3394)
private let cek = EphemeralSecret(with: Data(hexString: "00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F"))
/// A sample 'key encrypting key'
private let kek = EphemeralSecret(with: Data(hexString: "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"))
/// The sample CEK wrapped with the KEK
private let wrapped = Data(hexString: "28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21")
func testKeyWrap() throws {
let result = try fixture.wrap(key: cek, with: kek)
XCTAssertEqual(result, wrapped)
}
func testKeyUnwrap() throws {
let result = try fixture.unwrap(wrapped: wrapped, using: kek)
var data = Data()
try result.withUnsafeBytes{ resultPtr in
data.append(resultPtr.bindMemory(to: UInt8.self))
}
XCTAssertEqual(data, cek.value)
}
/// c.f. rfc3602
private let key = EphemeralSecret(with: Data(hexString: "56e47a38c5598974bc46903dba290349"))
private let iv = Data(hexString: "8ce82eefbea0da3c44699ed7db51b7d9")
private let plaintext = Data(hexString: "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf")
private let ciphertext = Data(hexString: "c30e32ffedc0774e6aff6af0869f71aa0f3af07a9a31a9c684db207eb0ef8e4e35907aa632c3ffdf868bb7b29d3d46ad83ce9f9a102ee99d49a53e87f4c3da55")
func testAESEncryption() throws {
let result = try fixture.encrypt(data: plaintext, with: key, iv: iv)
XCTAssertEqual(result, ciphertext)
}
func testAESDecryption() throws {
let result = try fixture.decrypt(data: ciphertext, with: key, iv: iv)
XCTAssertEqual(result, plaintext)
}
func testAESNonAlignedInput() throws {
/// 17 bytes of input
let plaintext = self.plaintext.prefix(fixture.blockSize+1)
let encrypted = try fixture.encrypt(data: plaintext, with: key, iv: iv)
let decrypted = try fixture.decrypt(data: encrypted, with: key, iv: iv)
/// Validate
XCTAssertEqual(decrypted, plaintext)
}
}

Просмотреть файл

@ -10,7 +10,6 @@ import XCTest
class HmacSha512Tests: XCTestCase {
private let secret = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
private var store: SecretStoreMock!
private var hmac: HmacSha512!
override func setUpWithError() throws {
@ -35,30 +34,27 @@ class HmacSha512Tests: XCTestCase {
}
func testValidate() throws {
let hmac = HmacSha512()
let result = try hmac.isValidAuthenticationCode(
Data(hexString:"368f91fd6ac70cb1d00035dfa5047a3f258111c7d80650c4de9bf7da20d23ba20aee6f94d3bdf325dc70a2735cf51a910c4364c0802eb4098cdbce813d97df87"),
authenticating: Utf8TestData.hello.rawValue.data(using: .utf8)!,
withSecret: SecretMock(id: UUID(), withData: secret.data(using: .utf8)!))
withSecret: EphemeralSecret(with: secret.data(using: .utf8)!))
XCTAssertTrue(result)
}
func testInvalid() throws {
let hmac = HmacSha512()
let result = try hmac.isValidAuthenticationCode(
Data(hexString:"aaaa91fd6ac70cb1d00035dfa5047a3f258111c7d80650c4de9bf7da20d23ba20aee6f94d3bdf325dc70a2735cf51a910c4364c0802eb4098cdbce813d97df87"),
authenticating: Utf8TestData.hello.rawValue.data(using: .utf8)!,
withSecret: SecretMock(id: UUID(), withData: secret.data(using: .utf8)!))
withSecret: EphemeralSecret(with: secret.data(using: .utf8)!))
XCTAssertFalse(result)
}
private func runAuthenticateTest(for message: String, expecting expected: String) throws {
let expectedResult = Data(hexString:expected)
let hmac = HmacSha512()
let result = try hmac.authenticate(
message: message.data(using: .utf8)!,
withSecret: SecretMock(id: UUID(), withData: secret.data(using: .utf8)!))
withSecret: EphemeralSecret(with: secret.data(using: .utf8)!))
XCTAssertEqual(result, expectedResult)
}
}

Просмотреть файл

@ -29,7 +29,7 @@ class KeychainSecretStoreTests: XCTestCase {
do {
let _ = try store.getSecret(id: secretId, itemTypeCode: "AAAA", accessGroup: nil)
XCTAssertTrue(false)
} catch KeychainStoreError.itemNotFound {
} catch SecretStoringError.itemNotFound {
// We expect an exception for this case.
}
}

Просмотреть файл

@ -28,9 +28,7 @@ final class SecretMock : Secret {
}
}
func isValidKey() -> Bool {
return true
}
func isValidKey() throws { }
func migrateKey(fromAccessGroup oldAccessGroup: String?) throws { }
}

Просмотреть файл

@ -21,4 +21,5 @@ internal class SecretStoreMock: SecretStoring {
func deleteSecret(id: UUID, itemTypeCode: String, accessGroup: String?) throws {
memoryStore.removeValue(forKey: id)
}
}

Просмотреть файл

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import XCTest
import Foundation
@testable import VCCrypto
class PbkdfTests : XCTestCase {
func testKeyDeriviation() throws {
// Setup, c.f. https://datatracker.ietf.org/doc/html/rfc7517#appendix-C.2
let password = "Thus from my lips, by yours, my sin is purged."
let p2s: [UInt8] = [217, 96, 147, 112, 150, 117, 70, 247, 127, 8, 155, 137, 174, 42, 80, 215]
let algorithmName = "PBES2-HS256+A128KW"
let expected: [UInt8] = [110, 171, 169, 92, 129, 92, 109, 117, 233, 242, 116, 233, 170, 14, 24, 75]
// Generate the key
let pbkdf = Pbkdf()
let derived = try pbkdf.derive(from: password, withSaltInput: Data(p2s), forAlgorithm: algorithmName, rounds: 4096)
// Validate
var data = Data()
try derived.withUnsafeBytes { (derivedPtr) in
data.append(derivedPtr.bindMemory(to: UInt8.self))
}
XCTAssertEqual(Data(expected), data)
}
}

Просмотреть файл

@ -7,6 +7,17 @@
objects = {
/* Begin PBXBuildFile section */
2F848B7F282D5D25005D3176 /* UnprotectedBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B70282D5D25005D3176 /* UnprotectedBackup.swift */; };
2F848B80282D5D25005D3176 /* ProtectedBackupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B72282D5D25005D3176 /* ProtectedBackupData.swift */; };
2F848B81282D5D25005D3176 /* Microsoft2020IdentifierBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B74282D5D25005D3176 /* Microsoft2020IdentifierBackup.swift */; };
2F848B82282D5D25005D3176 /* RawIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B75282D5D25005D3176 /* RawIdentity.swift */; };
2F848B83282D5D25005D3176 /* VcMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B76282D5D25005D3176 /* VcMetadata.swift */; };
2F848B84282D5D25005D3176 /* Microsoft2020UnprotectedBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B77282D5D25005D3176 /* Microsoft2020UnprotectedBackup.swift */; };
2F848B85282D5D25005D3176 /* WalletMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B78282D5D25005D3176 /* WalletMetadata.swift */; };
2F848B86282D5D25005D3176 /* UnprotectedBackupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B79282D5D25005D3176 /* UnprotectedBackupData.swift */; };
2F848B87282D5D25005D3176 /* BackupProtectionMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B7B282D5D25005D3176 /* BackupProtectionMethod.swift */; };
2F848B88282D5D25005D3176 /* JwePasswordProtectedBackupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B7D282D5D25005D3176 /* JwePasswordProtectedBackupData.swift */; };
2F848B89282D5D25005D3176 /* JwePasswordProtectionMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B7E282D5D25005D3176 /* JwePasswordProtectionMethod.swift */; };
55011E8C2572DF50002D2690 /* VCSDKLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55011E8B2572DF50002D2690 /* VCSDKLog.swift */; };
55011E8E2572DFBD002D2690 /* DefaultLogConsumer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55011E8D2572DFBD002D2690 /* DefaultLogConsumer.swift */; };
55077ABA25A62DE10052C58D /* IdentifierFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55077AB925A62DE10052C58D /* IdentifierFormatter.swift */; };
@ -147,6 +158,18 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
2F4EEF80282E758E00B62311 /* PromiseKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PromiseKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2F848B70282D5D25005D3176 /* UnprotectedBackup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnprotectedBackup.swift; sourceTree = "<group>"; };
2F848B72282D5D25005D3176 /* ProtectedBackupData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProtectedBackupData.swift; sourceTree = "<group>"; };
2F848B74282D5D25005D3176 /* Microsoft2020IdentifierBackup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Microsoft2020IdentifierBackup.swift; sourceTree = "<group>"; };
2F848B75282D5D25005D3176 /* RawIdentity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawIdentity.swift; sourceTree = "<group>"; };
2F848B76282D5D25005D3176 /* VcMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VcMetadata.swift; sourceTree = "<group>"; };
2F848B77282D5D25005D3176 /* Microsoft2020UnprotectedBackup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Microsoft2020UnprotectedBackup.swift; sourceTree = "<group>"; };
2F848B78282D5D25005D3176 /* WalletMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletMetadata.swift; sourceTree = "<group>"; };
2F848B79282D5D25005D3176 /* UnprotectedBackupData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnprotectedBackupData.swift; sourceTree = "<group>"; };
2F848B7B282D5D25005D3176 /* BackupProtectionMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupProtectionMethod.swift; sourceTree = "<group>"; };
2F848B7D282D5D25005D3176 /* JwePasswordProtectedBackupData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JwePasswordProtectedBackupData.swift; sourceTree = "<group>"; };
2F848B7E282D5D25005D3176 /* JwePasswordProtectionMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JwePasswordProtectionMethod.swift; sourceTree = "<group>"; };
55011E8B2572DF50002D2690 /* VCSDKLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCSDKLog.swift; sourceTree = "<group>"; };
55011E8D2572DFBD002D2690 /* DefaultLogConsumer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultLogConsumer.swift; sourceTree = "<group>"; };
55077AB925A62DE10052C58D /* IdentifierFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifierFormatter.swift; sourceTree = "<group>"; };
@ -303,6 +326,56 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2F848B6F282D5D25005D3176 /* backup */ = {
isa = PBXGroup;
children = (
2F848B70282D5D25005D3176 /* UnprotectedBackup.swift */,
2F848B71282D5D25005D3176 /* content */,
2F848B7A282D5D25005D3176 /* container */,
);
path = backup;
sourceTree = "<group>";
};
2F848B71282D5D25005D3176 /* content */ = {
isa = PBXGroup;
children = (
2F848B73282D5D25005D3176 /* microsoft2020 */,
2F848B72282D5D25005D3176 /* ProtectedBackupData.swift */,
2F848B79282D5D25005D3176 /* UnprotectedBackupData.swift */,
);
path = content;
sourceTree = "<group>";
};
2F848B73282D5D25005D3176 /* microsoft2020 */ = {
isa = PBXGroup;
children = (
2F848B74282D5D25005D3176 /* Microsoft2020IdentifierBackup.swift */,
2F848B75282D5D25005D3176 /* RawIdentity.swift */,
2F848B76282D5D25005D3176 /* VcMetadata.swift */,
2F848B77282D5D25005D3176 /* Microsoft2020UnprotectedBackup.swift */,
2F848B78282D5D25005D3176 /* WalletMetadata.swift */,
);
path = microsoft2020;
sourceTree = "<group>";
};
2F848B7A282D5D25005D3176 /* container */ = {
isa = PBXGroup;
children = (
2F848B7B282D5D25005D3176 /* BackupProtectionMethod.swift */,
2F848B7C282D5D25005D3176 /* jwe */,
);
path = container;
sourceTree = "<group>";
};
2F848B7C282D5D25005D3176 /* jwe */ = {
isa = PBXGroup;
children = (
2F848B7D282D5D25005D3176 /* JwePasswordProtectedBackupData.swift */,
2F848B7E282D5D25005D3176 /* JwePasswordProtectionMethod.swift */,
);
path = jwe;
sourceTree = "<group>";
};
55011E8A2572DF1C002D2690 /* logging */ = {
isa = PBXGroup;
children = (
@ -414,6 +487,7 @@
55575730251BC575009979AB /* VCEntities */ = {
isa = PBXGroup;
children = (
2F848B6F282D5D25005D3176 /* backup */,
55B4D2D42787BB600086A9F1 /* oidc */,
5517D28A25B90D0A00FBD239 /* exchange */,
5573FEFD25B8DE6900282BC7 /* discovery */,
@ -715,6 +789,7 @@
92E29989252522C300B25BB6 /* Frameworks */ = {
isa = PBXGroup;
children = (
2F4EEF80282E758E00B62311 /* PromiseKit.framework */,
5514E99B2609291E0004BD19 /* VCToken.framework */,
55253975252FCA6D003202D5 /* VCCrypto.framework */,
92E2998A252522C300B25BB6 /* VCJwt.framework */,
@ -833,7 +908,9 @@
buildActionMask = 2147483647;
files = (
55077ABA25A62DE10052C58D /* IdentifierFormatter.swift in Sources */,
2F848B89282D5D25005D3176 /* JwePasswordProtectionMethod.swift in Sources */,
5518CC7625264D5700C7A21B /* ResponseMappings.swift in Sources */,
2F848B86282D5D25005D3176 /* UnprotectedBackupData.swift in Sources */,
5573FEF525B8DE4A00282BC7 /* IONDocumentModel.swift in Sources */,
551F3068252E97000081D5E7 /* IdentifierCreator.swift in Sources */,
55253982252FDF89003202D5 /* KeyContainer.swift in Sources */,
@ -857,6 +934,7 @@
55B4D36927A337AD0086A9F1 /* InputDescriptorMapping.swift in Sources */,
55B4D36727A337590086A9F1 /* NestedInputDescriptorMapping.swift in Sources */,
5573FF0025B8DE6900282BC7 /* IdentifierDocument.swift in Sources */,
2F848B85282D5D25005D3176 /* WalletMetadata.swift in Sources */,
5580588725BFB6C00048E6FA /* PinDescriptor.swift in Sources */,
5557576E251BC6CF009979AB /* ClaimDescriptor.swift in Sources */,
5557576B251BC6CF009979AB /* ContractInputDescriptor.swift in Sources */,
@ -868,11 +946,16 @@
55575769251BC6CF009979AB /* IssuerDescriptor.swift in Sources */,
55575778251BC6CF009979AB /* JSONCodingKeys.swift in Sources */,
5518CC7425264CAD00C7A21B /* PresentationResponseContainer.swift in Sources */,
2F848B80282D5D25005D3176 /* ProtectedBackupData.swift in Sources */,
2F848B83282D5D25005D3176 /* VcMetadata.swift in Sources */,
5518CC7225264C6F00C7A21B /* PresentationResponseFormatter.swift in Sources */,
2F848B87282D5D25005D3176 /* BackupProtectionMethod.swift in Sources */,
55AF7488252F6B53006A8B25 /* IdentifierDocumentSuffixDescriptor.swift in Sources */,
55575774251BC6CF009979AB /* VCClaims.swift in Sources */,
555BDB1C2533AA85001E7A18 /* FormatterError.swift in Sources */,
2F848B81282D5D25005D3176 /* Microsoft2020IdentifierBackup.swift in Sources */,
55B4D2D62787BBED0086A9F1 /* AllowedAlgorithms.swift in Sources */,
2F848B88282D5D25005D3176 /* JwePasswordProtectedBackupData.swift in Sources */,
553D4C472821D89D00FD39A6 /* IssuanceRequestValidator.swift in Sources */,
5584E49925255ACA00A9DE58 /* PresentationRequestClaims.swift in Sources */,
55575771251BC6CF009979AB /* AttestationResponseDescriptor.swift in Sources */,
@ -884,6 +967,7 @@
559FCA3C25CA03D200737F14 /* IssuanceRequest.swift in Sources */,
55575773251BC6CF009979AB /* VerifiableCredentialDescriptor.swift in Sources */,
55B4D36127A330680086A9F1 /* IssuanceVPFormatter.swift in Sources */,
2F848B84282D5D25005D3176 /* Microsoft2020UnprotectedBackup.swift in Sources */,
555CE08F2526822300C1C938 /* VerifiablePresentationDescriptor.swift in Sources */,
55575770251BC6CF009979AB /* OIDCClaims.swift in Sources */,
55A1EDFC27AC47FC00C63CA8 /* VPTokenResponseDescription.swift in Sources */,
@ -909,6 +993,8 @@
5531D3B2255F6B6E0002CC0E /* ResponseContaining.swift in Sources */,
5557576A251BC6CF009979AB /* Contract.swift in Sources */,
5517D28E25B90D0A00FBD239 /* ExchangeRequestClaims.swift in Sources */,
2F848B82282D5D25005D3176 /* RawIdentity.swift in Sources */,
2F848B7F282D5D25005D3176 /* UnprotectedBackup.swift in Sources */,
559FC99025C8BEEB00737F14 /* DomainLinkageCredential.swift in Sources */,
55379AD725A8DF7A0048600A /* PresentationRequestValidator.swift in Sources */,
55AC02B525D3087D00AA15F4 /* IdentifierDocumentServiceEndpointDescriptor.swift in Sources */,

Просмотреть файл

@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
/// This protocol serves as the interface for any kind of backup that may hold any kind of data.
/// This way the implementations of backups can evolve without the APIs changing.
public protocol UnprotectedBackup : Codable { }

Просмотреть файл

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// This protocol serves as the interface for a family of objects which can apply different kinds of protection
/// (typically, encryption) to backup data
public protocol BackupProtectionMethod {
func wrap(unprotectedBackupData: UnprotectedBackupData) throws -> ProtectedBackupData
func unwrap(protectedBackupData: ProtectedBackupData) throws -> UnprotectedBackupData
}

Просмотреть файл

@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
struct JwePasswordProtectedBackupData : ProtectedBackupData {
let content: String
public func serialize() -> String {
content
}
}

Просмотреть файл

@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
import VCCrypto
import VCToken
enum JwePasswordProtectionError : Error {
case invalidContentType
}
public struct JwePasswordProtectionMethod : BackupProtectionMethod {
private struct Constants {
static let Algorithm = "PBES2-HS512+A256KW"
static let Method = "A256CBC-HS512"
static let SaltLength = 8
static let SaltIterations = UInt(100 * 1000)
}
let password: String
public init(password: String) {
self.password = password
}
public func wrap(unprotectedBackupData: UnprotectedBackupData) throws -> ProtectedBackupData {
// Generate a salt
let salt = try EphemeralSecret(size: Constants.SaltLength)
// Construct the headers
let headers = Header(type: nil,
algorithm: Constants.Algorithm,
encryptionMethod: Constants.Method,
jsonWebKey: nil,
keyId: nil,
contentType: unprotectedBackupData.type,
pbes2SaltInput: salt.value.base64URLEncodedString(),
pbes2Count: Constants.SaltIterations)
// Encrypt
let token = try PbesJwe().encrypt(unprotectedBackupData.encoded,
with: self.password,
using: headers)
// Encode and return
return try JwePasswordProtectedBackupData(content: JweEncoder().encode(token))
}
public func unwrap(protectedBackupData: ProtectedBackupData) throws -> UnprotectedBackupData {
// Decode
let token = try JweDecoder().decode(token: protectedBackupData.serialize())
guard let type = token.headers.contentType else {
throw JwePasswordProtectionError.invalidContentType
}
// Decrypt
let decrypted = try PbesJwe().decrypt(token, with: self.password)
// Return
return UnprotectedBackupData(type: type, encoded: decrypted)
}
}

Просмотреть файл

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// A ProtectedBackup holds a UnprotectedBackupData in some shape or form.
/// The details are defined by implementations of this protocol.
/// e.g. a JWE Token encrypted by a password.
public protocol ProtectedBackupData {
func serialize() -> String
}

Просмотреть файл

@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
/// A ProtectedBackup holds a UnprotectedBackupData in some shape or form. The details are defined by implementations of this protocol. e.g. a JWE Token encrypted by a password.
public struct UnprotectedBackupData {
public let type: String
public let encoded: Data
public init(type: String, encoded: Data) {
self.type = type
self.encoded = encoded
}
}

Просмотреть файл

@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
public struct Microsoft2020IdentifierBackup : Codable {
private struct Constants {
/// For compatability with the Android backup data model:
static let OnTheWireMainIdentifier = "did.main.identifier"
}
private var entries = [RawIdentity]()
public init() { }
public init(from decoder:Decoder) throws
{
let container = try decoder.singleValueContainer()
self.entries = try container.decode([RawIdentity].self)
}
public func encode(to encoder: Encoder) throws
{
var container = encoder.singleValueContainer()
try container.encode(self.entries)
}
public mutating func setMain(_ identifier:Identifier) throws {
// Try and convert
var entry = try RawIdentity(identifier: identifier)
entry.name = Constants.OnTheWireMainIdentifier
// Remove any incidence in the array using the same identifier
entries.removeAll(where: {$0.name == entry.name})
// Insert the new master identity at the head of the array
entries.insert(entry, at: 0)
}
public mutating func add(_ identifier:Identifier) throws {
let entry = try RawIdentity(identifier: identifier)
entries.append(entry)
}
public func all() throws -> [Identifier]
{
// Look for the main identifier
var main: Identifier? = nil
if var found = entries.first(where: {$0.name == Constants.OnTheWireMainIdentifier}) {
found.name = AliasComputer().compute(forId: VCEntitiesConstants.MASTER_ID,
andRelyingParty: VCEntitiesConstants.MASTER_ID)
do {
main = try found.asIdentifier()
}
catch (let error) {
VCSDKLog.sharedInstance.logError(message: "Failed to read \(found) as the main identifier with error \(error)")
throw error
}
}
// Set the main identifier, if any, as the first returned
var identifiers = [Identifier]()
if let alpha = main {
identifiers.append(alpha)
}
try entries.forEach { entry in
// Skip the main identifier (if we encounter it again)
if entry.name == Constants.OnTheWireMainIdentifier {
return
}
try identifiers.append(entry.asIdentifier())
}
return identifiers
}
}

Просмотреть файл

@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
public enum Microsoft2020BackupError : Error {
case undecodableCredential(source:String)
}
open class Microsoft2020UnprotectedBackup : UnprotectedBackup {
private struct Constants {
static let OnTheWireBackupType = "MicrosoftWallet2020"
}
public var type = Constants.OnTheWireBackupType
public var identifiers = Microsoft2020IdentifierBackup()
public var vcs = [String:String]()
public var vcsMetaInf = [String:VcMetadata]()
public var metaInf: WalletMetadata?
public init(metaInf: WalletMetadata? = nil) {
self.metaInf = metaInf
}
public func add(credential: VerifiableCredential, metadata:VcMetadata) {
if let id = credential.content.jti {
self.vcs[id] = credential.rawValue
self.vcsMetaInf[id] = metadata
}
}
public func forEachCredential(completionHandler: @escaping (VerifiableCredential, VcMetadata?) throws -> Void) rethrows {
try self.vcs.forEach { (key, value) in
guard let credential = VerifiableCredential(from: value) else {
// This might be better done during the parsing of the backup from JSON..?
throw Microsoft2020BackupError.undecodableCredential(source: value)
}
let metadata = self.vcsMetaInf[key]
try completionHandler(credential, metadata)
}
}
}

Просмотреть файл

@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
import VCToken
import VCCrypto
enum RawIdentityError: Error {
case signingKeyNotFound
case recoveryKeyNotFound
case updateKeyNotFound
case privateKeyNotFound(keyId: String, accessGroup: String?)
case keyIdNotFound
}
struct RawIdentity: Codable {
var id: String
var name: String
var keys: [Jwk]?
var recoveryKey: String
var updateKey: String
init(identifier: Identifier) throws {
self.id = identifier.did
self.name = identifier.alias
self.recoveryKey = identifier.recoveryKey.keyId
self.updateKey = identifier.updateKey.keyId
self.keys = nil
var keys = try identifier.didDocumentKeys.map(jwkFromKeyContainer)
try keys.append(jwkFromKeyContainer(identifier.recoveryKey))
try keys.append(jwkFromKeyContainer(identifier.updateKey))
self.keys = keys
}
func asIdentifier() throws -> Identifier {
// Get out the keys
guard let keys = self.keys else {
throw RawIdentityError.signingKeyNotFound
}
guard let recoveryJwk = keys.first(where: {$0.keyId == self.recoveryKey}) else {
throw RawIdentityError.recoveryKeyNotFound
}
guard let updateJwk = keys.first(where: {$0.keyId == self.updateKey}) else {
throw RawIdentityError.updateKeyNotFound
}
let set = Set([self.recoveryKey, self.updateKey])
guard let signingJwk = keys.first(where: {!set.contains($0.keyId!)}) else {
throw RawIdentityError.signingKeyNotFound
}
// Convert
let recoveryKeyContainer = try keyContainerFromJwk(recoveryJwk)
let updateKeyContainer = try keyContainerFromJwk(updateJwk)
let signingKeyContainer = try keyContainerFromJwk(signingJwk)
// Wrap up and return
return Identifier(longFormDid: self.id,
didDocumentKeys: [signingKeyContainer],
updateKey: updateKeyContainer,
recoveryKey: recoveryKeyContainer,
alias: self.name)
}
func jwkFromKeyContainer(_ keyContainer: KeyContainer) throws -> Jwk {
// Get out the private and public components of the key (pair)
let secret = keyContainer.keyReference
let (privateKey, publicKey) = try Secp256k1().createKeyPair(forSecret: secret)
if privateKey.value.isEmpty {
throw RawIdentityError.privateKeyNotFound(keyId: secret.id.uuidString,
accessGroup: secret.accessGroup)
}
// Wrap them up in a JSON Web Key
return Jwk(keyType: "EC",
keyId: keyContainer.keyId,
curve: "secp256k1",
use: "sig",
x: publicKey.x,
y: publicKey.y,
d: privateKey.value)
}
func keyContainerFromJwk(_ jwk: Jwk) throws -> KeyContainer {
// Get out the ID and the key data
guard let keyId = jwk.keyId else {
throw RawIdentityError.keyIdNotFound
}
guard let privateKeyData = jwk.d,
!privateKeyData.isEmpty else {
throw RawIdentityError.privateKeyNotFound(keyId: keyId, accessGroup: nil)
}
// Wrap it all up
let privateKey = EphemeralSecret(with: privateKeyData,
id: UUID(),
accessGroup: VCSDKConfiguration.sharedInstance.accessGroupIdentifier)
return KeyContainer(keyReference: privateKey, keyId: keyId)
}
}

Просмотреть файл

@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
open class VcMetadata : Codable {
public var displayContract: DisplayDescriptor?
public var type: String
public init(as type:String) {
self.type = type
}
}

Просмотреть файл

@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
import VCToken
open class WalletMetadata: Codable {
enum CodingKeys: String, CodingKey {
case seed
}
public var seed: Data
public init(with seed:Data) {
self.seed = seed
}
public required init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: Self.CodingKeys)
let string = try container.decode(String.self, forKey: .seed)
// Now, parse the string as a JWK
let jwk = try JSONDecoder().decode(Jwk.self, from: string.data(using: .utf8)!)
self.seed = jwk.key!
}
public func encode(to encoder: Encoder) throws {
// Wrap the seed value in a JWK and encode to a string
let jwk = Jwk(keyType: "oct", key: self.seed)
let string = try String(data: JSONEncoder().encode(jwk), encoding: .utf8)!
var container = encoder.container(keyedBy: Self.CodingKeys)
try container.encode(string, forKey: .seed)
}
}

Просмотреть файл

@ -97,6 +97,7 @@ public class IssuanceResponseFormatter: IssuanceResponseFormatting {
sdkLog.logVerbose(message: """
Creating Issuance Response with:
access_tokens: \(accessTokenMap?.count ?? 0)
id_tokens: \(idTokenMap?.count ?? 0)
self_issued claims: \(selfIssuedMap?.count ?? 0)
verifiable credentials: \(presentationsMap?.count ?? 0)

Просмотреть файл

@ -7,15 +7,15 @@ import VCCrypto
public struct KeyContainer {
/// key reference to key in Secret Store
let keyReference: VCCryptoSecret
/// keyId to specify key in Identifier Document (must be less than 20 chars long)
let keyId: String
/// Always ES256K because we only support Secp256k1 keys
let algorithm: String = "ES256K"
/// key reference to key in Secret Store
public let keyReference: VCCryptoSecret
/// keyId to specify key in Identifier Document (must be less than 20 chars long)
public let keyId: String
public init(keyReference: VCCryptoSecret,
keyId: String) {
self.keyReference = keyReference
@ -26,8 +26,8 @@ public struct KeyContainer {
return keyReference.id
}
public func isValidKey() -> Bool {
return keyReference.isValidKey()
public func isValidKey() throws {
try keyReference.isValidKey()
}
/// Migrate key from old access group to new one set in sdk config.

Просмотреть файл

@ -16,4 +16,13 @@ public struct CardDisplayDescriptor: Codable, Equatable {
case title, issuedBy, backgroundColor, textColor, logo
case cardDescription = "description"
}
public init(title: String, issuedBy: String, backgroundColor: String, textColor: String, logo: LogoDisplayDescriptor?, cardDescription: String) {
self.title = title
self.issuedBy = issuedBy
self.backgroundColor = backgroundColor
self.textColor = textColor
self.logo = logo
self.cardDescription = cardDescription
}
}

Просмотреть файл

@ -8,4 +8,8 @@ public struct ClaimDisplayDescriptor: Codable, Equatable {
public let type: String
public let label: String
public init(type:String, label:String) {
self.type = type
self.label = label
}
}

Просмотреть файл

@ -8,4 +8,8 @@ public struct ConsentDisplayDescriptor: Codable, Equatable {
public let title: String?
public let instructions: String
public init(title: String?, instructions: String) {
self.title = title
self.instructions = instructions
}
}

Просмотреть файл

@ -12,4 +12,12 @@ public struct DisplayDescriptor: Codable, Equatable {
public let consent: ConsentDisplayDescriptor
public let claims: [String: ClaimDisplayDescriptor]
public init(id: String?, locale: String?, contract: String?, card: CardDisplayDescriptor, consent: ConsentDisplayDescriptor, claims: [String : ClaimDisplayDescriptor]) {
self.id = id
self.locale = locale
self.contract = contract
self.card = card
self.consent = consent
self.claims = claims
}
}

Просмотреть файл

@ -11,6 +11,12 @@ public struct LogoDisplayDescriptor: Codable, Equatable {
// Image can be already included in the logo display descriptor as a base64 encoded png
public let image: String?
public init(uri: String?, logoDescription: String?, image: String?) {
self.uri = uri
self.logoDescription = logoDescription
self.image = image
}
enum CodingKeys: String, CodingKey {
case uri
case logoDescription = "description"

Просмотреть файл

@ -93,7 +93,7 @@ public struct VCSDKLog {
}
}
func event(name: String, properties: [String: String]? = nil, measurements: [String: NSNumber]? = nil) {
public func event(name: String, properties: [String: String]? = nil, measurements: [String: NSNumber]? = nil) {
consumers.forEach { logger in
logger.event(name: name, properties: properties, measurements: measurements)
}

Просмотреть файл

@ -10,10 +10,15 @@ import VCToken
struct MockCryptoOperations: CryptoOperating {
static let itemTypeCode = "r32b"
static let accessGroupIdentifier = "anywhere.com.microsoft.azureauthenticator.did"
static var generateKeyCallCount = 0
let cryptoOperations: CryptoOperating
let secretStore: SecretStoring
init(secretStore: SecretStoring) {
self.secretStore = secretStore
self.cryptoOperations = CryptoOperations(secretStore: secretStore, sdkConfiguration: VCSDKConfiguration.sharedInstance)
}
@ -25,4 +30,42 @@ struct MockCryptoOperations: CryptoOperating {
func retrieveKeyFromStorage(withId id: UUID) -> VCCryptoSecret {
return KeyId(id: id)
}
public func save(key: Data, withId id: UUID) throws {
// Take a copy of the key to let the store dispose of it
var data = Data()
data.append(key)
// Store down
try secretStore.saveSecret(id: id,
itemTypeCode: Self.itemTypeCode,
accessGroup: Self.accessGroupIdentifier,
value: &data)
}
public func deleteKey(withId id: UUID) throws {
let itemTypeCode = Self.itemTypeCode
let accessGroup = Self.accessGroupIdentifier
do {
let _ = try secretStore.getSecret(id: id,
itemTypeCode: itemTypeCode,
accessGroup: accessGroup)
// If we get here the key exists, so we can delete it
try secretStore.deleteSecret(id: id, itemTypeCode: itemTypeCode, accessGroup: accessGroup)
}
catch SecretStoringError.itemNotFound {
/* There's no key so nothing to delete */
}
}
public func getKey(withId id: UUID) throws -> Data {
return try secretStore.getSecret(id: id,
itemTypeCode: Self.itemTypeCode,
accessGroup: Self.accessGroupIdentifier)
}
}

Просмотреть файл

@ -16,9 +16,9 @@ struct MockCryptoSecret: VCCryptoSecret {
self.id = id
}
func isValidKey() -> Bool {
return true
}
func isValidKey() throws { }
func migrateKey(fromAccessGroup oldAccessGroup: String?) throws { }
func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> Void) throws { }
}

Просмотреть файл

@ -21,4 +21,5 @@ internal class SecretStoreMock: SecretStoring {
func deleteSecret(id: UUID, itemTypeCode: String, accessGroup: String?) throws {
memoryStore.removeValue(forKey: id)
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
2F848B52282D4DEC005D3176 /* difwordlist.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2F848B51282D4DEC005D3176 /* difwordlist.txt */; };
2F848B54282D4EF3005D3176 /* DifWordListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B53282D4EF3005D3176 /* DifWordListTests.swift */; };
2F848B56282D4F1F005D3176 /* DifWordList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B55282D4F1F005D3176 /* DifWordList.swift */; };
550F1E3F25101759009AF467 /* VCServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 550F1E3525101758009AF467 /* VCServices.framework */; };
550F1E4425101759009AF467 /* IssuanceServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550F1E4325101759009AF467 /* IssuanceServiceTests.swift */; };
550F1E4625101759009AF467 /* VCUseCase.h in Headers */ = {isa = PBXBuildFile; fileRef = 550F1E3825101758009AF467 /* VCUseCase.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -36,13 +39,14 @@
55793BD7255C5A26007F7599 /* ExchangeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55793BD6255C5A25007F7599 /* ExchangeService.swift */; };
55793BDB255C654E007F7599 /* MockExchangeRequestFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55793BDA255C654E007F7599 /* MockExchangeRequestFormatter.swift */; };
55793BDD255C65DA007F7599 /* ExchangeServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55793BDC255C65DA007F7599 /* ExchangeServiceTests.swift */; };
5580583325BF97DF0048E6FA /* VerifiableCredentialDataModel.xcdatamodeld in Resources */ = {isa = PBXBuildFile; fileRef = 555BDAEA2530AEC7001E7A18 /* VerifiableCredentialDataModel.xcdatamodeld */; };
5580583E25BFA2140048E6FA /* ServicesConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5580583D25BFA2140048E6FA /* ServicesConstants.swift */; };
559FC9D225C9D90B00737F14 /* LinkedDomainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559FC9D125C9D90B00737F14 /* LinkedDomainService.swift */; };
559FC9F125C9F2B700737F14 /* LinkedDomainServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559FC9F025C9F2B700737F14 /* LinkedDomainServiceTests.swift */; };
559FC9F925C9F35A00737F14 /* MockWellKnownConfigDocumentApiCalls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559FC9F825C9F35A00737F14 /* MockWellKnownConfigDocumentApiCalls.swift */; };
559FCA3425C9FB7D00737F14 /* MockDomainLinkageCredentialValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559FCA3325C9FB7D00737F14 /* MockDomainLinkageCredentialValidator.swift */; };
55AF7490252FBF5B006A8B25 /* MockVCCryptoSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55AF748F252FBF5B006A8B25 /* MockVCCryptoSecret.swift */; };
55BA803B2832C91B004C532A /* VCServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 550F1E3525101758009AF467 /* VCServices.framework */; };
55BA803D2832CDE0004C532A /* VCEntities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55BA803C2832CDE0004C532A /* VCEntities.framework */; };
55BF81652829B73600B61D33 /* MockIssuanceRequestValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55BF81642829B73600B61D33 /* MockIssuanceRequestValidator.swift */; };
55CA3A7F27FB60C900615CB6 /* VCSDKConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CA3A7E27FB60C900615CB6 /* VCSDKConfiguration.swift */; };
55DA76ED25BE36FF009C32E0 /* MockIssuanceApiCalls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55DA76EC25BE36FF009C32E0 /* MockIssuanceApiCalls.swift */; };
@ -74,6 +78,9 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
2F848B51282D4DEC005D3176 /* difwordlist.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = difwordlist.txt; sourceTree = "<group>"; };
2F848B53282D4EF3005D3176 /* DifWordListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DifWordListTests.swift; sourceTree = "<group>"; };
2F848B55282D4F1F005D3176 /* DifWordList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DifWordList.swift; sourceTree = "<group>"; };
550F1E3525101758009AF467 /* VCServices.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = VCServices.framework; sourceTree = BUILT_PRODUCTS_DIR; };
550F1E3825101758009AF467 /* VCUseCase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VCUseCase.h; sourceTree = "<group>"; };
550F1E3925101758009AF467 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -114,6 +121,10 @@
559FC9F825C9F35A00737F14 /* MockWellKnownConfigDocumentApiCalls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockWellKnownConfigDocumentApiCalls.swift; sourceTree = "<group>"; };
559FCA3325C9FB7D00737F14 /* MockDomainLinkageCredentialValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDomainLinkageCredentialValidator.swift; sourceTree = "<group>"; };
55AF748F252FBF5B006A8B25 /* MockVCCryptoSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockVCCryptoSecret.swift; sourceTree = "<group>"; };
55BA80352832C900004C532A /* VCCrypto.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = VCCrypto.framework; sourceTree = BUILT_PRODUCTS_DIR; };
55BA80372832C900004C532A /* VCEntities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = VCEntities.framework; sourceTree = BUILT_PRODUCTS_DIR; };
55BA80392832C900004C532A /* VCNetworking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = VCNetworking.framework; sourceTree = BUILT_PRODUCTS_DIR; };
55BA803C2832CDE0004C532A /* VCEntities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = VCEntities.framework; sourceTree = BUILT_PRODUCTS_DIR; };
55BF81642829B73600B61D33 /* MockIssuanceRequestValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockIssuanceRequestValidator.swift; sourceTree = "<group>"; };
55CA3A7E27FB60C900615CB6 /* VCSDKConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = VCSDKConfiguration.swift; path = ../../VCEntities/VCEntities/util/VCSDKConfiguration.swift; sourceTree = "<group>"; };
55DA76EC25BE36FF009C32E0 /* MockIssuanceApiCalls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockIssuanceApiCalls.swift; sourceTree = "<group>"; };
@ -152,12 +163,22 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
55BA803D2832CDE0004C532A /* VCEntities.framework in Frameworks */,
55BA803B2832C91B004C532A /* VCServices.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2F848B50282D4DEC005D3176 /* Resources */ = {
isa = PBXGroup;
children = (
2F848B51282D4DEC005D3176 /* difwordlist.txt */,
);
path = Resources;
sourceTree = "<group>";
};
550F1E2B25101758009AF467 = {
isa = PBXGroup;
children = (
@ -182,7 +203,9 @@
550F1E3725101758009AF467 /* VCServices */ = {
isa = PBXGroup;
children = (
2F848B50282D4DEC005D3176 /* Resources */,
552539832530AE5F003202D5 /* coreData */,
2F848B55282D4F1F005D3176 /* DifWordList.swift */,
555BDB142530F024001E7A18 /* VerifiableCredentialSDK.swift */,
5531D3AD255F1F360002CC0E /* IdentifierService.swift */,
550F1E532510195A009AF467 /* IssuanceService.swift */,
@ -202,6 +225,7 @@
isa = PBXGroup;
children = (
550F1E64251165DB009AF467 /* mocks */,
2F848B53282D4EF3005D3176 /* DifWordListTests.swift */,
550F1E4325101759009AF467 /* IssuanceServiceTests.swift */,
55793BDC255C65DA007F7599 /* ExchangeServiceTests.swift */,
559FC9F025C9F2B700737F14 /* LinkedDomainServiceTests.swift */,
@ -263,6 +287,10 @@
8A1DACB8079028BD52F83396 /* Frameworks */ = {
isa = PBXGroup;
children = (
55BA803C2832CDE0004C532A /* VCEntities.framework */,
55BA80352832C900004C532A /* VCCrypto.framework */,
55BA80372832C900004C532A /* VCEntities.framework */,
55BA80392832C900004C532A /* VCNetworking.framework */,
92D5988725C23E4E00C42370 /* Secp256k1.framework */,
92D5988525C23E2A00C42370 /* VCNetworking.framework */,
92F2ED2825B9EB2F00A6911C /* VCRepository.framework */,
@ -388,6 +416,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2F848B52282D4DEC005D3176 /* difwordlist.txt in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -395,7 +424,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5580583325BF97DF0048E6FA /* VerifiableCredentialDataModel.xcdatamodeld in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -427,6 +455,7 @@
555BDAEE2530CF27001E7A18 /* CoreDataManager.swift in Sources */,
5531D3AE255F1F360002CC0E /* IdentifierService.swift in Sources */,
5580583E25BFA2140048E6FA /* ServicesConstants.swift in Sources */,
2F848B56282D4F1F005D3176 /* DifWordList.swift in Sources */,
555CE0932526AD0400C1C938 /* PresentationService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -440,6 +469,7 @@
550F1E6625116CD6009AF467 /* TestData.swift in Sources */,
551F304F2527EAF40081D5E7 /* PresentationServiceTests.swift in Sources */,
55DA76ED25BE36FF009C32E0 /* MockIssuanceApiCalls.swift in Sources */,
2F848B54282D4EF3005D3176 /* DifWordListTests.swift in Sources */,
55DA770525BE45C7009C32E0 /* MockDiscoveryApiCalls.swift in Sources */,
55DA76FA25BE390E009C32E0 /* MockPresentationApiCalls.swift in Sources */,
555BDB132530EA79001E7A18 /* IdentifierDatabaseTests.swift in Sources */,

Просмотреть файл

@ -0,0 +1,78 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
public class DifWordList {
let words: [String]
public init?() throws {
if let path = Bundle(for: Self.self).path(forResource: "difwordlist", ofType: "txt") {
let content = try String(contentsOfFile: path, encoding: .utf8)
self.words = content.components(separatedBy: .whitespacesAndNewlines).filter{ !($0.isEmpty) }
} else {
return nil
}
}
/// Selects a word at random from the list and returns it
public func randomWord() -> String {
let index = arc4random_uniform(UInt32(self.words.count))
return self.words[Int(index)]
}
/// Randomly selects a specified number of words from the list
/// - Parameters:
/// - count: The number of words to select
/// - Returns: An array of words selected at random from the list up to lesser of the specified number or the number of words in the list
public func randomWords(count:UInt32) -> [String] {
// Look for an early out
if count == 0 {
return [String]()
}
if count >= UInt32(self.words.count) {
// We need to return all the words in the list
// so we take a copy and randomly shuffle it
return self.words.shuffled()
}
var list = [String]()
var set = Set<String>()
repeat {
// Get another word
let word = self.randomWord()
// Was it already selected?
if set.contains(word) {
continue
}
// Nope
set.insert(word)
list.append(word)
} while (list.count < count)
return list
}
public static func normalize(password:String) -> String {
var list = [String]()
// Split the input string along whitespace, etc, and accumulate
// each non-empty substring
let components = password.components(separatedBy: .whitespacesAndNewlines)
components.forEach { component in
if component.isEmpty {
return
}
list.append(component.lowercased())
}
return list.joined(separator: " ")
}
}

Просмотреть файл

@ -6,6 +6,12 @@
import VCEntities
import VCCrypto
public enum IdentifierServiceError: Error {
case keyNotFoundInKeyStore(innerError: Error)
case keyStoreError(message: String)
case noKeysSavedForIdentifier
}
public class IdentifierService {
private let identifierDB: IdentifierDatabase
@ -32,6 +38,63 @@ public class IdentifierService {
return try identifierDB.fetchMasterIdentifier()
}
func fetchOrCreateMasterIdentifier() throws -> Identifier {
let identifier: Identifier
do {
identifier = try fetchMasterIdentifier()
} catch {
sdkLog.logError(message: "Master identifier does not exist with error: \(String(describing: error))")
sdkLog.logInfo(message: "Creating new Identifier.")
return try createMasterIdentifier()
}
do {
try validateAndHandleKeys(for: identifier)
return identifier
} catch {
/// in rare chance no keys are saved, create a new identifier.
if case IdentifierServiceError.noKeysSavedForIdentifier = error {
return try refreshIdentifiers()
} else {
throw error
}
}
}
func createMasterIdentifier() throws -> Identifier {
try createAndSaveIdentifier(forId: VCEntitiesConstants.MASTER_ID, andRelyingParty: VCEntitiesConstants.MASTER_ID)
}
private func refreshIdentifiers() throws -> Identifier {
try identifierDB.removeAllIdentifiers()
let identifier = try createMasterIdentifier()
sdkLog.logInfo(message: "Refreshed identifier.")
return identifier
}
public func fetchIdentifiersForExport() throws -> [Identifier] {
return try identifierDB.fetchAllIdentifiers()
}
public func replaceIdentifiers(with identifiers:[Identifier]) throws {
try identifierDB.removeAllIdentifiers()
try identifiers.forEach { identifier in
try identifierDB.importIdentifier(identifier: identifier)
// Ensure the keys are migrated into the correct access group
let alias = identifier.alias
do {
let fetched = try identifierDB.fetchIdentifier(withAlias: alias)
try migrateKeys(in: fetched, fromAccessGroup: nil)
}
catch {
sdkLog.logWarning(message: "Failed to migrate keys in imported identifier w/alias: \(alias)")
}
}
}
func fetchIdentifier(withAlias alias: String) throws -> Identifier {
return try identifierDB.fetchIdentifier(withAlias: alias)
}
@ -54,9 +117,44 @@ public class IdentifierService {
return identifier
}
private func validateAndHandleKeys(for identifier: Identifier) throws
{
do {
/// Step 1: Check to see if keys are valid.
try areKeysValid(for: identifier)
} catch {
/// Step 2: If keys are not valid, check to see if we need to migrate keys.
if case IdentifierServiceError.keyNotFoundInKeyStore = error {
try migrateKeys()
return
}
/// Else, throw error.
throw error
}
}
/// Migrate keys from default access group to new access group.
private func migrateKeys() throws
{
do {
/// Step 3: migrate keys.
try migrateKeys(fromAccessGroup: nil)
sdkLog.logInfo(message: "Keys successfully migrated to new access group.")
} catch {
sdkLog.logError(message: "failed to migrate keys to new access group with error: \(String(describing: error))")
throw IdentifierServiceError.noKeysSavedForIdentifier
}
}
/// updates access group for keys if it needs to be updated.
public func migrateKeys(fromAccessGroup currentAccessGroup: String?) throws {
private func migrateKeys(fromAccessGroup currentAccessGroup: String?) throws {
let identifier = try fetchMasterIdentifier()
try migrateKeys(in: identifier, fromAccessGroup: currentAccessGroup)
}
private func migrateKeys(in identifier: Identifier, fromAccessGroup currentAccessGroup: String?) throws {
try identifier.recoveryKey.migrateKey(fromAccessGroup: currentAccessGroup)
try identifier.updateKey.migrateKey(fromAccessGroup: currentAccessGroup)
try identifier.didDocumentKeys.forEach { keyContainer in
@ -64,10 +162,27 @@ public class IdentifierService {
}
}
public func areKeysValid() throws -> Bool {
let identifier = try fetchMasterIdentifier()
return identifier.recoveryKey.isValidKey() &&
identifier.updateKey.isValidKey() &&
(identifier.didDocumentKeys.first?.isValidKey() ?? false)
private func areKeysValid(for identifier: Identifier) throws {
do {
try identifier.recoveryKey.isValidKey()
try identifier.updateKey.isValidKey()
for key in identifier.didDocumentKeys {
try key.isValidKey()
}
}
catch {
if case SecretStoringError.itemNotFound = error {
throw IdentifierServiceError.keyNotFoundInKeyStore(innerError: error)
}
if case SecretStoringError.readFromStoreError(status: _, message: let message) = error {
throw IdentifierServiceError.keyStoreError(message: message ?? "Unable to retrieve keys")
}
/// rethrow error if not key not found error.
throw error
}
}
}

Просмотреть файл

@ -88,7 +88,7 @@ public class IssuanceService {
/// turn off pairwise until we have a better solution.
self.exchangeVCsIfPairwise(response: response, isPairwise: false)
}.then { response in
self.formatIssuanceResponse(response: response, isPairwise: false)
self.formatIssuanceResponse(response: response)
}.then { signedToken in
self.apiCalls.sendResponse(usingUrl: response.audienceUrl, withBody: signedToken)
}
@ -157,26 +157,14 @@ public class IssuanceService {
}
}
private func formatIssuanceResponse(response: IssuanceResponseContainer, isPairwise: Bool) -> Promise<IssuanceResponse> {
private func formatIssuanceResponse(response: IssuanceResponseContainer) -> Promise<IssuanceResponse> {
return Promise { seal in
do {
/// fetch or create master identifier
let identifier = try identifierService.fetchOrCreateMasterIdentifier()
sdkLog.logVerbose(message: "Signing Issuance Response with Identifier")
var identifier: Identifier?
if isPairwise {
// TODO: will change when deterministic key generation is implemented.
identifier = try identifierService.fetchIdentifier(forId: VCEntitiesConstants.MASTER_ID, andRelyingParty: response.audienceDid)
} else {
identifier = try identifierService.fetchMasterIdentifier()
}
guard let id = identifier else {
throw IssuanceServiceError.unableToFetchIdentifier
}
sdkLog.logInfo(message: "Signing Issuance Response with Identifier")
seal.fulfill(try self.formatter.format(response: response, usingIdentifier: id))
seal.fulfill(try self.formatter.format(response: response, usingIdentifier: identifier))
} catch {
seal.reject(error)
}

Просмотреть файл

@ -9,10 +9,12 @@ import VCEntities
enum PresentationServiceError: Error {
case inputStringNotUri
case noKeysSavedForIdentifier
case noQueryParametersOnUri
case noValueForRequestUriQueryParameter
case noRequestUriQueryParameter
case unableToCastToPresentationResponseContainer
case unableToFetchIdentifier
case noKeyIdInRequestHeader
case noPublicKeysInIdentifierDocument
case noIssuerIdentifierInRequest
@ -81,7 +83,7 @@ public class PresentationService {
/// turn off pairwise until we have a better solution.
self.exchangeVCsIfPairwise(response: response, isPairwise: false)
}.then { response in
self.formatPresentationResponse(response: response, isPairwise: false)
self.formatPresentationResponse(response: response)
}.then { signedToken in
self.presentationApiCalls.sendResponse(usingUrl: response.audienceUrl, withBody: signedToken)
}
@ -197,26 +199,14 @@ public class PresentationService {
}
}
private func formatPresentationResponse(response: PresentationResponseContainer, isPairwise: Bool) -> Promise<PresentationResponse> {
private func formatPresentationResponse(response: PresentationResponseContainer) -> Promise<PresentationResponse> {
return Promise { seal in
do {
var identifier: Identifier?
let identifier = try identifierService.fetchOrCreateMasterIdentifier()
sdkLog.logVerbose(message: "Signing Presentation Response with Identifier")
if isPairwise {
// TODO: will change when deterministic key generation is implemented.
identifier = try identifierService.fetchIdentifier(forId: VCEntitiesConstants.MASTER_ID, andRelyingParty: response.audienceDid)
} else {
identifier = try identifierService.fetchMasterIdentifier()
}
guard let id = identifier else {
throw PresentationServiceError.inputStringNotUri
}
sdkLog.logInfo(message: "Signing Presentation Response with Identifier")
seal.fulfill(try self.formatter.format(response: response, usingIdentifier: id))
seal.fulfill(try self.formatter.format(response: response, usingIdentifier: identifier))
} catch {
seal.reject(error)
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -8,61 +8,29 @@ import VCEntities
/// status of a successful initialization
public enum VCSDKInitStatus
{
case newMasterIdentifierCreated
case success
}
/// initialization errors.
public enum VCSDKInitError: Error {
case invalidKeys
}
/// Class used to Initialize the SDK.
public class VerifiableCredentialSDK {
public static let identifierService = IdentifierService()
/// Initialized the SDK.
/// Returns: TRUE, if needed to create Master Identifier
/// FALSE, if Master Identifier is able to be fetched (included private keys from KeyStore)
/// Returns: Result<VCSDKInitStatus>, if successfully initialized the SDK.
/// Result<Error>, if there was an error, and unable to initialize SDK.
public static func initialize(logConsumer: VCLogConsumer = DefaultVCLogConsumer(),
accessGroupIdentifier: String? = nil) -> Result<VCSDKInitStatus, Error> {
/// Step 1: Add Log to VCSDKLog shared instance.
VCSDKLog.sharedInstance.add(consumer: logConsumer)
/// Get access group identifier for app.
/// Step 2: Set access group identifier for key chain.
if let accessGroupIdentifier = accessGroupIdentifier {
VCSDKConfiguration.sharedInstance.setAccessGroupIdentifier(with: accessGroupIdentifier)
}
/// Try to fetch master identifier from storage.
do {
_ = try identifierService.fetchMasterIdentifier()
if try !identifierService.areKeysValid() {
return .failure(VCSDKInitError.invalidKeys)
}
} catch {
/// If unable to fetch master identifier, create a new one.
VCSDKLog.sharedInstance.logWarning(message: "Failed to fetch master identifier with: \(String(describing: error))")
return createNewIdentifier()
}
/// VC sdk initialization successful.
/// Step 4: return success.
return .success(.success)
}
private static func createNewIdentifier() -> Result<VCSDKInitStatus, Error> {
do {
_ = try identifierService.createAndSaveIdentifier(forId: VCEntitiesConstants.MASTER_ID,
andRelyingParty: VCEntitiesConstants.MASTER_ID)
return .success(.newMasterIdentifierCreated)
} catch {
return .failure(error)
}
}
}

Просмотреть файл

@ -40,7 +40,10 @@ public class CoreDataManager {
signingKeyId: UUID,
recoveryKeyId: UUID,
updateKeyId: UUID,
alias: String) throws {
alias: String,
signingKeyAlias: String? = nil,
recoveryKeyAlias: String? = nil,
updateKeyAlias: String? = nil) throws {
guard let persistentContainer = persistentContainer else {
throw CoreDataManagerError.persistentStoreNotLoaded
}
@ -53,6 +56,9 @@ public class CoreDataManager {
model.signingKeyId = signingKeyId
model.updateKeyId = updateKeyId
model.alias = alias
model.signingKeyAlias = signingKeyAlias
model.recoveryKeyAlias = recoveryKeyAlias
model.updateKeyAlias = updateKeyAlias
try persistentContainer.viewContext.save()
}
@ -81,6 +87,10 @@ public class CoreDataManager {
try persistentContainer.viewContext.save()
}
public func deleteIdentifer(_ model:IdentifierModel) {
persistentContainer?.viewContext.delete(model)
}
private func loadPersistentContainer(sdkLog: VCSDKLog) {
let messageKitBundle = Bundle(for: Self.self)

Просмотреть файл

@ -32,7 +32,6 @@ struct IdentifierDatabase {
func saveIdentifier(identifier: Identifier) throws {
/// signing key is always first key in DID document keys until we implement more complex registration scenario.
guard let signingKey = identifier.didDocumentKeys.first else {
throw IdentifierDatabaseError.unableToSaveIdentifier
@ -42,7 +41,10 @@ struct IdentifierDatabase {
signingKeyId: signingKey.getId(),
recoveryKeyId: identifier.recoveryKey.getId(),
updateKeyId: identifier.updateKey.getId(),
alias: identifier.alias)
alias: identifier.alias,
signingKeyAlias: signingKey.keyId,
recoveryKeyAlias: identifier.recoveryKey.keyId,
updateKeyAlias: identifier.updateKey.keyId)
}
func fetchMasterIdentifier() throws -> Identifier {
@ -88,6 +90,40 @@ struct IdentifierDatabase {
return try createIdentifier(fromIdentifierModel: model)
}
func fetchAllIdentifiers() throws -> [Identifier] {
return try coreDataManager.fetchIdentifiers().map {
try createIdentifier(fromIdentifierModel:$0)
}
}
func removeAllIdentifiers() throws {
try coreDataManager.fetchIdentifiers().forEach { identifierModel in
// Step 1: remove the keys corresponding to each identifier
let keyIds = [identifierModel.signingKeyId, identifierModel.updateKeyId, identifierModel.recoveryKeyId]
try keyIds.forEach { keyId in
if let uuid = keyId {
try cryptoOperations.deleteKey(withId: uuid)
}
}
// Step 2: delete the identifier
coreDataManager.deleteIdentifer(identifierModel)
}
}
func importIdentifier(identifier: Identifier) throws {
// Put the keys in the keychain
let keyContainers = identifier.didDocumentKeys + [identifier.updateKey, identifier.recoveryKey]
try keyContainers.forEach { keyContainer in
let secret = try EphemeralSecret(with: keyContainer.keyReference)
try cryptoOperations.save(key: secret.value, withId: keyContainer.keyReference.id)
}
try self.saveIdentifier(identifier: identifier)
}
private func createIdentifier(fromIdentifierModel model: IdentifierModel) throws -> Identifier {
guard let longFormDid = model.did,
@ -95,9 +131,12 @@ struct IdentifierDatabase {
throw IdentifierDatabaseError.noAliasSavedInIdentifierModel
}
let signingKeyContainer = try createKeyContainer(keyRefId: model.signingKeyId, keyId: VCEntitiesConstants.SIGNING_KEYID_PREFIX + alias)
let updateKeyContainer = try createKeyContainer(keyRefId: model.updateKeyId, keyId: VCEntitiesConstants.UPDATE_KEYID_PREFIX + alias)
let recoveryKeyContainer = try createKeyContainer(keyRefId: model.recoveryKeyId, keyId: VCEntitiesConstants.RECOVER_KEYID_PREFIX + alias)
let signingKeyAlias = model.signingKeyAlias ?? VCEntitiesConstants.SIGNING_KEYID_PREFIX + alias
let signingKeyContainer = try createKeyContainer(keyRefId: model.signingKeyId, keyId: signingKeyAlias)
let updateKeyAlias = model.updateKeyAlias ?? VCEntitiesConstants.UPDATE_KEYID_PREFIX + alias
let updateKeyContainer = try createKeyContainer(keyRefId: model.updateKeyId, keyId: updateKeyAlias)
let recoveryKeyAlias = model.recoveryKeyAlias ?? VCEntitiesConstants.RECOVER_KEYID_PREFIX + alias
let recoveryKeyContainer = try createKeyContainer(keyRefId: model.recoveryKeyId, keyId: recoveryKeyAlias)
return Identifier(longFormDid: longFormDid,
didDocumentKeys: [signingKeyContainer],

Просмотреть файл

@ -1,13 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D91" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21E258" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="IdentifierModel" representedClassName="IdentifierModel" syncable="YES" codeGenerationType="class">
<attribute name="alias" optional="YES" attributeType="String"/>
<attribute name="did" optional="YES" attributeType="String"/>
<attribute name="recoveryKeyAlias" optional="YES" attributeType="String"/>
<attribute name="recoveryKeyId" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="signingKeyAlias" optional="YES" attributeType="String"/>
<attribute name="signingKeyId" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="updateKeyAlias" optional="YES" attributeType="String"/>
<attribute name="updateKeyId" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
</entity>
<elements>
<element name="IdentifierModel" positionX="-63" positionY="-18" width="128" height="104"/>
<element name="IdentifierModel" positionX="-63" positionY="-18" width="128" height="149"/>
</elements>
</model>

Просмотреть файл

@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import XCTest
@testable import VCServices
class DifWordListTests: XCTestCase {
var fixture: DifWordList? = nil
override func setUpWithError() throws {
self.fixture = try DifWordList()
}
func testDifWordListRandomSelection() throws {
let count = 12;
guard let words = fixture?.randomWords(count: UInt32(count)) else {
XCTFail("Failed to generate a random sequence of words using \(String(describing: fixture))")
return
}
NSLog("Words: %@", words)
// Validate that the returned list contains the specified number of words
// with no repetition
let set = Set(words)
XCTAssertEqual(set.count, count, "Unexpected number of words in generated password")
}
func testDifWordsListUpperBounds() throws {
// Get all the words from the list
guard let words1 = fixture?.randomWords(count: UInt32.max) else {
XCTFail("Failed to generate a random sequence of words using \(String(describing: fixture))")
return
}
// Do it again
guard let words2 = fixture?.randomWords(count: UInt32.max) else {
XCTFail("Failed to generate a random sequence of words using \(String(describing: fixture))")
return
}
// Ensure that they are not the same
let separator = " "
XCTAssertNotEqual(words1.joined(separator: separator), words2.joined(separator: separator))
}
func testDifWordListLowerBounds() throws {
let zero = 0
guard let words = fixture?.randomWords(count: UInt32(zero)) else {
XCTFail("Failed to generate an empty sequence of words using \(String(describing: fixture))")
return
}
XCTAssertEqual(words.count, zero)
}
}

Просмотреть файл

@ -9,11 +9,11 @@ struct MockVCCryptoSecret: VCCryptoSecret {
var accessGroup: String? = nil
func isValidKey() -> Bool {
return true
}
func isValidKey() throws { }
func migrateKey(fromAccessGroup oldAccessGroup: String?) throws { }
var id: UUID = UUID()
func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> Void) throws { }
}

Просмотреть файл

@ -23,4 +23,5 @@ internal class SecretStoreMock: SecretStoring {
print("deletingSecret... " + id.uuidString)
memoryStore.removeValue(forKey: id)
}
}

Просмотреть файл

@ -7,6 +7,11 @@
objects = {
/* Begin PBXBuildFile section */
2F848B66282D5236005D3176 /* PbesJwe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B65282D5236005D3176 /* PbesJwe.swift */; };
2F848B6B282D5266005D3176 /* JweEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B67282D5266005D3176 /* JweEncoder.swift */; };
2F848B6C282D5266005D3176 /* Jwk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B68282D5266005D3176 /* Jwk.swift */; };
2F848B6D282D5266005D3176 /* JweToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B69282D5266005D3176 /* JweToken.swift */; };
2F848B6E282D5266005D3176 /* JweDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F848B6A282D5266005D3176 /* JweDecoder.swift */; };
550F1E56251019BA009AF467 /* KeyId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550F1E55251019BA009AF467 /* KeyId.swift */; };
550F1E6125115866009AF467 /* ECPublicJwkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550F1E6025115866009AF467 /* ECPublicJwkTests.swift */; };
55269351250006020051A358 /* VCTokenError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55269350250006020051A358 /* VCTokenError.swift */; };
@ -45,6 +50,11 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
2F848B65282D5236005D3176 /* PbesJwe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PbesJwe.swift; sourceTree = "<group>"; };
2F848B67282D5266005D3176 /* JweEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JweEncoder.swift; sourceTree = "<group>"; };
2F848B68282D5266005D3176 /* Jwk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Jwk.swift; sourceTree = "<group>"; };
2F848B69282D5266005D3176 /* JweToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JweToken.swift; sourceTree = "<group>"; };
2F848B6A282D5266005D3176 /* JweDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JweDecoder.swift; sourceTree = "<group>"; };
550F1E55251019BA009AF467 /* KeyId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyId.swift; sourceTree = "<group>"; };
550F1E6025115866009AF467 /* ECPublicJwkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECPublicJwkTests.swift; sourceTree = "<group>"; };
55269350250006020051A358 /* VCTokenError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCTokenError.swift; sourceTree = "<group>"; };
@ -101,6 +111,7 @@
55ADE27A24F5410300D9990E /* Secp256k1Signer.swift */,
55C6C4A5250816500082BE73 /* Secp256k1Verifier.swift */,
5540904A2500311E001246DB /* ECPublicJwk.swift */,
2F848B65282D5236005D3176 /* PbesJwe.swift */,
);
path = algorithms;
sourceTree = "<group>";
@ -128,6 +139,10 @@
isa = PBXGroup;
children = (
5526934F24FF32850051A358 /* algorithms */,
2F848B6A282D5266005D3176 /* JweDecoder.swift */,
2F848B67282D5266005D3176 /* JweEncoder.swift */,
2F848B69282D5266005D3176 /* JweToken.swift */,
2F848B68282D5266005D3176 /* Jwk.swift */,
55ADE27824F540F700D9990E /* JwsToken.swift */,
55ADE27C24F5410E00D9990E /* Claims.swift */,
55ADE27124F5407D00D9990E /* Header.swift */,
@ -286,17 +301,22 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2F848B6D282D5266005D3176 /* JweToken.swift in Sources */,
55ADE27D24F5410E00D9990E /* Claims.swift in Sources */,
5540904B2500311E001246DB /* ECPublicJwk.swift in Sources */,
55C6C4A6250816500082BE73 /* Secp256k1Verifier.swift in Sources */,
55ADE27924F540F700D9990E /* JwsToken.swift in Sources */,
2F848B6C282D5266005D3176 /* Jwk.swift in Sources */,
55ADE27224F5407D00D9990E /* Header.swift in Sources */,
2F848B6B282D5266005D3176 /* JweEncoder.swift in Sources */,
55269351250006020051A358 /* VCTokenError.swift in Sources */,
55ADE28524F5412900D9990E /* TokenVerifying.swift in Sources */,
55ADE28124F5412100D9990E /* JwsDecoder.swift in Sources */,
550F1E56251019BA009AF467 /* KeyId.swift in Sources */,
55ADE27F24F5411E00D9990E /* JwsEncoder.swift in Sources */,
2F848B6E282D5266005D3176 /* JweDecoder.swift in Sources */,
55ADE27B24F5410400D9990E /* Secp256k1Signer.swift in Sources */,
2F848B66282D5236005D3176 /* PbesJwe.swift in Sources */,
55ADE28324F5412600D9990E /* TokenSigning.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

Просмотреть файл

@ -6,17 +6,29 @@
public struct Header: Codable {
public let type: String?
public let algorithm: String?
public let encryptionMethod: String?
public let jsonWebKey: String?
public let keyId: String?
public let contentType: String?
public let pbes2SaltInput: String?
public let pbes2Count: UInt?
public init(type: String? = nil,
algorithm: String? = nil,
encryptionMethod: String? = nil,
jsonWebKey: String? = nil,
keyId: String? = nil) {
keyId: String? = nil,
contentType: String? = nil,
pbes2SaltInput: String? = nil,
pbes2Count: UInt? = nil) {
self.type = type
self.algorithm = algorithm
self.encryptionMethod = encryptionMethod
self.jsonWebKey = jsonWebKey
self.keyId = keyId
self.contentType = contentType
self.pbes2SaltInput = pbes2SaltInput
self.pbes2Count = pbes2Count
}
public enum CodingKeys: String, CodingKey {
@ -24,6 +36,10 @@ public struct Header: Codable {
case algorithm = "alg"
case jsonWebKey = "jwk"
case keyId = "kid"
case contentType = "cty"
case encryptionMethod = "enc"
case pbes2SaltInput = "p2s"
case pbes2Count = "p2c"
}
// Note: implementing decode and encode to work around a compiler issue causing a EXC_BAD_ACCESS.
@ -32,15 +48,23 @@ public struct Header: Codable {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decodeIfPresent(String.self, forKey: .type)
algorithm = try values.decodeIfPresent(String.self, forKey: .algorithm)
encryptionMethod = try values.decodeIfPresent(String.self, forKey: .encryptionMethod)
jsonWebKey = try values.decodeIfPresent(String.self, forKey: .jsonWebKey)
keyId = try values.decodeIfPresent(String.self, forKey: .keyId)
contentType = try values.decodeIfPresent(String.self, forKey: .contentType)
pbes2SaltInput = try values.decodeIfPresent(String.self, forKey: .pbes2SaltInput)
pbes2Count = try values.decodeIfPresent(UInt.self, forKey: .pbes2Count)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(type, forKey: .type)
try container.encodeIfPresent(algorithm, forKey: .algorithm)
try container.encodeIfPresent(encryptionMethod, forKey: .encryptionMethod)
try container.encodeIfPresent(jsonWebKey, forKey: .jsonWebKey)
try container.encodeIfPresent(keyId, forKey: .keyId)
try container.encodeIfPresent(contentType, forKey: .contentType)
try container.encodeIfPresent(pbes2SaltInput, forKey: .pbes2SaltInput)
try container.encodeIfPresent(pbes2Count, forKey: .pbes2Count)
}
}

Просмотреть файл

@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
enum JweDecoderError: Error {
case unsupportedEncodingFormat
case unableToInitializeJweToken
}
public class JweDecoder {
private let json = JSONDecoder()
public init() {}
public func decode(token: String) throws -> JweToken {
let components = token.components(separatedBy: ".")
guard components.count == 5 else {
throw JweDecoderError.unsupportedEncodingFormat
}
guard let headerData = Data(base64URLEncoded: components[0]) else {
throw VCTokenError.unableToParseData
}
let headers = try json.decode(Header.self, from: headerData)
guard let aad = components[0].data(using: .nonLossyASCII) else {
throw VCTokenError.unableToParseData
}
guard let encryptedCek = Data(base64URLEncoded: components[1]) else {
throw VCTokenError.unableToParseData
}
guard let iv = Data(base64URLEncoded: components[2]) else {
throw VCTokenError.unableToParseData
}
guard let cipherText = Data(base64URLEncoded: components[3]) else {
throw VCTokenError.unableToParseData
}
guard let authenticationTag = Data(base64URLEncoded: components[4]) else {
throw VCTokenError.unableToParseData
}
// Wrap it all up
return JweToken(headers: headers, aad: aad, encryptedKey: encryptedCek, iv: iv, ciperText: cipherText, authenticationTag: authenticationTag)
}
}

Просмотреть файл

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
public enum JweFormat {
case compact
}
public class JweEncoder {
private let json = JSONEncoder()
public init() {}
public func encode(_ token: JweToken, format: JweFormat = JweFormat.compact) throws -> String {
switch format {
case .compact:
return try encodeUsingCompactFormat(token: token)
}
}
private func encodeUsingCompactFormat(token: JweToken) throws -> String {
var encoded = try json.encode(token.headers).base64URLEncodedString()
encoded.append(".")
encoded.append(token.encryptedKey.base64URLEncodedString())
encoded.append(".")
encoded.append(token.iv.base64URLEncodedString())
encoded.append(".")
encoded.append(token.ciperText.base64URLEncodedString())
encoded.append(".")
encoded.append(token.authenticationTag.base64URLEncodedString())
return encoded
}
}

Просмотреть файл

@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
public struct JweToken {
public let headers: Header
public let aad: Data
public let encryptedKey: Data
public let iv: Data
public let ciperText: Data
public let authenticationTag: Data
}

84
VCToken/VCToken/Jwk.swift Normal file
Просмотреть файл

@ -0,0 +1,84 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
enum JwkError: Error {
case invalidKeyValue
}
/// Runtime container for JSON Web Keys, in which key material is Base64URLEncoded
public struct Jwk: Codable, Equatable {
public let keyType: String
public let keyId: String?
public let key: Data?
public let curve: String?
public let use: String?
public let x: Data?
public let y: Data?
public let d: Data?
enum CodingKeys: String, CodingKey {
case keyType = "kty"
case keyId = "kid"
case key = "k"
case curve = "crv"
case use, x, y, d
}
public init(keyType: String,
keyId: String? = nil,
key: Data? = nil,
curve: String? = nil,
use: String? = nil,
x: Data? = nil,
y: Data? = nil,
d: Data? = nil) {
self.keyType = keyType
self.keyId = keyId
self.key = key
self.curve = curve
self.use = use
self.x = x
self.y = y
self.d = d
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
func parseKeyIfPresent(_ key: KeyedDecodingContainer<Jwk.CodingKeys>.Key) throws -> Data? {
if let base64 = try values.decodeIfPresent(String.self, forKey: key) {
guard let data = Data(base64URLEncoded: base64) else {
throw JwkError.invalidKeyValue
}
return data
}
return nil
}
keyType = try values.decode(String.self, forKey: .keyType)
keyId = try values.decodeIfPresent(String.self, forKey: .keyId)
key = try parseKeyIfPresent(.key)
curve = try values.decodeIfPresent(String.self, forKey: .curve)
use = try values.decodeIfPresent(String.self, forKey: .use)
x = try parseKeyIfPresent(.x)
y = try parseKeyIfPresent(.y)
d = try parseKeyIfPresent(.d)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(self.keyType, forKey: .keyType)
try container.encodeIfPresent(self.keyId, forKey: .keyId)
try container.encodeIfPresent(self.key?.base64URLEncodedString(), forKey: .key)
try container.encodeIfPresent(self.curve, forKey: .curve)
try container.encodeIfPresent(self.use, forKey: .use)
try container.encodeIfPresent(self.x?.base64URLEncodedString(), forKey: .x)
try container.encodeIfPresent(self.y?.base64URLEncodedString(), forKey: .y)
try container.encodeIfPresent(self.d?.base64URLEncodedString(), forKey: .d)
}
}

Просмотреть файл

@ -15,9 +15,9 @@ public struct KeyId: VCCryptoSecret {
self.id = id
}
public func isValidKey() -> Bool {
return true
}
public func isValidKey() throws { }
public func migrateKey(fromAccessGroup oldAccessGroup: String?) throws { }
public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> Void) throws { }
}

Просмотреть файл

@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import Foundation
import VCCrypto
enum PbesJweError: Error {
case encodingError
case invalidSaltInput
case invalidAlgorithm
case invalidEncryptionMethod
case unauthenticatable
}
/// Implementation of password-based encryption scheme for JSON Web Encryption
public struct PbesJwe {
private struct Constants {
static let InitializationVectorByteSize = 16
}
private let aes = Aes()
private let pbkdf = Pbkdf()
public init() {}
public func encrypt(_ plainText: Data, with password: String, using headers: Header) throws -> JweToken {
// Generate a content-encryption key
guard let method = headers.encryptionMethod else {
throw PbesJweError.invalidEncryptionMethod
}
let (_, keySize) = try HmacSha2AesCbc.props(for: method)
let cek = try EphemeralSecret(size: (keySize*2))
let keys = (cek.prefix(keySize), cek.suffix(keySize))
// Generate the additional authentication data, and the initialization vector, respectively
guard let aad = try JSONEncoder().encode(headers).base64URLEncodedString().data(using: .nonLossyASCII) else {
throw PbesJweError.encodingError
}
let iv = try EphemeralSecret(size: Constants.InitializationVectorByteSize)
// Generate the cipher text and message authentication code
guard let alg = headers.algorithm else {
throw PbesJweError.invalidAlgorithm
}
let hmac = try HmacSha2AesCbc(methodName: method)
let (cipherText, mac) = try hmac.encrypt(plainText: plainText, using: aad, iv: iv.value, with: keys)
// Derive the key-encrypting key from the password
guard let p2c = headers.pbes2Count,
let p2s = headers.pbes2SaltInput else {
throw PbesJweError.invalidSaltInput
}
let kek = try pbkdf.derive(from: password, withSaltInput: Data(base64URLEncoded: p2s)!, forAlgorithm: alg, rounds: UInt32(p2c))
// Wrap the content-encryption key with the key-encryption key
let encryptedKey = try aes.wrap(key: cek, with: kek)
// Pull it all together
let token = JweToken(headers: headers, aad: aad, encryptedKey: encryptedKey, iv: iv.value, ciperText: cipherText, authenticationTag: mac)
return token
}
public func decrypt(_ token: JweToken, with password: String) throws -> Data {
// Derive the key-encrypting key from the password
guard let alg = token.headers.algorithm else {
throw PbesJweError.invalidAlgorithm
}
guard let p2c = token.headers.pbes2Count,
let p2s = token.headers.pbes2SaltInput else {
throw PbesJweError.invalidSaltInput
}
let kek = try pbkdf.derive(from: password, withSaltInput: Data(base64URLEncoded: p2s)!, forAlgorithm: alg, rounds: UInt32(p2c))
// Unwrap the content-encryption key
guard let method = token.headers.encryptionMethod else {
throw PbesJweError.invalidEncryptionMethod
}
let (_, keySize) = try HmacSha2AesCbc.props(for: method)
let unwrapped = try aes.unwrap(wrapped: token.encryptedKey, using: kek)
let cek = try EphemeralSecret(with: unwrapped)
let keys = (cek.prefix(keySize), cek.suffix(keySize))
// Authenticate and decrypt the plaintext
let hmac = try HmacSha2AesCbc(methodName: method)
let plainText = try hmac.decrypt((token.ciperText, token.authenticationTag), using: token.aad, iv: token.iv, with: keys)
return plainText
}
}

Просмотреть файл

@ -14,13 +14,13 @@ struct MockVCCryptoSecret: VCCryptoSecret {
let accessGroup: String? = nil
func isValidKey() -> Bool {
true
}
func isValidKey() throws { }
func migrateKey(fromAccessGroup oldAccessGroup: String?) throws {}
let id: UUID
func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> Void) throws { }
}
struct MockAlgorithm: Signing {