Update to migrate key
This commit is contained in:
Родитель
feb7fe356d
Коммит
38e4423770
|
@ -6,6 +6,7 @@
|
|||
import Foundation
|
||||
|
||||
public enum KeychainStoreError: Error {
|
||||
case deleteFromStoreError(status: OSStatus)
|
||||
case saveToStoreError(status: Int32)
|
||||
case itemNotFound
|
||||
case readFromStoreError(status: OSStatus)
|
||||
|
@ -58,8 +59,9 @@ struct KeychainSecretStore : SecretStoring {
|
|||
/// - Parameters:
|
||||
/// - id: The Id of the secret
|
||||
/// - itemTypeCode: The secret type code (4 chars)
|
||||
/// - accessGroup: The access group to use to save secret.
|
||||
/// - value: The value of the secret
|
||||
func saveSecret(id: UUID, itemTypeCode: String, accessGroup: String? = nil, value: inout Data) throws {
|
||||
func saveSecret(id: UUID, itemTypeCode: String, accessGroup: String? = nil, value: inout Data) throws {
|
||||
defer {
|
||||
let secretSize = value.count
|
||||
value.withUnsafeMutableBytes { (secretPtr) in
|
||||
|
@ -89,4 +91,42 @@ struct KeychainSecretStore : SecretStoring {
|
|||
guard status == errSecSuccess else {
|
||||
throw KeychainStoreError.saveToStoreError(status: status)}
|
||||
}
|
||||
|
||||
/// Delete the secret from keychain
|
||||
/// Note: the value passed in will be zeroed out after the secret is deleted
|
||||
/// - Parameters:
|
||||
/// - id: The Id of the secret
|
||||
/// - itemTypeCode: The secret type code (4 chars)
|
||||
/// - accessGroup: The access group of the secret.
|
||||
/// - value: The value of the secret
|
||||
func deleteSecret(id: UUID, itemTypeCode: String, accessGroup: String? = nil, value: inout Data) throws {
|
||||
defer {
|
||||
let secretSize = value.count
|
||||
value.withUnsafeMutableBytes { (secretPtr) in
|
||||
memset_s(secretPtr.baseAddress, secretSize, 0, secretSize)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard itemTypeCode.count == 4 else { throw KeychainStoreError.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
|
||||
// kSecAttrType is used to store the secret type to allow us to cast it to the right Type on search
|
||||
var query = [kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrAccount: id.uuidString,
|
||||
kSecAttrService: vcService,
|
||||
kSecAttrType: itemTypeCode,
|
||||
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||
kSecValueData: value] as [String: Any]
|
||||
|
||||
if let accessGroup = accessGroup,
|
||||
accessGroup != "" {
|
||||
query[kSecAttrAccessGroup as String] = accessGroup
|
||||
}
|
||||
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
guard status == errSecSuccess else {
|
||||
throw KeychainStoreError.deleteFromStoreError(status: status)}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,12 +71,37 @@ final class Random32BytesSecret: Secret {
|
|||
}
|
||||
}
|
||||
|
||||
func updateAccessGroup() throws {
|
||||
var value = try self.store.getSecret(id: id, itemTypeCode: Random32BytesSecret.itemTypeCode, accessGroup: nil)
|
||||
func migrateKey(fromAccessGroup oldAccessGroup: String?) throws {
|
||||
|
||||
/// If old access group is equal to new access group, no action required.
|
||||
if oldAccessGroup == accessGroup
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
var value = try self.store.getSecret(id: id,
|
||||
itemTypeCode: Random32BytesSecret.itemTypeCode,
|
||||
accessGroup: oldAccessGroup)
|
||||
defer {
|
||||
let secretSize = value.count
|
||||
value.withUnsafeMutableBytes { (secretPtr) in
|
||||
memset_s(secretPtr.baseAddress, secretSize, 0, secretSize)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/// Save to new access group
|
||||
try self.store.saveSecret(id: id,
|
||||
itemTypeCode: Random32BytesSecret.itemTypeCode,
|
||||
accessGroup: accessGroup,
|
||||
value: &value)
|
||||
|
||||
/// Delete from old access group
|
||||
try self.store.deleteSecret(id: id,
|
||||
itemTypeCode: Random32BytesSecret.itemTypeCode,
|
||||
accessGroup: oldAccessGroup,
|
||||
value: &value)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,14 +7,14 @@ import Foundation
|
|||
|
||||
internal protocol Secret: VCCryptoSecret & InternalSecret {}
|
||||
|
||||
public protocol VCCryptoSecret{
|
||||
public protocol VCCryptoSecret {
|
||||
|
||||
/// The secret id
|
||||
var id:UUID { get }
|
||||
|
||||
func isValidKey() -> Bool
|
||||
|
||||
func updateAccessGroup() throws
|
||||
func migrateKey(fromAccessGroup oldAccessGroup: String?) throws
|
||||
}
|
||||
|
||||
protocol InternalSecret {
|
||||
|
|
|
@ -10,4 +10,5 @@ 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?, value: inout Data) throws
|
||||
}
|
||||
|
|
|
@ -34,14 +34,18 @@ public struct KeyContainer {
|
|||
return keyReference.isValidKey()
|
||||
}
|
||||
|
||||
public func updateAccessGroup() throws {
|
||||
public func migrateKey(fromAccessGroup oldAccessGroup: String?) throws {
|
||||
do {
|
||||
try keyReference.updateAccessGroup()
|
||||
} catch
|
||||
try keyReference.migrateKey(fromAccessGroup: oldAccessGroup)
|
||||
}
|
||||
catch
|
||||
{
|
||||
if case KeychainStoreError.itemNotFound = error {
|
||||
if case KeychainStoreError.itemNotFound = error
|
||||
{
|
||||
throw KeyContainerError.noSigningKeyFoundInStorage
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,15 +53,17 @@ public class IdentifierService {
|
|||
}
|
||||
|
||||
/// updates access group for keys if it needs to be updated.
|
||||
public func updateAccessGroupForKeys(for identifier: Identifier) throws {
|
||||
try identifier.recoveryKey.updateAccessGroup()
|
||||
try identifier.updateKey.updateAccessGroup()
|
||||
public func migrateKeys(fromAccessGroup oldAccessGroup: String?) throws {
|
||||
let identifier = try fetchMasterIdentifier()
|
||||
try identifier.recoveryKey.migrateKey(fromAccessGroup: oldAccessGroup)
|
||||
try identifier.updateKey.migrateKey(fromAccessGroup: oldAccessGroup)
|
||||
try identifier.didDocumentKeys.forEach { keyContainer in
|
||||
try keyContainer.updateAccessGroup()
|
||||
try keyContainer.migrateKey(fromAccessGroup: oldAccessGroup)
|
||||
}
|
||||
}
|
||||
|
||||
public func areKeysValid(for identifier: Identifier) -> Bool {
|
||||
public func areKeysValid() throws -> Bool {
|
||||
let identifier = try fetchMasterIdentifier()
|
||||
return identifier.recoveryKey.isValidKey() &&
|
||||
identifier.updateKey.isValidKey() &&
|
||||
(identifier.didDocumentKeys.first?.isValidKey() ?? false)
|
||||
|
|
|
@ -7,11 +7,8 @@ import VCEntities
|
|||
|
||||
public enum VCSDKInitStatus
|
||||
{
|
||||
case firstTimeMasterIdentifierCreated
|
||||
case newMasterIdentifierCreated
|
||||
case accessGroupForKeysUpdated
|
||||
case success
|
||||
case error(error: Error)
|
||||
}
|
||||
|
||||
public class VerifiableCredentialSDK {
|
||||
|
@ -22,11 +19,10 @@ public class VerifiableCredentialSDK {
|
|||
/// Returns: TRUE, if needed to create Master Identifier
|
||||
/// FALSE, if Master Identifier is able to be fetched (included private keys from KeyStore)
|
||||
public static func initialize(logConsumer: VCLogConsumer = DefaultVCLogConsumer(),
|
||||
accessGroupIdentifier: String? = nil) -> VCSDKInitStatus {
|
||||
accessGroupIdentifier: String? = nil) -> Result<VCSDKInitStatus, Error> {
|
||||
|
||||
VCSDKLog.sharedInstance.add(consumer: logConsumer)
|
||||
|
||||
|
||||
/// Get access group identifier for app.
|
||||
if let accessGroupIdentifier = accessGroupIdentifier {
|
||||
VCSDKConfiguration.sharedInstance.setAccessGroupIdentifier(with: accessGroupIdentifier)
|
||||
|
@ -42,38 +38,21 @@ public class VerifiableCredentialSDK {
|
|||
|
||||
/// 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(status: .firstTimeMasterIdentifierCreated)
|
||||
return createNewIdentifier()
|
||||
|
||||
}
|
||||
|
||||
/// Make sure keys are valid for master identifier, if they are not valid, update access group.
|
||||
do {
|
||||
if !identifierService.areKeysValid(for: identifier) {
|
||||
try identifierService.updateAccessGroupForKeys(for: identifier)
|
||||
return .accessGroupForKeysUpdated
|
||||
}
|
||||
} catch {
|
||||
|
||||
/// if keys are not found in storage, create a new master identifier.
|
||||
if case KeyContainerError.noSigningKeyFoundInStorage = error
|
||||
{
|
||||
VCSDKLog.sharedInstance.logWarning(message: "Failed find signing keys in storage with: \(String(describing: error))")
|
||||
return createNewIdentifier(status: .newMasterIdentifierCreated)
|
||||
}
|
||||
|
||||
return .error(error: error)
|
||||
}
|
||||
|
||||
/// VC sdk initialization successful.
|
||||
return .success
|
||||
return .success(.success)
|
||||
}
|
||||
|
||||
private static func createNewIdentifier(status: VCSDKInitStatus) -> VCSDKInitStatus {
|
||||
private static func createNewIdentifier() -> Result<VCSDKInitStatus, Error> {
|
||||
do {
|
||||
_ = try identifierService.createAndSaveIdentifier(forId: VCEntitiesConstants.MASTER_ID, andRelyingParty: VCEntitiesConstants.MASTER_ID)
|
||||
return status
|
||||
_ = try identifierService.createAndSaveIdentifier(forId: VCEntitiesConstants.MASTER_ID,
|
||||
andRelyingParty: VCEntitiesConstants.MASTER_ID)
|
||||
return .success(.newMasterIdentifierCreated)
|
||||
} catch {
|
||||
return .error(error: error)
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import VCCrypto
|
||||
|
||||
public struct KeyId: VCCryptoSecret {
|
||||
|
||||
|
||||
public let id: UUID
|
||||
|
||||
public init(id: UUID) {
|
||||
|
@ -17,5 +17,5 @@ public struct KeyId: VCCryptoSecret {
|
|||
return true
|
||||
}
|
||||
|
||||
public func updateAccessGroup() throws { }
|
||||
public func migrateKey(fromAccessGroup oldAccessGroup: String?) throws { }
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче