mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
added iCloud sync for keys??
reindented all apple sample code
This commit is contained in:
@@ -19,8 +19,6 @@ class KeyManager: ObservableObject {
|
|||||||
var keyIDs: [UUID] {
|
var keyIDs: [UUID] {
|
||||||
keypairs.map { $0.id }
|
keypairs.map { $0.id }
|
||||||
}
|
}
|
||||||
// @Published var keyTypes: [UUID: KeyType] = [:]
|
|
||||||
// @Published var keyNames: [UUID: String] = [:]
|
|
||||||
private let baseTag = "com.neon443.ShhShell.keys".data(using: .utf8)!
|
private let baseTag = "com.neon443.ShhShell.keys".data(using: .utf8)!
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@@ -59,9 +57,8 @@ class KeyManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func deleteKey(_ keypair: Keypair) {
|
func deleteKey(_ keypair: Keypair) {
|
||||||
|
withAnimation { keypairs.removeAll(where: { $0.id == keypair.id }) }
|
||||||
removeFromKeycahin(keypair: keypair)
|
removeFromKeycahin(keypair: keypair)
|
||||||
let keyID = keypair.id
|
|
||||||
withAnimation { keypairs.removeAll(where: { $0.id == keyID }) }
|
|
||||||
saveKeypairs()
|
saveKeypairs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,84 +1,84 @@
|
|||||||
/*
|
/*
|
||||||
See the LICENSE.txt file for this sample’s licensing information.
|
See the LICENSE.txt file for this sample’s licensing information.
|
||||||
|
|
||||||
Abstract:
|
Abstract:
|
||||||
The interface required for conversion to a generic password keychain item.
|
The interface required for conversion to a generic password keychain item.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
||||||
/// The interface needed for SecKey conversion.
|
/// The interface needed for SecKey conversion.
|
||||||
protocol GenericPasswordConvertible: CustomStringConvertible {
|
protocol GenericPasswordConvertible: CustomStringConvertible {
|
||||||
/// Creates a key from a generic key representation.
|
/// Creates a key from a generic key representation.
|
||||||
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes
|
||||||
|
|
||||||
/// A generic representation of the key.
|
/// A generic representation of the key.
|
||||||
var genericKeyRepresentation: SymmetricKey { get }
|
var genericKeyRepresentation: SymmetricKey { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension GenericPasswordConvertible {
|
extension GenericPasswordConvertible {
|
||||||
/// A string version of the key for visual inspection.
|
/// A string version of the key for visual inspection.
|
||||||
/// IMPORTANT: Never log the actual key data.
|
/// IMPORTANT: Never log the actual key data.
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return self.genericKeyRepresentation.withUnsafeBytes { bytes in
|
return self.genericKeyRepresentation.withUnsafeBytes { bytes in
|
||||||
return "Key representation contains \(bytes.count) bytes."
|
return "Key representation contains \(bytes.count) bytes."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Declare that the Curve25519 keys are generic passord convertible.
|
// Declare that the Curve25519 keys are generic passord convertible.
|
||||||
extension Curve25519.KeyAgreement.PrivateKey: GenericPasswordConvertible {
|
extension Curve25519.KeyAgreement.PrivateKey: GenericPasswordConvertible {
|
||||||
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
||||||
try self.init(rawRepresentation: data)
|
try self.init(rawRepresentation: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
var genericKeyRepresentation: SymmetricKey {
|
var genericKeyRepresentation: SymmetricKey {
|
||||||
self.rawRepresentation.withUnsafeBytes {
|
self.rawRepresentation.withUnsafeBytes {
|
||||||
SymmetricKey(data: $0)
|
SymmetricKey(data: $0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extension Curve25519.Signing.PrivateKey: GenericPasswordConvertible {
|
extension Curve25519.Signing.PrivateKey: GenericPasswordConvertible {
|
||||||
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
||||||
try self.init(rawRepresentation: data)
|
try self.init(rawRepresentation: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
var genericKeyRepresentation: SymmetricKey {
|
var genericKeyRepresentation: SymmetricKey {
|
||||||
self.rawRepresentation.withUnsafeBytes {
|
self.rawRepresentation.withUnsafeBytes {
|
||||||
SymmetricKey(data: $0)
|
SymmetricKey(data: $0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that SymmetricKey is generic password convertible.
|
// Ensure that SymmetricKey is generic password convertible.
|
||||||
extension SymmetricKey: GenericPasswordConvertible {
|
extension SymmetricKey: GenericPasswordConvertible {
|
||||||
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
||||||
self.init(data: data)
|
self.init(data: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
var genericKeyRepresentation: SymmetricKey {
|
var genericKeyRepresentation: SymmetricKey {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that Secure Enclave keys are generic password convertible.
|
// Ensure that Secure Enclave keys are generic password convertible.
|
||||||
extension SecureEnclave.P256.KeyAgreement.PrivateKey: GenericPasswordConvertible {
|
extension SecureEnclave.P256.KeyAgreement.PrivateKey: GenericPasswordConvertible {
|
||||||
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
||||||
try self.init(dataRepresentation: data.withUnsafeBytes { Data($0) })
|
try self.init(dataRepresentation: data.withUnsafeBytes { Data($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
var genericKeyRepresentation: SymmetricKey {
|
var genericKeyRepresentation: SymmetricKey {
|
||||||
return SymmetricKey(data: dataRepresentation)
|
return SymmetricKey(data: dataRepresentation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SecureEnclave.P256.Signing.PrivateKey: GenericPasswordConvertible {
|
extension SecureEnclave.P256.Signing.PrivateKey: GenericPasswordConvertible {
|
||||||
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
||||||
try self.init(dataRepresentation: data.withUnsafeBytes { Data($0) })
|
try self.init(dataRepresentation: data.withUnsafeBytes { Data($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
var genericKeyRepresentation: SymmetricKey {
|
var genericKeyRepresentation: SymmetricKey {
|
||||||
return SymmetricKey(data: dataRepresentation)
|
return SymmetricKey(data: dataRepresentation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +1,86 @@
|
|||||||
/*
|
/*
|
||||||
See the LICENSE.txt file for this sample’s licensing information.
|
See the LICENSE.txt file for this sample’s licensing information.
|
||||||
|
|
||||||
Abstract:
|
Abstract:
|
||||||
Methods for storing generic password convertible items in the keychain.
|
Methods for storing generic password convertible items in the keychain.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import Security
|
import Security
|
||||||
|
|
||||||
struct GenericPasswordStore {
|
struct GenericPasswordStore {
|
||||||
|
|
||||||
/// Stores a CryptoKit key in the keychain as a generic password.
|
/// Stores a CryptoKit key in the keychain as a generic password.
|
||||||
func storeKey<T: GenericPasswordConvertible>(_ key: T, account: String) throws {
|
func storeKey<T: GenericPasswordConvertible>(_ key: T, account: String) throws {
|
||||||
|
|
||||||
// Treat the key data as a generic password.
|
// Treat the key data as a generic password.
|
||||||
try key.genericKeyRepresentation.withUnsafeBytes { keyBytes in
|
try key.genericKeyRepresentation.withUnsafeBytes { keyBytes in
|
||||||
let cfd = Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: keyBytes.baseAddress!), count: keyBytes.count, deallocator: .none)
|
let cfd = Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: keyBytes.baseAddress!), count: keyBytes.count, deallocator: .none)
|
||||||
let query = [kSecClass: kSecClassGenericPassword,
|
let query = [kSecClass: kSecClassGenericPassword,
|
||||||
kSecAttrAccount: account,
|
kSecAttrAccount: account,
|
||||||
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
|
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
|
||||||
kSecUseDataProtectionKeychain: true,
|
kSecUseDataProtectionKeychain: true,
|
||||||
kSecValueData: cfd] as [String: Any]
|
kSecAttrSynchronizable: true,
|
||||||
|
kSecValueData: cfd] as [String: Any]
|
||||||
// Add the key data.
|
|
||||||
let status = SecItemAdd(query as CFDictionary, nil)
|
// Add the key data.
|
||||||
guard status == errSecSuccess else {
|
let status = SecItemAdd(query as CFDictionary, nil)
|
||||||
throw KeyStoreError("Unable to store item: \(status.message)")
|
guard status == errSecSuccess else {
|
||||||
}
|
throw KeyStoreError("Unable to store item: \(status.message)")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
/// Reads a CryptoKit key from the keychain as a generic password.
|
|
||||||
func readKey<T: GenericPasswordConvertible>(account: String) throws -> T? {
|
/// Reads a CryptoKit key from the keychain as a generic password.
|
||||||
|
func readKey<T: GenericPasswordConvertible>(account: String) throws -> T? {
|
||||||
// Seek a generic password with the given account.
|
|
||||||
let query = [kSecClass: kSecClassGenericPassword,
|
// Seek a generic password with the given account.
|
||||||
kSecAttrAccount: account,
|
let query = [kSecClass: kSecClassGenericPassword,
|
||||||
kSecUseDataProtectionKeychain: true,
|
kSecAttrAccount: account,
|
||||||
kSecReturnData: true] as [String: Any]
|
kSecUseDataProtectionKeychain: true,
|
||||||
|
kSecReturnData: true,
|
||||||
// Find and cast the result as data.
|
kSecAttrSynchronizable: true] as [String: Any]
|
||||||
var item: CFTypeRef?
|
|
||||||
switch SecItemCopyMatching(query as CFDictionary, &item) {
|
// Find and cast the result as data.
|
||||||
case errSecSuccess:
|
var item: CFTypeRef?
|
||||||
guard let data = item as? Data else { return nil }
|
switch SecItemCopyMatching(query as CFDictionary, &item) {
|
||||||
return try T(genericKeyRepresentation: data) // Convert back to a key.
|
case errSecSuccess:
|
||||||
case errSecItemNotFound: return nil
|
guard let data = item as? Data else { return nil }
|
||||||
case let status: throw KeyStoreError("Keychain read failed: \(status.message)")
|
return try T(genericKeyRepresentation: data) // Convert back to a key.
|
||||||
}
|
case errSecItemNotFound: return nil
|
||||||
}
|
case let status: throw KeyStoreError("Keychain read failed: \(status.message)")
|
||||||
|
}
|
||||||
/// Stores a key in the keychain and then reads it back.
|
}
|
||||||
func roundTrip<T: GenericPasswordConvertible>(_ key: T) throws -> T {
|
|
||||||
|
/// Stores a key in the keychain and then reads it back.
|
||||||
// An account name for the key in the keychain.
|
func roundTrip<T: GenericPasswordConvertible>(_ key: T) throws -> T {
|
||||||
let account = "com.example.genericpassword.key"
|
|
||||||
|
// An account name for the key in the keychain.
|
||||||
// Start fresh.
|
let account = "com.example.genericpassword.key"
|
||||||
try deleteKey(account: account)
|
|
||||||
|
// Start fresh.
|
||||||
// Store and read it back.
|
try deleteKey(account: account)
|
||||||
try storeKey(key, account: account)
|
|
||||||
guard let key: T = try readKey(account: account) else {
|
// Store and read it back.
|
||||||
throw KeyStoreError("Failed to locate stored key.")
|
try storeKey(key, account: account)
|
||||||
}
|
guard let key: T = try readKey(account: account) else {
|
||||||
return key
|
throw KeyStoreError("Failed to locate stored key.")
|
||||||
}
|
}
|
||||||
|
return key
|
||||||
/// Removes any existing key with the given account.
|
}
|
||||||
func deleteKey(account: String) throws {
|
|
||||||
let query = [kSecClass: kSecClassGenericPassword,
|
/// Removes any existing key with the given account.
|
||||||
kSecUseDataProtectionKeychain: true,
|
func deleteKey(account: String) throws {
|
||||||
kSecAttrAccount: account] as [String: Any]
|
let query = [kSecClass: kSecClassGenericPassword,
|
||||||
switch SecItemDelete(query as CFDictionary) {
|
kSecUseDataProtectionKeychain: true,
|
||||||
case errSecItemNotFound, errSecSuccess: break // Okay to ignore
|
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
|
||||||
case let status:
|
kSecAttrAccount: account] as [String: Any]
|
||||||
throw KeyStoreError("Unexpected deletion error: \(status.message)")
|
switch SecItemDelete(query as CFDictionary) {
|
||||||
}
|
case errSecItemNotFound, errSecSuccess: break // Okay to ignore
|
||||||
}
|
case let status:
|
||||||
|
throw KeyStoreError("Unexpected deletion error: \(status.message)")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
/*
|
/*
|
||||||
See the LICENSE.txt file for this sample’s licensing information.
|
See the LICENSE.txt file for this sample’s licensing information.
|
||||||
|
|
||||||
Abstract:
|
Abstract:
|
||||||
Errors that can be generated as a result of attempting to store keys.
|
Errors that can be generated as a result of attempting to store keys.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// An error we can throw when something goes wrong.
|
/// An error we can throw when something goes wrong.
|
||||||
struct KeyStoreError: Error, CustomStringConvertible {
|
struct KeyStoreError: Error, CustomStringConvertible {
|
||||||
var message: String
|
var message: String
|
||||||
|
|
||||||
init(_ message: String) {
|
init(_ message: String) {
|
||||||
self.message = message
|
self.message = message
|
||||||
}
|
}
|
||||||
|
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OSStatus {
|
extension OSStatus {
|
||||||
|
|
||||||
/// A human readable message for the status.
|
/// A human readable message for the status.
|
||||||
var message: String {
|
var message: String {
|
||||||
return (SecCopyErrorMessageString(self, nil) as String?) ?? String(self)
|
return (SecCopyErrorMessageString(self, nil) as String?) ?? String(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
/*
|
/*
|
||||||
See the LICENSE.txt file for this sample’s licensing information.
|
See the LICENSE.txt file for this sample’s licensing information.
|
||||||
|
|
||||||
Abstract:
|
Abstract:
|
||||||
The interface required for conversion to a SecKey instance.
|
The interface required for conversion to a SecKey instance.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
||||||
/// The interface needed for SecKey conversion.
|
/// The interface needed for SecKey conversion.
|
||||||
protocol SecKeyConvertible: CustomStringConvertible {
|
protocol SecKeyConvertible: CustomStringConvertible {
|
||||||
/// Creates a key from an X9.63 representation.
|
/// Creates a key from an X9.63 representation.
|
||||||
init<Bytes>(x963Representation: Bytes) throws where Bytes: ContiguousBytes
|
init<Bytes>(x963Representation: Bytes) throws where Bytes: ContiguousBytes
|
||||||
|
|
||||||
/// An X9.63 representation of the key.
|
/// An X9.63 representation of the key.
|
||||||
var x963Representation: Data { get }
|
var x963Representation: Data { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SecKeyConvertible {
|
extension SecKeyConvertible {
|
||||||
/// A string version of the key for visual inspection.
|
/// A string version of the key for visual inspection.
|
||||||
/// IMPORTANT: Never log the actual key data.
|
/// IMPORTANT: Never log the actual key data.
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return self.x963Representation.withUnsafeBytes { bytes in
|
return self.x963Representation.withUnsafeBytes { bytes in
|
||||||
return "Key representation contains \(bytes.count) bytes."
|
return "Key representation contains \(bytes.count) bytes."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert that the NIST keys are convertible.
|
// Assert that the NIST keys are convertible.
|
||||||
|
|||||||
@@ -1,99 +1,99 @@
|
|||||||
/*
|
/*
|
||||||
See the LICENSE.txt file for this sample’s licensing information.
|
See the LICENSE.txt file for this sample’s licensing information.
|
||||||
|
|
||||||
Abstract:
|
Abstract:
|
||||||
Methods for storing SecKey convertible items in the keychain.
|
Methods for storing SecKey convertible items in the keychain.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import Security
|
import Security
|
||||||
|
|
||||||
struct SecKeyStore {
|
struct SecKeyStore {
|
||||||
|
|
||||||
/// Stores a CryptoKit key in the keychain as a SecKey instance.
|
/// Stores a CryptoKit key in the keychain as a SecKey instance.
|
||||||
func storeKey<T: SecKeyConvertible>(_ key: T, label: String) throws {
|
func storeKey<T: SecKeyConvertible>(_ key: T, label: String) throws {
|
||||||
|
|
||||||
// Describe the key.
|
// Describe the key.
|
||||||
let attributes = [kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
let attributes = [kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
||||||
kSecAttrKeyClass: kSecAttrKeyClassPrivate] as [String: Any]
|
kSecAttrKeyClass: kSecAttrKeyClassPrivate] as [String: Any]
|
||||||
|
|
||||||
// Get a SecKey representation.
|
// Get a SecKey representation.
|
||||||
guard let secKey = SecKeyCreateWithData(key.x963Representation as CFData,
|
guard let secKey = SecKeyCreateWithData(key.x963Representation as CFData,
|
||||||
attributes as CFDictionary,
|
attributes as CFDictionary,
|
||||||
nil)
|
nil)
|
||||||
else {
|
else {
|
||||||
throw KeyStoreError("Unable to create SecKey representation.")
|
throw KeyStoreError("Unable to create SecKey representation.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describe the add operation.
|
// Describe the add operation.
|
||||||
let query = [kSecClass: kSecClassKey,
|
let query = [kSecClass: kSecClassKey,
|
||||||
kSecAttrApplicationLabel: label,
|
kSecAttrApplicationLabel: label,
|
||||||
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
|
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
|
||||||
kSecUseDataProtectionKeychain: true,
|
kSecUseDataProtectionKeychain: true,
|
||||||
kSecValueRef: secKey] as [String: Any]
|
kSecValueRef: secKey] as [String: Any]
|
||||||
|
|
||||||
// Add the key to the keychain.
|
// Add the key to the keychain.
|
||||||
let status = SecItemAdd(query as CFDictionary, nil)
|
let status = SecItemAdd(query as CFDictionary, nil)
|
||||||
guard status == errSecSuccess else {
|
guard status == errSecSuccess else {
|
||||||
throw KeyStoreError("Unable to store item: \(status.message)")
|
throw KeyStoreError("Unable to store item: \(status.message)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a CryptoKit key from the keychain as a SecKey instance.
|
/// Reads a CryptoKit key from the keychain as a SecKey instance.
|
||||||
func readKey<T: SecKeyConvertible>(label: String) throws -> T? {
|
func readKey<T: SecKeyConvertible>(label: String) throws -> T? {
|
||||||
|
|
||||||
// Seek an elliptic-curve key with a given label.
|
// Seek an elliptic-curve key with a given label.
|
||||||
let query = [kSecClass: kSecClassKey,
|
let query = [kSecClass: kSecClassKey,
|
||||||
kSecAttrApplicationLabel: label,
|
kSecAttrApplicationLabel: label,
|
||||||
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
||||||
kSecUseDataProtectionKeychain: true,
|
kSecUseDataProtectionKeychain: true,
|
||||||
kSecReturnRef: true] as [String: Any]
|
kSecReturnRef: true] as [String: Any]
|
||||||
|
|
||||||
// Find and cast the result as a SecKey instance.
|
// Find and cast the result as a SecKey instance.
|
||||||
var item: CFTypeRef?
|
var item: CFTypeRef?
|
||||||
var secKey: SecKey
|
var secKey: SecKey
|
||||||
switch SecItemCopyMatching(query as CFDictionary, &item) {
|
switch SecItemCopyMatching(query as CFDictionary, &item) {
|
||||||
case errSecSuccess: secKey = item as! SecKey
|
case errSecSuccess: secKey = item as! SecKey
|
||||||
case errSecItemNotFound: return nil
|
case errSecItemNotFound: return nil
|
||||||
case let status: throw KeyStoreError("Keychain read failed: \(status.message)")
|
case let status: throw KeyStoreError("Keychain read failed: \(status.message)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the SecKey into a CryptoKit key.
|
// Convert the SecKey into a CryptoKit key.
|
||||||
var error: Unmanaged<CFError>?
|
var error: Unmanaged<CFError>?
|
||||||
guard let data = SecKeyCopyExternalRepresentation(secKey, &error) as Data? else {
|
guard let data = SecKeyCopyExternalRepresentation(secKey, &error) as Data? else {
|
||||||
throw KeyStoreError(error.debugDescription)
|
throw KeyStoreError(error.debugDescription)
|
||||||
}
|
}
|
||||||
let key = try T(x963Representation: data)
|
let key = try T(x963Representation: data)
|
||||||
|
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores a key in the keychain and then reads it back.
|
/// Stores a key in the keychain and then reads it back.
|
||||||
func roundTrip<T: SecKeyConvertible>(_ key: T) throws -> T {
|
func roundTrip<T: SecKeyConvertible>(_ key: T) throws -> T {
|
||||||
// A label for the key in the keychain.
|
// A label for the key in the keychain.
|
||||||
let label = "com.example.seckey.key"
|
let label = "com.example.seckey.key"
|
||||||
|
|
||||||
// Start fresh.
|
// Start fresh.
|
||||||
try deleteKey(label: label)
|
try deleteKey(label: label)
|
||||||
|
|
||||||
// Store it and then get it back.
|
// Store it and then get it back.
|
||||||
try storeKey(key, label: label)
|
try storeKey(key, label: label)
|
||||||
guard let key: T = try readKey(label: label) else {
|
guard let key: T = try readKey(label: label) else {
|
||||||
throw KeyStoreError("Failed to locate stored key.")
|
throw KeyStoreError("Failed to locate stored key.")
|
||||||
}
|
}
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes any existing key with the given label.
|
/// Removes any existing key with the given label.
|
||||||
func deleteKey(label: String) throws {
|
func deleteKey(label: String) throws {
|
||||||
let query = [kSecClass: kSecClassKey,
|
let query = [kSecClass: kSecClassKey,
|
||||||
kSecUseDataProtectionKeychain: true,
|
kSecUseDataProtectionKeychain: true,
|
||||||
kSecAttrApplicationLabel: label] as [String: Any]
|
kSecAttrApplicationLabel: label] as [String: Any]
|
||||||
switch SecItemDelete(query as CFDictionary) {
|
switch SecItemDelete(query as CFDictionary) {
|
||||||
case errSecItemNotFound, errSecSuccess: break // Ignore these.
|
case errSecItemNotFound, errSecSuccess: break // Ignore these.
|
||||||
case let status:
|
case let status:
|
||||||
throw KeyStoreError("Unexpected deletion error: \(status.message)")
|
throw KeyStoreError("Unexpected deletion error: \(status.message)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user