From 49306b9457d565fcd44e1c559a41f5dc4090314f Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 11 Mar 2023 15:35:25 -0800 Subject: [PATCH] Expose verify as public api --- .../SecretKit/Erasers/AnySecretStore.swift | 6 ++++ .../Sources/SecretKit/Types/SecretStore.swift | 8 +++++ .../SecureEnclaveStore.swift | 33 ++++++++++++++++++- .../Preview Content/PreviewStore.swift | 4 +++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift b/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift index 0cb6c40..0754fbf 100644 --- a/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Erasers/AnySecretStore.swift @@ -10,6 +10,7 @@ public class AnySecretStore: SecretStore { private let _name: () -> String private let _secrets: () -> [AnySecret] private let _sign: (Data, AnySecret, SigningRequestProvenance) throws -> Data + private let _verify: (Data, Data, AnySecret) throws -> Bool private let _existingPersistedAuthenticationContext: (AnySecret) -> PersistedAuthenticationContext? private let _persistAuthentication: (AnySecret, TimeInterval) throws -> Void private let _reloadSecrets: () -> Void @@ -23,6 +24,7 @@ public class AnySecretStore: SecretStore { _id = { secretStore.id } _secrets = { secretStore.secrets.map { AnySecret($0) } } _sign = { try secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2) } + _verify = { try secretStore.verify(data: $0, signature: $1, with: $2.base as! SecretStoreType.SecretType) } _existingPersistedAuthenticationContext = { secretStore.existingPersistedAuthenticationContext(secret: $0.base as! SecretStoreType.SecretType) } _persistAuthentication = { try secretStore.persistAuthentication(secret: $0.base as! SecretStoreType.SecretType, forDuration: $1) } _reloadSecrets = { secretStore.reloadSecrets() } @@ -51,6 +53,10 @@ public class AnySecretStore: SecretStore { try _sign(data, secret, provenance) } + public func verify(data: Data, signature: Data, with secret: AnySecret) throws -> Bool { + try _verify(data, signature, secret) + } + public func existingPersistedAuthenticationContext(secret: AnySecret) -> PersistedAuthenticationContext? { _existingPersistedAuthenticationContext(secret) } diff --git a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift index f13fec7..e629a6e 100644 --- a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift @@ -23,6 +23,14 @@ public protocol SecretStore: ObservableObject, Identifiable { /// - Returns: The signed data. func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data + /// Verifies that a signature is valid over a specified payload. + /// - Parameters: + /// - data: The data to verify the signature of. + /// - signature: The signature over the data. + /// - secret: The secret whose signature to verify. + /// - Returns: Whether the signature was verified. + func verify(data: Data, signature: Data, with secret: SecretType) throws -> Bool + /// Checks to see if there is currently a valid persisted authentication for a given secret. /// - Parameters: /// - secret: The ``Secret`` to check if there is a persisted authentication for. diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift index 8c4784a..e071e0f 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift @@ -101,7 +101,7 @@ extension SecureEnclave { reloadSecretsInternal() } - public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data { + public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) throws -> Data { let context: LAContext if let existing = persistedAuthenticationContexts[secret], existing.valid { context = existing.context @@ -138,6 +138,37 @@ extension SecureEnclave { return signature as Data } + public func verify(data: Data, signature: Data, with secret: Secret) throws -> Bool { + let context = LAContext() + context.localizedReason = "verify a signature using secret \"\(secret.name)\"" + context.localizedCancelTitle = "Deny" + let attributes = KeychainDictionary([ + kSecClass: kSecClassKey, + kSecAttrKeyClass: kSecAttrKeyClassPrivate, + kSecAttrApplicationLabel: secret.id as CFData, + kSecAttrKeyType: Constants.keyType, + kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, + kSecAttrApplicationTag: Constants.keyTag, + kSecUseAuthenticationContext: context, + kSecReturnRef: true + ]) + var verifyError: SecurityError? + var untyped: CFTypeRef? + let status = SecItemCopyMatching(attributes, &untyped) + if status != errSecSuccess { + throw KeychainError(statusCode: status) + } + guard let untypedSafe = untyped else { + throw KeychainError(statusCode: errSecSuccess) + } + let key = untypedSafe as! SecKey + let signature = SecKeyVerifySignature(key, .ecdsaSignatureMessageX962SHA256, data as CFData, signature as CFData, &verifyError) + if !signature { + throw SigningError(error: verifyError) + } + return signature + } + public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? { guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil } return persisted diff --git a/Sources/Secretive/Preview Content/PreviewStore.swift b/Sources/Secretive/Preview Content/PreviewStore.swift index 839273e..8006d6c 100644 --- a/Sources/Secretive/Preview Content/PreviewStore.swift +++ b/Sources/Secretive/Preview Content/PreviewStore.swift @@ -40,6 +40,10 @@ extension Preview { return data } + func verify(data: Data, signature: Data, with secret: Preview.Secret) throws -> Bool { + true + } + func existingPersistedAuthenticationContext(secret: Preview.Secret) -> PersistedAuthenticationContext? { nil }