added iCloud sync for keys??

reindented all apple sample code
This commit is contained in:
neon443
2025-07-03 11:10:54 +01:00
parent 5ef1a51ba8
commit 200e1fd76c
6 changed files with 262 additions and 262 deletions

View File

@@ -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()
} }

View File

@@ -1,84 +1,84 @@
/* /*
See the LICENSE.txt file for this samples licensing information. See the LICENSE.txt file for this samples 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)
} }
} }

View File

@@ -1,9 +1,9 @@
/* /*
See the LICENSE.txt file for this samples licensing information. See the LICENSE.txt file for this samples 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
@@ -11,73 +11,76 @@ 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. // Add the key data.
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 generic password. /// Reads a CryptoKit key from the keychain as a generic password.
func readKey<T: GenericPasswordConvertible>(account: String) throws -> T? { func readKey<T: GenericPasswordConvertible>(account: String) throws -> T? {
// Seek a generic password with the given account. // Seek a generic password with the given account.
let query = [kSecClass: kSecClassGenericPassword, let query = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account, kSecAttrAccount: account,
kSecUseDataProtectionKeychain: true, kSecUseDataProtectionKeychain: true,
kSecReturnData: true] as [String: Any] kSecReturnData: true,
kSecAttrSynchronizable: true] as [String: Any]
// Find and cast the result as data. // Find and cast the result as data.
var item: CFTypeRef? var item: CFTypeRef?
switch SecItemCopyMatching(query as CFDictionary, &item) { switch SecItemCopyMatching(query as CFDictionary, &item) {
case errSecSuccess: case errSecSuccess:
guard let data = item as? Data else { return nil } guard let data = item as? Data else { return nil }
return try T(genericKeyRepresentation: data) // Convert back to a key. return try T(genericKeyRepresentation: data) // Convert back to a key.
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)")
} }
} }
/// 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: GenericPasswordConvertible>(_ key: T) throws -> T { func roundTrip<T: GenericPasswordConvertible>(_ key: T) throws -> T {
// An account name for the key in the keychain. // An account name for the key in the keychain.
let account = "com.example.genericpassword.key" let account = "com.example.genericpassword.key"
// Start fresh. // Start fresh.
try deleteKey(account: account) try deleteKey(account: account)
// Store and read it back. // Store and read it back.
try storeKey(key, account: account) try storeKey(key, account: account)
guard let key: T = try readKey(account: account) else { guard let key: T = try readKey(account: account) 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 account. /// Removes any existing key with the given account.
func deleteKey(account: String) throws { func deleteKey(account: String) throws {
let query = [kSecClass: kSecClassGenericPassword, let query = [kSecClass: kSecClassGenericPassword,
kSecUseDataProtectionKeychain: true, kSecUseDataProtectionKeychain: true,
kSecAttrAccount: account] as [String: Any] kSecAttrSynchronizable: kSecAttrSynchronizableAny,
switch SecItemDelete(query as CFDictionary) { kSecAttrAccount: account] as [String: Any]
case errSecItemNotFound, errSecSuccess: break // Okay to ignore switch SecItemDelete(query as CFDictionary) {
case let status: case errSecItemNotFound, errSecSuccess: break // Okay to ignore
throw KeyStoreError("Unexpected deletion error: \(status.message)") case let status:
} throw KeyStoreError("Unexpected deletion error: \(status.message)")
} }
}
} }

View File

@@ -1,29 +1,29 @@
/* /*
See the LICENSE.txt file for this samples licensing information. See the LICENSE.txt file for this samples 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)
} }
} }

View File

@@ -1,30 +1,30 @@
/* /*
See the LICENSE.txt file for this samples licensing information. See the LICENSE.txt file for this samples 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.

View File

@@ -1,9 +1,9 @@
/* /*
See the LICENSE.txt file for this samples licensing information. See the LICENSE.txt file for this samples 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
@@ -11,89 +11,89 @@ 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)")
} }
} }
} }