This commit is contained in:
Max Goedjen 2025-08-24 00:34:37 -07:00
parent 3a62a855df
commit cec13ea994
No known key found for this signature in database
13 changed files with 50 additions and 51 deletions

View File

@ -93,7 +93,7 @@ extension Agent {
for secret in secrets {
let keyBlob = writer.data(secret: secret)
let curveData = writer.curveType(for: secret.keyType).data(using: .utf8)!
let curveData = Data(writer.curveType(for: secret.keyType).utf8)
keyData.append(writer.lengthAndData(of: keyBlob))
keyData.append(writer.lengthAndData(of: curveData))
@ -138,7 +138,7 @@ extension Agent {
let signed = try await store.sign(data: dataToSign, with: secret, for: provenance)
let derSignature = signed
let curveData = writer.curveType(for: secret.keyType).data(using: .utf8)!
let curveData = Data(writer.curveType(for: secret.keyType).utf8)
// Convert from DER formatted rep to raw (r||s)

View File

@ -3,7 +3,7 @@ import Foundation
/// Type eraser for Secret.
public struct AnySecret: Secret, @unchecked Sendable {
public let base: Any
public let base: any Secret
private let hashable: AnyHashable
private let _id: () -> AnyHashable
private let _name: () -> String
@ -19,7 +19,7 @@ public struct AnySecret: Secret, @unchecked Sendable {
_publicKey = secret._publicKey
_attributes = secret._attributes
} else {
base = secret as Any
base = secret
self.hashable = secret
_id = { secret.id as AnyHashable }
_name = { secret.name }

View File

@ -3,7 +3,7 @@ import Foundation
/// Type eraser for SecretStore.
open class AnySecretStore: SecretStore, @unchecked Sendable {
let base: any Sendable
let base: any SecretStore
private let _isAvailable: @MainActor @Sendable () -> Bool
private let _id: @Sendable () -> UUID
private let _name: @MainActor @Sendable () -> String

View File

@ -40,7 +40,7 @@ public actor OpenSSHCertificateHandler: Sendable {
let curveIdentifier = reader.readNextChunk()
let publicKey = reader.readNextChunk()
let curveType = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "").data(using: .utf8)!
let curveType = Data(certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "").utf8)
return writer.lengthAndData(of: curveType) +
writer.lengthAndData(of: curveIdentifier) +
writer.lengthAndData(of: publicKey)
@ -78,14 +78,13 @@ public actor OpenSSHCertificateHandler: Sendable {
throw OpenSSHCertificateError.parsingFailed
}
if certElements.count >= 3, let certName = certElements[2].data(using: .utf8) {
if certElements.count >= 3 {
let certName = Data(certElements[2].utf8)
return (certDecoded, certName)
} else if let certName = secret.name.data(using: .utf8) {
}
let certName = Data(secret.name.utf8)
logger.info("Certificate for \(secret.name) does not have a name tag, using secret name instead")
return (certDecoded, certName)
} else {
throw OpenSSHCertificateError.parsingFailed
}
}
}

View File

@ -11,8 +11,8 @@ public struct OpenSSHKeyWriter: Sendable {
/// Generates an OpenSSH data payload identifying the secret.
/// - Returns: OpenSSH data payload identifying the secret.
public func data<SecretType: Secret>(secret: SecretType) -> Data {
lengthAndData(of: curveType(for: secret.keyType).data(using: .utf8)!) +
lengthAndData(of: curveIdentifier(for: secret.keyType).data(using: .utf8)!) +
lengthAndData(of: Data(curveType(for: secret.keyType).utf8)) +
lengthAndData(of: Data(curveIdentifier(for: secret.keyType).utf8)) +
lengthAndData(of: secret.publicKey)
}

View File

@ -32,7 +32,7 @@ public final class PublicKeyFileStoreController: Sendable {
try? FileManager.default.createDirectory(at: URL(fileURLWithPath: directory), withIntermediateDirectories: false, attributes: nil)
for secret in secrets {
let path = publicKeyPath(for: secret)
guard let data = keyWriter.openSSHString(secret: secret).data(using: .utf8) else { continue }
let data = Data(keyWriter.openSSHString(secret: secret).utf8)
FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
}
logger.log("Finished writing public keys")

View File

@ -20,7 +20,7 @@ extension SecureEnclave {
private let persistentAuthenticationHandler = PersistentAuthenticationHandler()
/// Initializes a Store.
@MainActor public init() {
@MainActor init() {
loadSecrets()
Task {
for await _ in DistributedNotificationCenter.default().notifications(named: .secretStoreUpdated) {
@ -33,7 +33,7 @@ extension SecureEnclave {
// MARK: SecretStore
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
var context: LAContext
if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
context = existing.context
@ -84,7 +84,7 @@ extension SecureEnclave {
}
public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool {
func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool {
let context = LAContext()
context.localizedReason = String(localized: "auth_context_request_verify_description_\(secret.name)")
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
@ -119,22 +119,22 @@ extension SecureEnclave {
return verified
}
public func existingPersistedAuthenticationContext(secret: Secret) async -> PersistedAuthenticationContext? {
func existingPersistedAuthenticationContext(secret: Secret) async -> PersistedAuthenticationContext? {
await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret)
}
public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
try await persistentAuthenticationHandler.persistAuthentication(secret: secret, forDuration: duration)
}
@MainActor public func reloadSecrets() {
@MainActor func reloadSecrets() {
secrets.removeAll()
loadSecrets()
}
// MARK: SecretStoreModifiable
public func create(name: String, attributes: Attributes) async throws {
func create(name: String, attributes: Attributes) async throws {
var accessError: SecurityError?
let flags: SecAccessControlCreateFlags = switch attributes.authentication {
case .notRequired:
@ -174,7 +174,7 @@ extension SecureEnclave {
await reloadSecrets()
}
public func delete(secret: Secret) async throws {
func delete(secret: Secret) async throws {
let deleteAttributes = KeychainDictionary([
kSecClass: Constants.keyClass,
kSecAttrService: SecureEnclave.Constants.keyTag,
@ -188,7 +188,7 @@ extension SecureEnclave {
await reloadSecrets()
}
public func update(secret: Secret, name: String, attributes: Attributes) async throws {
func update(secret: Secret, name: String, attributes: Attributes) async throws {
let updateQuery = KeychainDictionary([
kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id as CFData
@ -205,7 +205,7 @@ extension SecureEnclave {
await reloadSecrets()
}
public var supportedKeyTypes: [KeyType] {
var supportedKeyTypes: [KeyType] {
[
.init(algorithm: .ecdsa, size: 256),
.init(algorithm: .mldsa, size: 65),
@ -245,7 +245,7 @@ extension SecureEnclave.CryptoKitStore {
switch (attributes.keyType.algorithm, attributes.keyType.size) {
case (.ecdsa, 256):
let key = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: keyData)
publicKey = key.publicKey.rawRepresentation
publicKey = key.publicKey.x963Representation
case (.mldsa, 65):
guard #available(macOS 26.0, *) else { throw UnsupportedAlgorithmError() }
let key = try CryptoKit.SecureEnclave.MLDSA65.PrivateKey(dataRepresentation: keyData)

View File

@ -8,22 +8,22 @@ import SecretKit
extension SecureEnclave {
/// An implementation of Store backed by the Secure Enclave.
@Observable public final class VanillaKeychainStore: SecretStoreModifiable {
@Observable final class VanillaKeychainStore: SecretStoreModifiable {
@MainActor public var secrets: [Secret] = []
public var isAvailable: Bool {
@MainActor var secrets: [Secret] = []
var isAvailable: Bool {
CryptoKit.SecureEnclave.isAvailable
}
public let id = UUID()
public let name = String(localized: .secureEnclave)
public var supportedKeyTypes: [KeyType] {
let id = UUID()
let name = String(localized: .secureEnclave)
var supportedKeyTypes: [KeyType] {
[KeyType(algorithm: .ecdsa, size: 256)]
}
private let persistentAuthenticationHandler = PersistentAuthenticationHandler()
/// Initializes a Store.
@MainActor public init() {
@MainActor init() {
loadSecrets()
}
@ -31,7 +31,7 @@ extension SecureEnclave {
// MARK: SecretStore
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
let context: LAContext
if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
context = existing.context
@ -68,26 +68,26 @@ extension SecureEnclave {
return signature as Data
}
public func existingPersistedAuthenticationContext(secret: Secret) async -> PersistedAuthenticationContext? {
func existingPersistedAuthenticationContext(secret: Secret) async -> PersistedAuthenticationContext? {
await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret)
}
public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
try await persistentAuthenticationHandler.persistAuthentication(secret: secret, forDuration: duration)
}
@MainActor public func reloadSecrets() {
@MainActor func reloadSecrets() {
secrets.removeAll()
loadSecrets()
}
// MARK: SecretStoreModifiable
public func create(name: String, attributes: Attributes) async throws {
func create(name: String, attributes: Attributes) async throws {
throw DeprecatedCreationStore()
}
public func delete(secret: Secret) async throws {
func delete(secret: Secret) async throws {
let deleteAttributes = KeychainDictionary([
kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id as CFData
@ -99,7 +99,7 @@ extension SecureEnclave {
await reloadSecrets()
}
public func update(secret: Secret, name: String, attributes: Attributes) async throws {
func update(secret: Secret, name: String, attributes: Attributes) async throws {
let updateQuery = KeychainDictionary([
kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id as CFData

View File

@ -79,9 +79,9 @@ extension Stub {
struct Secret: SecretKit.Secret, CustomDebugStringConvertible {
let id = UUID().uuidString.data(using: .utf8)!
let id = Data(UUID().uuidString.utf8)
let name = UUID().uuidString
let algorithm = Algorithm.ellipticCurve
let algorithm = Algorithm.ecdsa
let keySize: Int
let publicKey: Data

View File

@ -7,12 +7,12 @@ import Testing
@Suite struct AnySecretTests {
@Test func eraser() {
let secret = SmartCard.Secret(id: UUID().uuidString.data(using: .utf8)!, name: "Name", algorithm: .ellipticCurve, keySize: 256, publicKey: UUID().uuidString.data(using: .utf8)!)
let data = Data(UUID().uuidString.utf8)
let secret = SmartCard.Secret(id: data, name: "Name", publicKey: data, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 256)))
let erased = AnySecret(secret)
#expect(erased.id == secret.id as AnyHashable)
#expect(erased.name == secret.name)
#expect(erased.algorithm == secret.algorithm)
#expect(erased.keySize == secret.keySize)
#expect(erased.keyType == secret.keyType)
#expect(erased.publicKey == secret.publicKey)
}

View File

@ -18,7 +18,7 @@ import Testing
@Test func ecdsa256PublicKey() {
#expect(writer.openSSHString(secret: Constants.ecdsa256Secret) ==
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo=")
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo= test@example.com")
}
@Test func ecdsa256Hash() {
@ -35,7 +35,7 @@ import Testing
@Test func ecdsa384PublicKey() {
#expect(writer.openSSHString(secret: Constants.ecdsa384Secret) ==
"ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBG2MNc/C5OTHFE2tBvbZCVcpOGa8vBMquiTLkH4lwkeqOPxhi+PyYUfQZMTRJNPiTyWPoMBqNiCIFRVv60yPN/AHufHaOgbdTP42EgMlMMImkAjYUEv9DESHTVIs2PW1yQ==")
"ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBG2MNc/C5OTHFE2tBvbZCVcpOGa8vBMquiTLkH4lwkeqOPxhi+PyYUfQZMTRJNPiTyWPoMBqNiCIFRVv60yPN/AHufHaOgbdTP42EgMlMMImkAjYUEv9DESHTVIs2PW1yQ== test@example.com")
}
@Test func ecdsa384Hash() {
@ -47,8 +47,8 @@ import Testing
extension OpenSSHWriterTests {
enum Constants {
static let ecdsa256Secret = SmartCard.Secret(id: Data(), name: "Test Key (ECDSA 256)", algorithm: .ellipticCurve, keySize: 256, publicKey: Data(base64Encoded: "BOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo=")!)
static let ecdsa384Secret = SmartCard.Secret(id: Data(), name: "Test Key (ECDSA 384)", algorithm: .ellipticCurve, keySize: 384, publicKey: Data(base64Encoded: "BG2MNc/C5OTHFE2tBvbZCVcpOGa8vBMquiTLkH4lwkeqOPxhi+PyYUfQZMTRJNPiTyWPoMBqNiCIFRVv60yPN/AHufHaOgbdTP42EgMlMMImkAjYUEv9DESHTVIs2PW1yQ==")!)
static let ecdsa256Secret = SmartCard.Secret(id: Data(), name: "Test Key (ECDSA 256)", publicKey: Data(base64Encoded: "BOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo=")!, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 256), publicKeyAttribution: "test@example.com"))
static let ecdsa384Secret = SmartCard.Secret(id: Data(), name: "Test Key (ECDSA 384)", publicKey: Data(base64Encoded: "BG2MNc/C5OTHFE2tBvbZCVcpOGa8vBMquiTLkH4lwkeqOPxhi+PyYUfQZMTRJNPiTyWPoMBqNiCIFRVv60yPN/AHufHaOgbdTP42EgMlMMImkAjYUEv9DESHTVIs2PW1yQ==")!, attributes: Attributes(keyType: KeyType(algorithm: .ecdsa, size: 384), publicKeyAttribution: "test@example.com"))
}

View File

@ -56,7 +56,7 @@ struct ShellConfigurationController {
} catch {
return false
}
handle.write("\n# Secretive Config\n\(shellInstructions.text)\n".data(using: .utf8)!)
handle.write(Data("\n# Secretive Config\n\(shellInstructions.text)\n".utf8))
return true
}

View File

@ -9,7 +9,7 @@ extension Preview {
let id = UUID().uuidString
let name: String
let publicKey = UUID().uuidString.data(using: .utf8)!
let publicKey = Data(UUID().uuidString.utf8)
var attributes: Attributes {
Attributes(
keyType: .init(algorithm: .ecdsa, size: 256),