Skip to content

Commit

Permalink
Factor out some common keychain functionality (#456)
Browse files Browse the repository at this point in the history
* Factor out some common keychain functionality

* Remove redundant

* Remove redundant
  • Loading branch information
maxgoedjen authored Mar 12, 2023
1 parent 93e7947 commit be58ddd
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,9 @@ SecretKit is a collection of protocols describing secrets and stores.
### Authentication Persistence

- ``PersistedAuthenticationContext``

### Errors

- ``KeychainError``
- ``SigningError``
- ``SecurityError``
5 changes: 0 additions & 5 deletions Sources/Packages/Sources/SecretKit/KeychainDictionary.swift

This file was deleted.

71 changes: 71 additions & 0 deletions Sources/Packages/Sources/SecretKit/KeychainTypes.swift
Original file line number Diff line number Diff line change
@@ -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()
}

}

}
12 changes: 0 additions & 12 deletions Sources/Packages/Sources/SecretKit/Types/SecretStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,3 @@ extension NSNotification.Name {
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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -295,29 +295,12 @@ extension SecureEnclave.Store {
])
let status = SecItemAdd(attributes, nil)
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 {

enum Constants {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,3 @@

- ``Secret``
- ``Store``

### Errors

- ``KeychainError``
- ``SigningError``
- ``SecurityError``
94 changes: 21 additions & 73 deletions Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,12 @@ extension SmartCard {
}
let key = untypedSafe as! SecKey
var signError: SecurityError?
let signatureAlgorithm: SecKeyAlgorithm
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 {
guard let signature = SecKeyCreateSignature(key, signatureAlgorithm(for: secret, allowRSA: true), data as CFData, &signError) else {
throw SigningError(error: signError)
}
return signature as Data
}

public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool {
let attributes = KeychainDictionary([
kSecAttrKeyType: secret.algorithm.secAttrKeyType,
Expand All @@ -98,20 +86,7 @@ extension SmartCard {
throw KeychainError(statusCode: errSecSuccess)
}
let key = untypedSafe as! SecKey
let signatureAlgorithm: SecKeyAlgorithm
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)
let verified = SecKeyVerifySignature(key, signatureAlgorithm(for: secret, allowRSA: true), data as CFData, signature as CFData, &verifyError)
if !verified, let verifyError {
if verifyError.takeUnretainedValue() ~= .verifyError {
return false
Expand All @@ -122,11 +97,11 @@ extension SmartCard {
return verified
}

public func existingPersistedAuthenticationContext(secret: SmartCard.Secret) -> PersistedAuthenticationContext? {
public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? {
nil
}

public func persistAuthentication(secret: SmartCard.Secret, forDuration: TimeInterval) throws {
public func persistAuthentication(secret: Secret, forDuration: TimeInterval) throws {
}

/// Reloads all secrets from the store.
Expand Down Expand Up @@ -186,7 +161,7 @@ extension SmartCard.Store {
var untyped: CFTypeRef?
SecItemCopyMatching(attributes, &untyped)
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 tokenID = $0[kSecAttrApplicationLabel] as! Data
let algorithm = Algorithm(secAttr: $0[kSecAttrKeyType] as! NSNumber)
Expand Down Expand Up @@ -225,24 +200,11 @@ extension SmartCard.Store {
var encryptError: SecurityError?
let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &encryptError)
guard let untypedSafe = untyped else {
throw SmartCard.KeychainError(statusCode: errSecSuccess)
throw KeychainError(statusCode: errSecSuccess)
}
let key = untypedSafe as! SecKey
let signatureAlgorithm: SecKeyAlgorithm
switch (secret.algorithm, secret.keySize) {
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)
guard let signature = SecKeyCreateEncryptedData(key, encryptionAlgorithm(for: secret), data as CFData, &encryptError) else {
throw SigningError(error: encryptError)
}
return signature as Data
}
Expand All @@ -269,28 +231,30 @@ extension SmartCard.Store {
var untyped: CFTypeRef?
let status = SecItemCopyMatching(attributes, &untyped)
if status != errSecSuccess {
throw SmartCard.KeychainError(statusCode: status)
throw KeychainError(statusCode: status)
}
guard let untypedSafe = untyped else {
throw SmartCard.KeychainError(statusCode: errSecSuccess)
throw KeychainError(statusCode: errSecSuccess)
}
let key = untypedSafe as! SecKey
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) {
case (.ellipticCurve, 256):
signatureAlgorithm = .eciesEncryptionStandardX963SHA256AESGCM
return .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
case (.ellipticCurve, 384):
signatureAlgorithm = .eciesEncryptionStandardX963SHA384AESGCM
return .eciesEncryptionCofactorVariableIVX963SHA256AESGCM
case (.rsa, 1024), (.rsa, 2048):
signatureAlgorithm = .rsaEncryptionOAEPSHA512AESGCM
return .rsaEncryptionOAEPSHA512AESGCM
default:
fatalError()
}
guard let signature = SecKeyCreateDecryptedData(key, signatureAlgorithm, data as CFData, &encryptError) else {
throw SmartCard.SigningError(error: encryptError)
}
return signature as Data
}

}
Expand All @@ -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?
}

}
26 changes: 2 additions & 24 deletions Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,7 @@ extension Stub {
kSecAttrKeyClass: kSecAttrKeyClassPrivate
])
, nil)!
let signatureAlgorithm: SecKeyAlgorithm
switch secret.keySize {
case 256:
signatureAlgorithm = .ecdsaSignatureMessageX962SHA256
case 384:
signatureAlgorithm = .ecdsaSignatureMessageX962SHA384
default:
fatalError()
}
return SecKeyCreateSignature(privateKey, signatureAlgorithm, data as CFData, nil)! as Data
return SecKeyCreateSignature(privateKey, signatureAlgorithm(for: secret), data as CFData, nil)! as Data
}

public func verify(signature: Data, for data: Data, with secret: Stub.Secret) throws -> Bool {
Expand All @@ -82,20 +73,7 @@ extension Stub {
throw NSError(domain: "test", code: 0, userInfo: nil)
}
let key = untypedSafe as! SecKey
let signatureAlgorithm: SecKeyAlgorithm
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)
let verified = SecKeyVerifySignature(key, signatureAlgorithm(for: secret), data as CFData, signature as CFData, &verifyError)
if let verifyError {
if verifyError.takeUnretainedValue() ~= .verifyError {
return false
Expand Down

0 comments on commit be58ddd

Please sign in to comment.