Factor out some common keychain functionality (#456)
* Factor out some common keychain functionality * Remove redundant * Remove redundant
This commit is contained in:
parent
93e79470b7
commit
be58ddd324
|
@ -32,3 +32,9 @@ SecretKit is a collection of protocols describing secrets and stores.
|
||||||
### Authentication Persistence
|
### Authentication Persistence
|
||||||
|
|
||||||
- ``PersistedAuthenticationContext``
|
- ``PersistedAuthenticationContext``
|
||||||
|
|
||||||
|
### Errors
|
||||||
|
|
||||||
|
- ``KeychainError``
|
||||||
|
- ``SigningError``
|
||||||
|
- ``SecurityError``
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public func KeychainDictionary(_ dictionary: [CFString: Any]) -> CFDictionary {
|
|
||||||
dictionary as CFDictionary
|
|
||||||
}
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public typealias SecurityError = Unmanaged<CFError>
|
||||||
|
|
||||||
|
/// Wraps a Swift dictionary in a CFDictionary.
|
||||||
|
/// - Parameter dictionary: The Swift dictionary to wrap.
|
||||||
|
/// - Returns: A CFDictionary containing the keys and values.
|
||||||
|
public func KeychainDictionary(_ dictionary: [CFString: Any]) -> CFDictionary {
|
||||||
|
dictionary as CFDictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension CFError {
|
||||||
|
|
||||||
|
/// The CFError returned when a verification operation fails.
|
||||||
|
static let verifyError = CFErrorCreate(nil, NSOSStatusErrorDomain as CFErrorDomain, CFIndex(errSecVerifyFailed), nil)!
|
||||||
|
|
||||||
|
/// Equality operation that only considers domain and code.
|
||||||
|
static func ~=(lhs: CFError, rhs: CFError) -> Bool {
|
||||||
|
CFErrorGetDomain(lhs) == CFErrorGetDomain(rhs) && CFErrorGetCode(lhs) == CFErrorGetCode(rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrapper around an error code reported by a Keychain API.
|
||||||
|
public struct KeychainError: Error {
|
||||||
|
/// The status code involved, if one was reported.
|
||||||
|
public let statusCode: OSStatus?
|
||||||
|
|
||||||
|
/// Initializes a KeychainError with an optional error code.
|
||||||
|
/// - Parameter statusCode: The status code returned by the keychain operation, if one is applicable.
|
||||||
|
public init(statusCode: OSStatus?) {
|
||||||
|
self.statusCode = statusCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A signing-related error.
|
||||||
|
public struct SigningError: Error {
|
||||||
|
/// The underlying error reported by the API, if one was returned.
|
||||||
|
public let error: SecurityError?
|
||||||
|
|
||||||
|
/// Initializes a SigningError with an optional SecurityError.
|
||||||
|
/// - Parameter statusCode: The SecurityError, if one is applicable.
|
||||||
|
public init(error: SecurityError?) {
|
||||||
|
self.error = error
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension SecretStore {
|
||||||
|
|
||||||
|
/// Returns the appropriate keychian signature algorithm to use for a given secret.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - secret: The secret which will be used for signing.
|
||||||
|
/// - allowRSA: Whether or not RSA key types should be permited.
|
||||||
|
/// - Returns: The appropriate algorithm.
|
||||||
|
func signatureAlgorithm(for secret: SecretType, allowRSA: Bool = false) -> SecKeyAlgorithm {
|
||||||
|
switch (secret.algorithm, secret.keySize) {
|
||||||
|
case (.ellipticCurve, 256):
|
||||||
|
return .ecdsaSignatureMessageX962SHA256
|
||||||
|
case (.ellipticCurve, 384):
|
||||||
|
return .ecdsaSignatureMessageX962SHA384
|
||||||
|
case (.rsa, 1024), (.rsa, 2048):
|
||||||
|
guard allowRSA else { fatalError() }
|
||||||
|
return .rsaSignatureMessagePKCS1v15SHA512
|
||||||
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -79,15 +79,3 @@ extension NSNotification.Name {
|
||||||
public static let secretStoreReloaded = NSNotification.Name("com.maxgoedjen.Secretive.secretStore.reloaded")
|
public static let secretStoreReloaded = NSNotification.Name("com.maxgoedjen.Secretive.secretStore.reloaded")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public typealias SecurityError = Unmanaged<CFError>
|
|
||||||
|
|
||||||
extension CFError {
|
|
||||||
|
|
||||||
public static let verifyError = CFErrorCreate(nil, NSOSStatusErrorDomain as CFErrorDomain, CFIndex(errSecVerifyFailed), nil)!
|
|
||||||
|
|
||||||
static public func ~=(lhs: CFError, rhs: CFError) -> Bool {
|
|
||||||
CFErrorGetDomain(lhs) == CFErrorGetDomain(rhs) && CFErrorGetCode(lhs) == CFErrorGetCode(rhs)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -295,29 +295,12 @@ extension SecureEnclave.Store {
|
||||||
])
|
])
|
||||||
let status = SecItemAdd(attributes, nil)
|
let status = SecItemAdd(attributes, nil)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
throw SecureEnclave.KeychainError(statusCode: status)
|
throw KeychainError(statusCode: status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SecureEnclave {
|
|
||||||
|
|
||||||
/// A wrapper around an error code reported by a Keychain API.
|
|
||||||
public struct KeychainError: Error {
|
|
||||||
/// The status code involved, if one was reported.
|
|
||||||
public let statusCode: OSStatus?
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A signing-related error.
|
|
||||||
public struct SigningError: Error {
|
|
||||||
/// The underlying error reported by the API, if one was returned.
|
|
||||||
public let error: SecurityError?
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extension SecureEnclave {
|
extension SecureEnclave {
|
||||||
|
|
||||||
enum Constants {
|
enum Constants {
|
||||||
|
|
|
@ -6,9 +6,3 @@
|
||||||
|
|
||||||
- ``Secret``
|
- ``Secret``
|
||||||
- ``Store``
|
- ``Store``
|
||||||
|
|
||||||
### Errors
|
|
||||||
|
|
||||||
- ``KeychainError``
|
|
||||||
- ``SigningError``
|
|
||||||
- ``SecurityError``
|
|
||||||
|
|
|
@ -68,24 +68,12 @@ extension SmartCard {
|
||||||
}
|
}
|
||||||
let key = untypedSafe as! SecKey
|
let key = untypedSafe as! SecKey
|
||||||
var signError: SecurityError?
|
var signError: SecurityError?
|
||||||
let signatureAlgorithm: SecKeyAlgorithm
|
guard let signature = SecKeyCreateSignature(key, signatureAlgorithm(for: secret, allowRSA: true), data as CFData, &signError) else {
|
||||||
switch (secret.algorithm, secret.keySize) {
|
|
||||||
case (.ellipticCurve, 256):
|
|
||||||
signatureAlgorithm = .ecdsaSignatureMessageX962SHA256
|
|
||||||
case (.ellipticCurve, 384):
|
|
||||||
signatureAlgorithm = .ecdsaSignatureMessageX962SHA384
|
|
||||||
case (.rsa, 1024):
|
|
||||||
signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512
|
|
||||||
case (.rsa, 2048):
|
|
||||||
signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512
|
|
||||||
default:
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
guard let signature = SecKeyCreateSignature(key, signatureAlgorithm, data as CFData, &signError) else {
|
|
||||||
throw SigningError(error: signError)
|
throw SigningError(error: signError)
|
||||||
}
|
}
|
||||||
return signature as Data
|
return signature as Data
|
||||||
}
|
}
|
||||||
|
|
||||||
public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool {
|
public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool {
|
||||||
let attributes = KeychainDictionary([
|
let attributes = KeychainDictionary([
|
||||||
kSecAttrKeyType: secret.algorithm.secAttrKeyType,
|
kSecAttrKeyType: secret.algorithm.secAttrKeyType,
|
||||||
|
@ -98,20 +86,7 @@ extension SmartCard {
|
||||||
throw KeychainError(statusCode: errSecSuccess)
|
throw KeychainError(statusCode: errSecSuccess)
|
||||||
}
|
}
|
||||||
let key = untypedSafe as! SecKey
|
let key = untypedSafe as! SecKey
|
||||||
let signatureAlgorithm: SecKeyAlgorithm
|
let verified = SecKeyVerifySignature(key, signatureAlgorithm(for: secret, allowRSA: true), data as CFData, signature as CFData, &verifyError)
|
||||||
switch (secret.algorithm, secret.keySize) {
|
|
||||||
case (.ellipticCurve, 256):
|
|
||||||
signatureAlgorithm = .ecdsaSignatureMessageX962SHA256
|
|
||||||
case (.ellipticCurve, 384):
|
|
||||||
signatureAlgorithm = .ecdsaSignatureMessageX962SHA384
|
|
||||||
case (.rsa, 1024):
|
|
||||||
signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512
|
|
||||||
case (.rsa, 2048):
|
|
||||||
signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512
|
|
||||||
default:
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
let verified = SecKeyVerifySignature(key, signatureAlgorithm, data as CFData, signature as CFData, &verifyError)
|
|
||||||
if !verified, let verifyError {
|
if !verified, let verifyError {
|
||||||
if verifyError.takeUnretainedValue() ~= .verifyError {
|
if verifyError.takeUnretainedValue() ~= .verifyError {
|
||||||
return false
|
return false
|
||||||
|
@ -122,11 +97,11 @@ extension SmartCard {
|
||||||
return verified
|
return verified
|
||||||
}
|
}
|
||||||
|
|
||||||
public func existingPersistedAuthenticationContext(secret: SmartCard.Secret) -> PersistedAuthenticationContext? {
|
public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? {
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func persistAuthentication(secret: SmartCard.Secret, forDuration: TimeInterval) throws {
|
public func persistAuthentication(secret: Secret, forDuration: TimeInterval) throws {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reloads all secrets from the store.
|
/// Reloads all secrets from the store.
|
||||||
|
@ -186,7 +161,7 @@ extension SmartCard.Store {
|
||||||
var untyped: CFTypeRef?
|
var untyped: CFTypeRef?
|
||||||
SecItemCopyMatching(attributes, &untyped)
|
SecItemCopyMatching(attributes, &untyped)
|
||||||
guard let typed = untyped as? [[CFString: Any]] else { return }
|
guard let typed = untyped as? [[CFString: Any]] else { return }
|
||||||
let wrapped: [SmartCard.Secret] = typed.map {
|
let wrapped = typed.map {
|
||||||
let name = $0[kSecAttrLabel] as? String ?? "Unnamed"
|
let name = $0[kSecAttrLabel] as? String ?? "Unnamed"
|
||||||
let tokenID = $0[kSecAttrApplicationLabel] as! Data
|
let tokenID = $0[kSecAttrApplicationLabel] as! Data
|
||||||
let algorithm = Algorithm(secAttr: $0[kSecAttrKeyType] as! NSNumber)
|
let algorithm = Algorithm(secAttr: $0[kSecAttrKeyType] as! NSNumber)
|
||||||
|
@ -225,24 +200,11 @@ extension SmartCard.Store {
|
||||||
var encryptError: SecurityError?
|
var encryptError: SecurityError?
|
||||||
let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &encryptError)
|
let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &encryptError)
|
||||||
guard let untypedSafe = untyped else {
|
guard let untypedSafe = untyped else {
|
||||||
throw SmartCard.KeychainError(statusCode: errSecSuccess)
|
throw KeychainError(statusCode: errSecSuccess)
|
||||||
}
|
}
|
||||||
let key = untypedSafe as! SecKey
|
let key = untypedSafe as! SecKey
|
||||||
let signatureAlgorithm: SecKeyAlgorithm
|
guard let signature = SecKeyCreateEncryptedData(key, encryptionAlgorithm(for: secret), data as CFData, &encryptError) else {
|
||||||
switch (secret.algorithm, secret.keySize) {
|
throw SigningError(error: encryptError)
|
||||||
case (.ellipticCurve, 256):
|
|
||||||
signatureAlgorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
|
|
||||||
case (.ellipticCurve, 384):
|
|
||||||
signatureAlgorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
|
|
||||||
case (.rsa, 1024):
|
|
||||||
signatureAlgorithm = .rsaEncryptionOAEPSHA512AESGCM
|
|
||||||
case (.rsa, 2048):
|
|
||||||
signatureAlgorithm = .rsaEncryptionOAEPSHA512AESGCM
|
|
||||||
default:
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
guard let signature = SecKeyCreateEncryptedData(key, signatureAlgorithm, data as CFData, &encryptError) else {
|
|
||||||
throw SmartCard.SigningError(error: encryptError)
|
|
||||||
}
|
}
|
||||||
return signature as Data
|
return signature as Data
|
||||||
}
|
}
|
||||||
|
@ -269,28 +231,30 @@ extension SmartCard.Store {
|
||||||
var untyped: CFTypeRef?
|
var untyped: CFTypeRef?
|
||||||
let status = SecItemCopyMatching(attributes, &untyped)
|
let status = SecItemCopyMatching(attributes, &untyped)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
throw SmartCard.KeychainError(statusCode: status)
|
throw KeychainError(statusCode: status)
|
||||||
}
|
}
|
||||||
guard let untypedSafe = untyped else {
|
guard let untypedSafe = untyped else {
|
||||||
throw SmartCard.KeychainError(statusCode: errSecSuccess)
|
throw KeychainError(statusCode: errSecSuccess)
|
||||||
}
|
}
|
||||||
let key = untypedSafe as! SecKey
|
let key = untypedSafe as! SecKey
|
||||||
var encryptError: SecurityError?
|
var encryptError: SecurityError?
|
||||||
let signatureAlgorithm: SecKeyAlgorithm
|
guard let signature = SecKeyCreateDecryptedData(key, encryptionAlgorithm(for: secret), data as CFData, &encryptError) else {
|
||||||
|
throw SigningError(error: encryptError)
|
||||||
|
}
|
||||||
|
return signature as Data
|
||||||
|
}
|
||||||
|
|
||||||
|
private func encryptionAlgorithm(for secret: SecretType) -> SecKeyAlgorithm {
|
||||||
switch (secret.algorithm, secret.keySize) {
|
switch (secret.algorithm, secret.keySize) {
|
||||||
case (.ellipticCurve, 256):
|
case (.ellipticCurve, 256):
|
||||||
signatureAlgorithm = .eciesEncryptionStandardX963SHA256AESGCM
|
return .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
|
||||||
case (.ellipticCurve, 384):
|
case (.ellipticCurve, 384):
|
||||||
signatureAlgorithm = .eciesEncryptionStandardX963SHA384AESGCM
|
return .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
|
||||||
case (.rsa, 1024), (.rsa, 2048):
|
case (.rsa, 1024), (.rsa, 2048):
|
||||||
signatureAlgorithm = .rsaEncryptionOAEPSHA512AESGCM
|
return .rsaEncryptionOAEPSHA512AESGCM
|
||||||
default:
|
default:
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
guard let signature = SecKeyCreateDecryptedData(key, signatureAlgorithm, data as CFData, &encryptError) else {
|
|
||||||
throw SmartCard.SigningError(error: encryptError)
|
|
||||||
}
|
|
||||||
return signature as Data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -303,19 +267,3 @@ extension TKTokenWatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SmartCard {
|
|
||||||
|
|
||||||
/// A wrapper around an error code reported by a Keychain API.
|
|
||||||
public struct KeychainError: Error {
|
|
||||||
/// The status code involved.
|
|
||||||
public let statusCode: OSStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A signing-related error.
|
|
||||||
public struct SigningError: Error {
|
|
||||||
/// The underlying error reported by the API, if one was returned.
|
|
||||||
public let error: SecurityError?
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -58,16 +58,7 @@ extension Stub {
|
||||||
kSecAttrKeyClass: kSecAttrKeyClassPrivate
|
kSecAttrKeyClass: kSecAttrKeyClassPrivate
|
||||||
])
|
])
|
||||||
, nil)!
|
, nil)!
|
||||||
let signatureAlgorithm: SecKeyAlgorithm
|
return SecKeyCreateSignature(privateKey, signatureAlgorithm(for: secret), data as CFData, nil)! as Data
|
||||||
switch secret.keySize {
|
|
||||||
case 256:
|
|
||||||
signatureAlgorithm = .ecdsaSignatureMessageX962SHA256
|
|
||||||
case 384:
|
|
||||||
signatureAlgorithm = .ecdsaSignatureMessageX962SHA384
|
|
||||||
default:
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
return SecKeyCreateSignature(privateKey, signatureAlgorithm, data as CFData, nil)! as Data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func verify(signature: Data, for data: Data, with secret: Stub.Secret) throws -> Bool {
|
public func verify(signature: Data, for data: Data, with secret: Stub.Secret) throws -> Bool {
|
||||||
|
@ -82,20 +73,7 @@ extension Stub {
|
||||||
throw NSError(domain: "test", code: 0, userInfo: nil)
|
throw NSError(domain: "test", code: 0, userInfo: nil)
|
||||||
}
|
}
|
||||||
let key = untypedSafe as! SecKey
|
let key = untypedSafe as! SecKey
|
||||||
let signatureAlgorithm: SecKeyAlgorithm
|
let verified = SecKeyVerifySignature(key, signatureAlgorithm(for: secret), data as CFData, signature as CFData, &verifyError)
|
||||||
switch (secret.algorithm, secret.keySize) {
|
|
||||||
case (.ellipticCurve, 256):
|
|
||||||
signatureAlgorithm = .ecdsaSignatureMessageX962SHA256
|
|
||||||
case (.ellipticCurve, 384):
|
|
||||||
signatureAlgorithm = .ecdsaSignatureMessageX962SHA384
|
|
||||||
case (.rsa, 1024):
|
|
||||||
signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512
|
|
||||||
case (.rsa, 2048):
|
|
||||||
signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512
|
|
||||||
default:
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
let verified = SecKeyVerifySignature(key, signatureAlgorithm, data as CFData, signature as CFData, &verifyError)
|
|
||||||
if let verifyError {
|
if let verifyError {
|
||||||
if verifyError.takeUnretainedValue() ~= .verifyError {
|
if verifyError.takeUnretainedValue() ~= .verifyError {
|
||||||
return false
|
return false
|
||||||
|
|
Loading…
Reference in New Issue