Save text (#632)

This commit is contained in:
Max Goedjen 2025-08-24 20:19:29 -07:00 committed by GitHub
parent 828c61cb2f
commit f0a6f2e43b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 53 additions and 32 deletions

View File

@ -2557,67 +2557,67 @@
"localizations" : {
"ca" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Canvia el nom"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Umbenennen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Rename"
"value" : "Save"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Renommer"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Rinomina"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "名前の変更"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "이름 변경"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Zmień nazwę"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Renomear"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Переименовать"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "重命名"
}
}

View File

@ -46,16 +46,16 @@ extension SecureEnclave {
let auth: AuthenticationRequirement = String(describing: accessControl)
.contains("DeviceOwnerAuthentication") ? .presenceRequired : .unknown
let parsed = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: tokenObjectID)
let secret = Secret(id: id, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth))
let secret = Secret(id: UUID().uuidString, name: name, publicKey: parsed.publicKey.x963Representation, attributes: Attributes(keyType: .init(algorithm: .ecdsa, size: 256), authentication: auth))
guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
logger.log("Skipping \(name), public key already present. Marking as migrated.")
try markMigrated(secret: secret)
try markMigrated(secret: secret, oldID: id)
continue
}
logger.log("Migrating \(name).")
try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes)
logger.log("Migrated \(name).")
try markMigrated(secret: secret)
try markMigrated(secret: secret, oldID: id)
migrated = true
}
if migrated {
@ -65,13 +65,13 @@ extension SecureEnclave {
public func markMigrated(secret: Secret) throws {
public func markMigrated(secret: Secret, oldID: Data) throws {
let updateQuery = KeychainDictionary([
kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id as CFData
kSecAttrApplicationLabel: secret.id
])
let newID = secret.id + Constants.migrationMagicNumber
let newID = oldID + Constants.migrationMagicNumber
let updatedAttributes = KeychainDictionary([
kSecAttrApplicationLabel: newID as CFData
])

View File

@ -6,13 +6,13 @@ extension SecureEnclave {
/// An implementation of Secret backed by the Secure Enclave.
public struct Secret: SecretKit.Secret {
public let id: Data
public let id: String
public let name: String
public let publicKey: Data
public let attributes: Attributes
init(
id: Data,
id: String,
name: String,
publicKey: Data,
attributes: Attributes

View File

@ -48,9 +48,9 @@ extension SecureEnclave {
kSecClass: Constants.keyClass,
kSecAttrService: Constants.keyTag,
kSecUseDataProtectionKeychain: true,
kSecAttrAccount: String(decoding: secret.id, as: UTF8.self),
kSecAttrAccount: secret.id,
kSecReturnAttributes: true,
kSecReturnData: true
kSecReturnData: true,
])
var untyped: CFTypeRef?
let status = SecItemCopyMatching(queryAttributes, &untyped)
@ -143,8 +143,7 @@ extension SecureEnclave {
kSecClass: Constants.keyClass,
kSecAttrService: Constants.keyTag,
kSecUseDataProtectionKeychain: true,
kSecAttrAccount: String(decoding: secret.id, as: UTF8.self),
kSecAttrCanSign: true,
kSecAttrAccount: secret.id,
])
let status = SecItemDelete(deleteAttributes)
if status != errSecSuccess {
@ -155,12 +154,14 @@ extension SecureEnclave {
public func update(secret: Secret, name: String, attributes: Attributes) async throws {
let updateQuery = KeychainDictionary([
kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id as CFData
kSecClass: Constants.keyClass,
kSecAttrAccount: secret.id,
])
let attributes = try JSONEncoder().encode(attributes)
let updatedAttributes = KeychainDictionary([
kSecAttrLabel: name,
kSecAttrGeneric: attributes,
])
let status = SecItemUpdate(updateQuery, updatedAttributes)
@ -213,10 +214,9 @@ extension SecureEnclave.Store {
do {
let name = $0[kSecAttrLabel] as? String ?? String(localized: "unnamed_secret")
guard let attributesData = $0[kSecAttrGeneric] as? Data,
let idString = $0[kSecAttrAccount] as? String else {
let id = $0[kSecAttrAccount] as? String else {
throw MissingAttributesError()
}
let id = Data(idString.utf8)
let attributes = try JSONDecoder().decode(Attributes.self, from: attributesData)
let keyData = $0[kSecValueData] as! Data
let publicKey: Data

View File

@ -8,6 +8,7 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
var dismissalBlock: (Bool) -> ()
@State private var confirm = ""
@State var errorText: String?
var body: some View {
VStack {
@ -31,6 +32,11 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
}
}
}
if let errorText {
Text(verbatim: errorText)
.foregroundStyle(.red)
.font(.callout)
}
HStack {
Spacer()
Button(.deleteConfirmationDeleteButton, action: delete)
@ -50,8 +56,12 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
func delete() {
Task {
try! await store.delete(secret: secret)
dismissalBlock(true)
do {
try await store.delete(secret: secret)
dismissalBlock(true)
} catch {
errorText = error.localizedDescription
}
}
}

View File

@ -9,6 +9,7 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
@State private var name: String
@State private var publicKeyAttribution: String
@State var errorText: String?
init(store: StoreType, secret: StoreType.SecretType, dismissalBlock: @escaping (Bool) -> ()) {
self.store = store
@ -30,6 +31,11 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
.foregroundStyle(.secondary)
}
}
if let errorText {
Text(verbatim: errorText)
.foregroundStyle(.red)
.font(.callout)
}
}
HStack {
Button(.editSaveButton, action: rename)
@ -37,7 +43,8 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
.keyboardShortcut(.return)
Button(.editCancelButton) {
dismissalBlock(false)
}.keyboardShortcut(.cancelAction)
}
.keyboardShortcut(.cancelAction)
}
.padding()
}
@ -50,8 +57,12 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
attributes.publicKeyAttribution = publicKeyAttribution
}
Task {
try? await store.update(secret: secret, name: name, attributes: attributes)
dismissalBlock(true)
do {
try await store.update(secret: secret, name: name, attributes: attributes)
dismissalBlock(true)
} catch {
errorText = error.localizedDescription
}
}
}
}