mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-08-31 01:20:57 +00:00
WIP
This commit is contained in:
parent
3a62a855df
commit
cec13ea994
@ -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)
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
logger.info("Certificate for \(secret.name) does not have a name tag, using secret name instead")
|
||||
return (certDecoded, certName)
|
||||
} else {
|
||||
throw OpenSSHCertificateError.parsingFailed
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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"))
|
||||
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user