Add support for serialising verifiable credentials

with password-based encryption
This commit is contained in:
Stephen Higgins 2022-04-26 15:13:03 +01:00
Родитель 30e572634e
Коммит ebbb771f3f
48 изменённых файлов: 1673 добавлений и 48 удалений

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

@ -7,6 +7,13 @@
objects = {
/* Begin PBXBuildFile section */
2F23154D27F708A000A35917 /* HmacSha2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F23154C27F708A000A35917 /* HmacSha2.swift */; };
2F23154F27F711B400A35917 /* HmacSha2AesCbc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F23154E27F711B400A35917 /* HmacSha2AesCbc.swift */; };
2FA3E3CB27F1EA7F0008F7C3 /* EphemeralSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA3E3CA27F1EA7F0008F7C3 /* EphemeralSecret.swift */; };
2FA3E3CF27F1F36C0008F7C3 /* Pbkdf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA3E3CE27F1F36C0008F7C3 /* Pbkdf.swift */; };
2FA3E3D127F208A80008F7C3 /* PbkdfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA3E3D027F208A80008F7C3 /* PbkdfTests.swift */; };
2FD7F10C27F3588100B2C05A /* Aes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD7F10B27F3588100B2C05A /* Aes.swift */; };
2FD7F10E27F3673500B2C05A /* AesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD7F10D27F3673500B2C05A /* AesTests.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 */
2F23154C27F708A000A35917 /* HmacSha2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HmacSha2.swift; sourceTree = "<group>"; };
2F23154E27F711B400A35917 /* HmacSha2AesCbc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HmacSha2AesCbc.swift; sourceTree = "<group>"; };
2FA3E3CA27F1EA7F0008F7C3 /* EphemeralSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralSecret.swift; sourceTree = "<group>"; };
2FA3E3CE27F1F36C0008F7C3 /* Pbkdf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pbkdf.swift; sourceTree = "<group>"; };
2FA3E3D027F208A80008F7C3 /* PbkdfTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PbkdfTests.swift; sourceTree = "<group>"; };
2FD7F10B27F3588100B2C05A /* Aes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Aes.swift; sourceTree = "<group>"; };
2FD7F10D27F3673500B2C05A /* AesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AesTests.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>"; };
@ -134,6 +148,7 @@
92CDE80524E1ABEA00F95B5D /* Random32BytesSecret.swift */,
92CDE80724E1F7AF00F95B5D /* Secret.swift */,
928AAF7024EEDC9D0029A8F9 /* Secp256k1PublicKey.swift */,
2FA3E3CA27F1EA7F0008F7C3 /* EphemeralSecret.swift */,
);
path = Keys;
sourceTree = "<group>";
@ -186,6 +201,8 @@
928AAF7224EEE0AB0029A8F9 /* Secp256k1PublicKeyTests.swift */,
92CDE7E724DE1A2000F95B5D /* KeychainSecretStoreTests.swift */,
92CDE80B24E213AE00F95B5D /* Random32BytesSecretTests.swift */,
2FA3E3D027F208A80008F7C3 /* PbkdfTests.swift */,
2FD7F10D27F3673500B2C05A /* AesTests.swift */,
);
path = VCCryptoTests;
sourceTree = "<group>";
@ -207,6 +224,10 @@
92CDE7DA24DDC65100F95B5D /* Sha512.swift */,
55ADE29824F5752500D9990E /* Sha256.swift */,
5540903C25000F6A001246DB /* Signing.swift */,
2FA3E3CE27F1F36C0008F7C3 /* Pbkdf.swift */,
2FD7F10B27F3588100B2C05A /* Aes.swift */,
2F23154C27F708A000A35917 /* HmacSha2.swift */,
2F23154E27F711B400A35917 /* HmacSha2AesCbc.swift */,
);
path = Algo;
sourceTree = "<group>";
@ -402,11 +423,15 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2FA3E3CF27F1F36C0008F7C3 /* Pbkdf.swift in Sources */,
92CDE7DB24DDC65100F95B5D /* Sha512.swift in Sources */,
92CDE7C924DCA48A00F95B5D /* SecretStoring.swift in Sources */,
551F3059252E378E0081D5E7 /* CryptoOperations.swift in Sources */,
92CDE7E624DE109200F95B5D /* Data+Base64.swift in Sources */,
2F23154D27F708A000A35917 /* HmacSha2.swift in Sources */,
92CDE7CB24DCA61800F95B5D /* KeychainSecretStore.swift in Sources */,
2FD7F10C27F3588100B2C05A /* Aes.swift in Sources */,
2FA3E3CB27F1EA7F0008F7C3 /* EphemeralSecret.swift in Sources */,
92CDE7DE24DDCB9500F95B5D /* Data+Hex.swift in Sources */,
92CDE80624E1ABEA00F95B5D /* Random32BytesSecret.swift in Sources */,
92CDE7D924DDBA0500F95B5D /* HmacSha512.swift in Sources */,
@ -416,6 +441,7 @@
92CDE7D224DCA6CF00F95B5D /* Secp256k1.swift in Sources */,
928AAF7124EEDC9D0029A8F9 /* Secp256k1PublicKey.swift in Sources */,
5540903D25000F6A001246DB /* Signing.swift in Sources */,
2F23154F27F711B400A35917 /* HmacSha2AesCbc.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -425,6 +451,8 @@
files = (
928AAF7324EEE0AB0029A8F9 /* Secp256k1PublicKeyTests.swift in Sources */,
92CDE7D724DCD07D00F95B5D /* Secp256k1Tests.swift in Sources */,
2FD7F10E27F3673500B2C05A /* AesTests.swift in Sources */,
2FA3E3D127F208A80008F7C3 /* PbkdfTests.swift in Sources */,
92CDE7D524DCCE3C00F95B5D /* SecretStoreMock.swift in Sources */,
92CDE80A24E2047900F95B5D /* SecretMock.swift in Sources */,
92CDE80C24E213AE00F95B5D /* Random32BytesSecretTests.swift in Sources */,

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

@ -0,0 +1,159 @@
/*---------------------------------------------------------------------------------------------
* 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 invalidSecret
case cryptoError(operation:CCOperation, status:CCCryptorStatus)
}
public struct Aes {
private let keyWrapAlg = CCWrappingAlgorithm(kCCWRAPAES)
public init() {}
public func wrap(key: VCCryptoSecret, with kek: VCCryptoSecret) throws -> Data {
// Look for an early out
guard key is Secret, kek is Secret else {
throw AesError.invalidSecret
}
var wrappedSize: Int = 0
try (key as! Secret).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 as! Secret).withUnsafeBytes { (keyPtr: UnsafeRawBufferPointer) in
let keyBytes = keyPtr.bindMemory(to: UInt8.self)
try (kek as! Secret).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 {
// Look for an early out
guard kek is Secret else {
throw AesError.invalidSecret
}
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 as! Secret).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)
}
// AES processes inputs in blocks of 128 bits (16 bytes)
internal let blockSize = size_t(16)
private func apply(operation: CCOperation, withOptions options:CCOptions, to data: Data, using key: VCCryptoSecret, iv: Data) throws -> Data {
// Look for an early out
guard key is Secret else { throw AesError.invalidSecret }
// 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 as! Secret).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,71 @@
/*---------------------------------------------------------------------------------------------
* 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 invalidSecret
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 }
guard secret is Secret else { throw HmacSha2Error.invalidSecret }
// 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 as! 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,9 @@
import Foundation
import CommonCrypto
enum HmacSha512Error: Error {
case invalidMessage
case invalidSecret
}
public struct HmacSha512 {
public init() { }
/// Authenticate a message
/// - Parameters:
@ -19,17 +16,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 +27,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,9 @@
//
// Pbes2HmacSha2Aes.swift
// VCCrypto
//
// Created by Stephen Higgins on 01/04/2022.
// Copyright © 2022 Daniel Godbout. All rights reserved.
//
import Foundation

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

@ -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)
}
}

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

@ -6,6 +6,8 @@
import Foundation
import Secp256k1
public typealias Secp256k1KeyPair = (publicKey: Secp256k1PublicKey, privateKey: VCCryptoSecret)
enum Secp256k1Error: Error {
case invalidMessageHash
case invalidSecretKey

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

@ -4,13 +4,16 @@
*--------------------------------------------------------------------------------------------*/
public protocol CryptoOperating {
var secretStore: SecretStoring { get }
func generateKey() throws -> VCCryptoSecret
func retrieveKeyFromStorage(withId id: UUID) -> VCCryptoSecret
func retrieveKeyIfStored(uuid: UUID) throws -> VCCryptoSecret?
}
public struct CryptoOperations: CryptoOperating {
private let secretStore: SecretStoring
public let secretStore: SecretStoring
private let sdkConfiguration: VCSDKConfigurable
@ -33,4 +36,21 @@ public struct CryptoOperations: CryptoOperating {
let accessGroup = sdkConfiguration.accessGroupIdentifier
return Random32BytesSecret(withStore: secretStore, andId: id, inAccessGroup: accessGroup)
}
/// Tests if a key corresponding to the given id is stored and, if it is, returns a reference to it; returns nil otherwise
public func retrieveKeyIfStored(uuid: UUID) throws -> VCCryptoSecret? {
let accessGroup = sdkConfiguration.accessGroupIdentifier
var keyRef: VCCryptoSecret? = nil
do {
let _ = try secretStore.getSecret(id: uuid,
itemTypeCode: Random32BytesSecret.itemTypeCode,
accessGroup: accessGroup)
keyRef = Random32BytesSecret(withStore: secretStore, andId: uuid)
}
catch SecretStoringError.itemNotFound {
keyRef = nil
}
return keyRef
}
}

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

@ -8,11 +8,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 {
@ -48,9 +44,9 @@ struct KeychainSecretStore : SecretStoring {
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status != errSecItemNotFound else { throw KeychainStoreError.itemNotFound }
guard status != errSecItemNotFound else { throw SecretStoringError.itemNotFound }
guard status == errSecSuccess else { throw KeychainStoreError.readFromStoreError(status: status as OSStatus) }
guard var value = item as? Data else { throw KeychainStoreError.invalidItemInStore }
guard var value = item as? Data else { throw SecretStoringError.invalidItemInStore }
defer {
let secretSize = value.count
value.withUnsafeMutableBytes { (secretPtr) in
@ -78,7 +74,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
@ -116,7 +112,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 +129,52 @@ struct KeychainSecretStore : SecretStoring {
}
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess else {
guard status == errSecSuccess || status == errSecItemNotFound else {
throw KeychainStoreError.deleteFromStoreError(status: status)
}
}
/// Save the secret to keychain
/// - Parameters:
/// - secret: the secret container
func save(secret: VCCryptoSecret) throws {
let (ephemeral, itemTypeCode) = try Self.secretDataAndItemTypeCodeFor(secret: secret)
var data = Data()
data.append(ephemeral.value)
try self.saveSecret(id: secret.id,
itemTypeCode: itemTypeCode,
accessGroup: secret.accessGroup,
value: &data)
}
/// Remove a secret from the keychain
/// - Parameters:
/// - secret: the secret reference
func delete(secret: VCCryptoSecret) throws {
let (_, itemTypeCode) = try Self.secretDataAndItemTypeCodeFor(secret: secret)
try self.deleteSecret(id: secret.id,
itemTypeCode: itemTypeCode,
accessGroup: secret.accessGroup)
}
private static func secretDataAndItemTypeCodeFor(secret: VCCryptoSecret) throws -> (EphemeralSecret, String) {
// Get out the secret data
let ephemeral = try EphemeralSecret(with: secret)
// Figure out the item type code
var itemTypeCode: String = ""
if let internalSecret = secret as? Secret {
itemTypeCode = type(of: internalSecret).self.itemTypeCode
}
if itemTypeCode == "" {
// Fallback
itemTypeCode = String(format: "r%02dB", ephemeral.value.count)
}
// Wrap up and return
return (ephemeral, itemTypeCode)
}
}

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

@ -0,0 +1,83 @@
/*---------------------------------------------------------------------------------------------
* 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 as! Secret).withUnsafeBytes { secretPtr in
value.append(secretPtr.bindMemory(to: UInt8.self))
}
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() -> Bool {
return true
}
public func migrateKey(fromAccessGroup currentAccessGroup: String?) throws {
self.accessGroup
}
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
}
}
}

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

@ -5,10 +5,21 @@
import Foundation
public enum SecretStoringError: Error {
case itemNotFound
case invalidItemInStore
case itemAlreadyInStore
case invalidType
case invalidSecret
}
// 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
func save(secret:VCCryptoSecret) throws
func delete(secret:VCCryptoSecret) 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 as! Secret).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.
}
}

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

@ -21,4 +21,13 @@ internal class SecretStoreMock: SecretStoring {
func deleteSecret(id: UUID, itemTypeCode: String, accessGroup: String?) throws {
memoryStore.removeValue(forKey: id)
}
func save(secret: VCCryptoSecret) throws {
let ephemeral = try EphemeralSecret(with: secret)
memoryStore[secret.id] = ephemeral.value
}
func delete(secret: VCCryptoSecret) throws {
memoryStore.removeValue(forKey: secret.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 as! Secret).withUnsafeBytes { (derivedPtr) in
data.append(derivedPtr.bindMemory(to: UInt8.self))
}
XCTAssertEqual(Data(expected), data)
}
}

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

@ -7,6 +7,17 @@
objects = {
/* Begin PBXBuildFile section */
2F357883280983A900997F31 /* VcMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F357882280983A900997F31 /* VcMetadata.swift */; };
2F53AA0E27E0CEC800C7C81E /* ProtectedBackupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F53AA0D27E0CEC800C7C81E /* ProtectedBackupData.swift */; };
2F53AA1027E0CF9700C7C81E /* UnprotectedBackupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F53AA0F27E0CF9700C7C81E /* UnprotectedBackupData.swift */; };
2F53AA1227E0E74500C7C81E /* BackupProtectionMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F53AA1127E0E74500C7C81E /* BackupProtectionMethod.swift */; };
2F58B92F27FDA78F001C6DFE /* JwePasswordProtectionMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F58B92E27FDA78F001C6DFE /* JwePasswordProtectionMethod.swift */; };
2F8CCDAF27FC6FF7004B7861 /* Microsoft2020UnprotectedBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F8CCDAE27FC6FF7004B7861 /* Microsoft2020UnprotectedBackup.swift */; };
2F8CCDB327FC7369004B7861 /* WalletMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F8CCDB227FC7369004B7861 /* WalletMetadata.swift */; };
2F8CCDB527FC745E004B7861 /* RawIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F8CCDB427FC745E004B7861 /* RawIdentity.swift */; };
2F8CCDBD27FC88D3004B7861 /* UnprotectedBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F8CCDBC27FC88D3004B7861 /* UnprotectedBackup.swift */; };
2FB0154D2805C75400E67168 /* JwePasswordProtectedBackupData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FB0154C2805C75400E67168 /* JwePasswordProtectedBackupData.swift */; };
2FD480112817CE99002AF37F /* Microsoft2020IdentifierBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD480102817CE98002AF37F /* Microsoft2020IdentifierBackup.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 */; };
@ -146,6 +157,17 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
2F357882280983A900997F31 /* VcMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VcMetadata.swift; sourceTree = "<group>"; };
2F53AA0D27E0CEC800C7C81E /* ProtectedBackupData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtectedBackupData.swift; sourceTree = "<group>"; };
2F53AA0F27E0CF9700C7C81E /* UnprotectedBackupData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnprotectedBackupData.swift; sourceTree = "<group>"; };
2F53AA1127E0E74500C7C81E /* BackupProtectionMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupProtectionMethod.swift; sourceTree = "<group>"; };
2F58B92E27FDA78F001C6DFE /* JwePasswordProtectionMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JwePasswordProtectionMethod.swift; sourceTree = "<group>"; };
2F8CCDAE27FC6FF7004B7861 /* Microsoft2020UnprotectedBackup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Microsoft2020UnprotectedBackup.swift; sourceTree = "<group>"; };
2F8CCDB227FC7369004B7861 /* WalletMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletMetadata.swift; sourceTree = "<group>"; };
2F8CCDB427FC745E004B7861 /* RawIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawIdentity.swift; sourceTree = "<group>"; };
2F8CCDBC27FC88D3004B7861 /* UnprotectedBackup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnprotectedBackup.swift; sourceTree = "<group>"; };
2FB0154C2805C75400E67168 /* JwePasswordProtectedBackupData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JwePasswordProtectedBackupData.swift; sourceTree = "<group>"; };
2FD480102817CE98002AF37F /* Microsoft2020IdentifierBackup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Microsoft2020IdentifierBackup.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>"; };
@ -301,6 +323,56 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2F53AA0A27E0CDC000C7C81E /* backup */ = {
isa = PBXGroup;
children = (
2F53AA0C27E0CEA200C7C81E /* content */,
2F53AA0B27E0CDCB00C7C81E /* container */,
2F8CCDBC27FC88D3004B7861 /* UnprotectedBackup.swift */,
);
path = backup;
sourceTree = "<group>";
};
2F53AA0B27E0CDCB00C7C81E /* container */ = {
isa = PBXGroup;
children = (
2F58B92D27FDA77B001C6DFE /* jwe */,
2F53AA1127E0E74500C7C81E /* BackupProtectionMethod.swift */,
);
path = container;
sourceTree = "<group>";
};
2F53AA0C27E0CEA200C7C81E /* content */ = {
isa = PBXGroup;
children = (
2F8CCDAB27FC6EE9004B7861 /* microsoft2020 */,
2F53AA0D27E0CEC800C7C81E /* ProtectedBackupData.swift */,
2F53AA0F27E0CF9700C7C81E /* UnprotectedBackupData.swift */,
);
path = content;
sourceTree = "<group>";
};
2F58B92D27FDA77B001C6DFE /* jwe */ = {
isa = PBXGroup;
children = (
2F58B92E27FDA78F001C6DFE /* JwePasswordProtectionMethod.swift */,
2FB0154C2805C75400E67168 /* JwePasswordProtectedBackupData.swift */,
);
path = jwe;
sourceTree = "<group>";
};
2F8CCDAB27FC6EE9004B7861 /* microsoft2020 */ = {
isa = PBXGroup;
children = (
2F8CCDAE27FC6FF7004B7861 /* Microsoft2020UnprotectedBackup.swift */,
2F8CCDB227FC7369004B7861 /* WalletMetadata.swift */,
2F357882280983A900997F31 /* VcMetadata.swift */,
2F8CCDB427FC745E004B7861 /* RawIdentity.swift */,
2FD480102817CE98002AF37F /* Microsoft2020IdentifierBackup.swift */,
);
path = microsoft2020;
sourceTree = "<group>";
};
55011E8A2572DF1C002D2690 /* logging */ = {
isa = PBXGroup;
children = (
@ -411,6 +483,7 @@
55575730251BC575009979AB /* VCEntities */ = {
isa = PBXGroup;
children = (
2F53AA0A27E0CDC000C7C81E /* backup */,
55B4D2D42787BB600086A9F1 /* oidc */,
5517D28A25B90D0A00FBD239 /* exchange */,
5573FEFD25B8DE6900282BC7 /* discovery */,
@ -830,6 +903,7 @@
buildActionMask = 2147483647;
files = (
55077ABA25A62DE10052C58D /* IdentifierFormatter.swift in Sources */,
2F58B92F27FDA78F001C6DFE /* JwePasswordProtectionMethod.swift in Sources */,
5518CC7625264D5700C7A21B /* ResponseMappings.swift in Sources */,
5573FEF525B8DE4A00282BC7 /* IONDocumentModel.swift in Sources */,
551F3068252E97000081D5E7 /* IdentifierCreator.swift in Sources */,
@ -837,6 +911,7 @@
55CA3A8127FB635300615CB6 /* VCSDKConfiguration.swift in Sources */,
5599D0CC26A07EE10037C131 /* IssuancePin.swift in Sources */,
559FC9AA25C9ABB500737F14 /* DomainLinkageCredentialValidator.swift in Sources */,
2F53AA1027E0CF9700C7C81E /* UnprotectedBackupData.swift in Sources */,
557BFC53251E665D005B5B24 /* IssuanceResponseFormatter.swift in Sources */,
5584E4A02525656500A9DE58 /* InputDescriptorSchema.swift in Sources */,
559FC9A625C8E42B00737F14 /* WellKnownConfigDocument.swift in Sources */,
@ -856,6 +931,7 @@
5573FF0025B8DE6900282BC7 /* IdentifierDocument.swift in Sources */,
5580588725BFB6C00048E6FA /* PinDescriptor.swift in Sources */,
5557576E251BC6CF009979AB /* ClaimDescriptor.swift in Sources */,
2F8CCDAF27FC6FF7004B7861 /* Microsoft2020UnprotectedBackup.swift in Sources */,
5557576B251BC6CF009979AB /* ContractInputDescriptor.swift in Sources */,
5573FEFA25B8DE5900282BC7 /* IONDocumentInitialState.swift in Sources */,
55575764251BC6CF009979AB /* PresentationDescriptor.swift in Sources */,
@ -864,6 +940,7 @@
55575767251BC6CF009979AB /* DisplayDescriptor.swift in Sources */,
55575769251BC6CF009979AB /* IssuerDescriptor.swift in Sources */,
55575778251BC6CF009979AB /* JSONCodingKeys.swift in Sources */,
2F53AA0E27E0CEC800C7C81E /* ProtectedBackupData.swift in Sources */,
5518CC7425264CAD00C7A21B /* PresentationResponseContainer.swift in Sources */,
5518CC7225264C6F00C7A21B /* PresentationResponseFormatter.swift in Sources */,
55AF7488252F6B53006A8B25 /* IdentifierDocumentSuffixDescriptor.swift in Sources */,
@ -875,6 +952,7 @@
5522D04F27DA727400617D15 /* AccessTokenDescriptor.swift in Sources */,
55575763251BC6CF009979AB /* AttestationsDescriptor.swift in Sources */,
559FCA5225CA042400737F14 /* LinkedDomainResult.swift in Sources */,
2FB0154D2805C75400E67168 /* JwePasswordProtectedBackupData.swift in Sources */,
55077ACA25A7763B0052C58D /* IdentifierDocumentPublicKey.swift in Sources */,
5573FF0125B8DE6900282BC7 /* DiscoveryServiceResponse.swift in Sources */,
559FCA3C25CA03D200737F14 /* IssuanceRequest.swift in Sources */,
@ -887,6 +965,7 @@
5573FEF625B8DE4A00282BC7 /* IONDocumentDeltaDescriptor.swift in Sources */,
5557576C251BC6CF009979AB /* CardDisplayDescriptor.swift in Sources */,
55AF748E252F6BE0006A8B25 /* Identifier.swift in Sources */,
2F8CCDBD27FC88D3004B7861 /* UnprotectedBackup.swift in Sources */,
55AF7486252F6B1F006A8B25 /* IdentifierDocumentServiceEndpoint.swift in Sources */,
55BEF45D2589544500C720BD /* PinClaims.swift in Sources */,
55A2CA6D266EA44B00AE1A20 /* MeasureTime.swift in Sources */,
@ -898,6 +977,7 @@
551F30432527DC050081D5E7 /* JwsHeaderFormatter .swift in Sources */,
55575765251BC6CF009979AB /* ConsentDisplayDescriptor.swift in Sources */,
5584E49C2525641600A9DE58 /* PresentationDefinition.swift in Sources */,
2F357883280983A900997F31 /* VcMetadata.swift in Sources */,
555CE08D2526810100C1C938 /* VerifiablePresentationClaims.swift in Sources */,
55793BCD255A070E007F7599 /* VCEntitiesConstants.swift in Sources */,
5584E4A2252565D900A9DE58 /* IssuanceMetadata.swift in Sources */,
@ -907,8 +987,10 @@
5517D28E25B90D0A00FBD239 /* ExchangeRequestClaims.swift in Sources */,
559FC99025C8BEEB00737F14 /* DomainLinkageCredential.swift in Sources */,
55379AD725A8DF7A0048600A /* PresentationRequestValidator.swift in Sources */,
2F8CCDB327FC7369004B7861 /* WalletMetadata.swift in Sources */,
55AC02B525D3087D00AA15F4 /* IdentifierDocumentServiceEndpointDescriptor.swift in Sources */,
55011E8E2572DFBD002D2690 /* DefaultLogConsumer.swift in Sources */,
2F53AA1227E0E74500C7C81E /* BackupProtectionMethod.swift in Sources */,
5557576D251BC6CF009979AB /* SelfIssuedClaimsDescriptor.swift in Sources */,
559FC96025C8940100737F14 /* DomainLinkageCredentialSubject.swift in Sources */,
55BEF461258A5E0400C720BD /* IssuerIdToken.swift in Sources */,
@ -917,6 +999,7 @@
55FE6ED82697A06D00201EDC /* IssuanceCompletionResponse.swift in Sources */,
55B4D2E02787BE5F0086A9F1 /* PresentationExchangeConstraints.swift in Sources */,
559FC9B825C9C7DD00737F14 /* ContractServiceResponse.swift in Sources */,
2F8CCDB527FC745E004B7861 /* RawIdentity.swift in Sources */,
5584E49E252564A600A9DE58 /* PresentationInputDescriptor.swift in Sources */,
5570597925754CA7000DD244 /* VCLogConsumer.swift in Sources */,
5531D3AC255F03EF0002CC0E /* AliasComputer.swift in Sources */,
@ -926,6 +1009,7 @@
55B4D2DA2787BC7D0086A9F1 /* RequestedClaims.swift in Sources */,
55B4D2D82787BC220086A9F1 /* SupportedVerifiablePresentationFormats.swift in Sources */,
559FC98C25C8BEC900737F14 /* DomainLinkageCredentialContent.swift in Sources */,
2FD480112817CE99002AF37F /* Microsoft2020IdentifierBackup.swift in Sources */,
55575775251BC6CF009979AB /* IssuanceResponseClaims.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

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

@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* 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 protocol UnprotectedBackup : Codable { }
/*
public typealias CredentialBackup = (credential: VerifiableCredential, metadata: VcMetadata)
public struct IdentifierBackup {
public var master: Identifier?
public var etc: [Identifier] = []
public init(withMasterIdentifer identifier:Identifier? = nil) {
self.master = identifier;
}
public mutating func addNonMaster(_ identifier:Identifier) {
if let master = self.master,
master.alias == identifier.alias {
return
}
etc.append(identifier)
}
public var all : [Identifier] {
if let master = self.master {
return [master] + self.etc
}
return self.etc
}
}
public struct UnprotectedBackup {
public var seed: Data
public var credentials: [CredentialBackup]
public var identifiers: IdentifierBackup
public init(seed: Data,
credentials: [CredentialBackup],
identifiers: IdentifierBackup) {
self.seed = seed
self.credentials = credentials
self.identifiers = identifiers
}
}
*/

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

@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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,60 @@
/*---------------------------------------------------------------------------------------------
* 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 {
static let OnTheWireMainIdentifier = "did.main.identifier"
}
public var master: Identifier?
public var etc: [Identifier]
public var all: [Identifier] {
if let master = self.master {
return [master] + self.etc
}
return self.etc
}
init() {
self.master = nil
self.etc = []
}
public init(from decoder:Decoder) throws {
let container = try decoder.singleValueContainer()
let decoded = try container.decode([RawIdentity].self)
var master: RawIdentity? = nil
self.etc = []
try decoded.forEach{ rawIdentity in
if rawIdentity.name == Constants.OnTheWireMainIdentifier {
master = rawIdentity
return
}
try self.etc.append(rawIdentity.identifier)
}
master?.name = AliasComputer().compute(forId: VCEntitiesConstants.MASTER_ID,
andRelyingParty: VCEntitiesConstants.MASTER_ID)
self.master = try master?.identifier
}
public func encode(to encoder: Encoder) throws {
var output = try self.etc.map(RawIdentity.init)
if let master = self.master {
// For interoperability w/the Android implementation..
var mapped = try RawIdentity(identifier: master)
mapped.name = Constants.OnTheWireMainIdentifier
output.insert(mapped, at: 0)
}
var container = encoder.singleValueContainer()
try container.encode(output)
}
}

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

@ -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,100 @@
/*---------------------------------------------------------------------------------------------
* 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
case keyIdNotFound
}
struct RawIdentity: Codable {
var id: String
var name: String
var keys: [Jwk]?
var recoveryKey: String
var updateKey: String
init(identifier: Identifier) throws {
var keys = try identifier.didDocumentKeys.map(RawIdentity.jwkFromKeyContainer)
try keys.append(RawIdentity.jwkFromKeyContainer(identifier.recoveryKey))
try keys.append(RawIdentity.jwkFromKeyContainer(identifier.updateKey))
self.id = identifier.did
self.name = identifier.alias
self.recoveryKey = identifier.recoveryKey.keyId
self.updateKey = identifier.updateKey.keyId
self.keys = keys
}
var identifier: Identifier {
get throws {
// 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 RawIdentity.keyContainerFromJwk(recoveryJwk)
let updateKeyContainer = try RawIdentity.keyContainerFromJwk(updateJwk)
let signingKeyContainer = try RawIdentity.keyContainerFromJwk(signingJwk)
// Wrap up and return
return Identifier(longFormDid: self.id,
didDocumentKeys: [signingKeyContainer],
updateKey: updateKeyContainer,
recoveryKey: recoveryKeyContainer,
alias: self.name)
}
}
private static func jwkFromKeyContainer(_ keyContainer: KeyContainer) throws -> Jwk {
// Get out the public and private components of the key (pair)
let secret = keyContainer.keyReference
let publicKey = try Secp256k1().createPublicKey(forSecret: secret)
let privateKey = try EphemeralSecret(with: secret)
// 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)
}
private static func keyContainerFromJwk(_ jwk: Jwk) throws -> KeyContainer {
guard let privateKeyData = jwk.d else {
throw RawIdentityError.privateKeyNotFound
}
let privateKey = EphemeralSecret(with: privateKeyData,
accessGroup: VCSDKConfiguration.sharedInstance.accessGroupIdentifier)
guard let keyId = jwk.keyId else {
throw RawIdentityError.keyIdNotFound
}
// Wrap it all up
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)
}
}

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

@ -10,12 +10,12 @@ 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"
/// 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
@ -34,4 +34,8 @@ public struct KeyContainer {
public func migrateKey(fromAccessGroup currentAccessGroup: String?) throws {
try keyReference.migrateKey(fromAccessGroup: currentAccessGroup)
}
public func persist(in secretStore:SecretStoring) throws {
try secretStore.save(secret: self.keyReference)
}
}

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

@ -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"

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

@ -12,8 +12,10 @@ struct MockCryptoOperations: CryptoOperating {
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 +27,8 @@ struct MockCryptoOperations: CryptoOperating {
func retrieveKeyFromStorage(withId id: UUID) -> VCCryptoSecret {
return KeyId(id: id)
}
func retrieveKeyIfStored(uuid: UUID) throws -> VCCryptoSecret? {
return KeyId(id: uuid)
}
}

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

@ -21,4 +21,14 @@ internal class SecretStoreMock: SecretStoring {
func deleteSecret(id: UUID, itemTypeCode: String, accessGroup: String?) throws {
memoryStore.removeValue(forKey: id)
}
func save(secret: VCCryptoSecret) throws {
let ephemeral = try EphemeralSecret(with: secret)
memoryStore[secret.id] = ephemeral.value
}
func delete(secret: VCCryptoSecret) throws {
let ephemeral = try EphemeralSecret(with: secret)
memoryStore.removeValue(forKey: secret.id)
}
}

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

@ -32,6 +32,16 @@ public class IdentifierService {
return try identifierDB.fetchMasterIdentifier()
}
public func fetchAllIdentifiers() throws -> [Identifier] {
return try identifierDB.fetchAllIdentifiers()
}
public func replaceIdentifiers(with identifiers:[Identifier]) throws {
try identifierDB.removeAllIdentifiers()
try identifiers.forEach(identifierDB.importIdentifier)
}
func fetchIdentifier(withAlias alias: String) throws -> Identifier {
return try identifierDB.fetchIdentifier(withAlias: alias)
}

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

@ -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)

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

@ -42,7 +42,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 +91,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,
let key = try cryptoOperations.retrieveKeyIfStored(uuid: uuid) {
try cryptoOperations.secretStore.delete(secret: key)
}
}
// 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
try keyContainer.persist(in: cryptoOperations.secretStore)
}
try self.saveIdentifier(identifier: identifier)
}
private func createIdentifier(fromIdentifierModel model: IdentifierModel) throws -> Identifier {
guard let longFormDid = model.did,
@ -95,9 +132,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>

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

@ -7,6 +7,11 @@
objects = {
/* Begin PBXBuildFile section */
2F29BA412808759F0076CB02 /* Jwk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F29BA402808759F0076CB02 /* Jwk.swift */; };
2F3C363027E0EA670067F3BD /* JweToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3C362F27E0EA670067F3BD /* JweToken.swift */; };
2F58B93127FDBF13001C6DFE /* PbesJwe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F58B93027FDBF13001C6DFE /* PbesJwe.swift */; };
2FA0117627F5BCC1006D5BF7 /* JweDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA0117527F5BCC1006D5BF7 /* JweDecoder.swift */; };
2FBC5E3627F1BBD3000DB57F /* JweEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FBC5E3527F1BBD3000DB57F /* JweEncoder.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 */
2F29BA402808759F0076CB02 /* Jwk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Jwk.swift; sourceTree = "<group>"; };
2F3C362F27E0EA670067F3BD /* JweToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JweToken.swift; sourceTree = "<group>"; };
2F58B93027FDBF13001C6DFE /* PbesJwe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PbesJwe.swift; sourceTree = "<group>"; };
2FA0117527F5BCC1006D5BF7 /* JweDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JweDecoder.swift; sourceTree = "<group>"; };
2FBC5E3527F1BBD3000DB57F /* JweEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JweEncoder.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 */,
2F58B93027FDBF13001C6DFE /* PbesJwe.swift */,
);
path = algorithms;
sourceTree = "<group>";
@ -128,6 +139,10 @@
isa = PBXGroup;
children = (
5526934F24FF32850051A358 /* algorithms */,
2F29BA402808759F0076CB02 /* Jwk.swift */,
2FBC5E3527F1BBD3000DB57F /* JweEncoder.swift */,
2FA0117527F5BCC1006D5BF7 /* JweDecoder.swift */,
2F3C362F27E0EA670067F3BD /* JweToken.swift */,
55ADE27824F540F700D9990E /* JwsToken.swift */,
55ADE27C24F5410E00D9990E /* Claims.swift */,
55ADE27124F5407D00D9990E /* Header.swift */,
@ -288,8 +303,13 @@
files = (
55ADE27D24F5410E00D9990E /* Claims.swift in Sources */,
5540904B2500311E001246DB /* ECPublicJwk.swift in Sources */,
2F3C363027E0EA670067F3BD /* JweToken.swift in Sources */,
55C6C4A6250816500082BE73 /* Secp256k1Verifier.swift in Sources */,
2F29BA412808759F0076CB02 /* Jwk.swift in Sources */,
2FA0117627F5BCC1006D5BF7 /* JweDecoder.swift in Sources */,
55ADE27924F540F700D9990E /* JwsToken.swift in Sources */,
2F58B93127FDBF13001C6DFE /* PbesJwe.swift in Sources */,
2FBC5E3627F1BBD3000DB57F /* JweEncoder.swift in Sources */,
55ADE27224F5407D00D9990E /* Header.swift in Sources */,
55269351250006020051A358 /* VCTokenError.swift in Sources */,
55ADE28524F5412900D9990E /* TokenVerifying.swift in Sources */,

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

@ -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)
}
}

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

@ -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
}
}