mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-08-26 23:20:57 +00:00
Save text (#632)
This commit is contained in:
parent
828c61cb2f
commit
f0a6f2e43b
@ -2557,67 +2557,67 @@
|
|||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ca" : {
|
"ca" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "needs_review",
|
||||||
"value" : "Canvia el nom"
|
"value" : "Canvia el nom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "needs_review",
|
||||||
"value" : "Umbenennen"
|
"value" : "Umbenennen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Rename"
|
"value" : "Save"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fr" : {
|
"fr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "needs_review",
|
||||||
"value" : "Renommer"
|
"value" : "Renommer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"it" : {
|
"it" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "needs_review",
|
||||||
"value" : "Rinomina"
|
"value" : "Rinomina"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ja" : {
|
"ja" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "needs_review",
|
||||||
"value" : "名前の変更"
|
"value" : "名前の変更"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ko" : {
|
"ko" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "needs_review",
|
||||||
"value" : "이름 변경"
|
"value" : "이름 변경"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pl" : {
|
"pl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "needs_review",
|
||||||
"value" : "Zmień nazwę"
|
"value" : "Zmień nazwę"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pt-BR" : {
|
"pt-BR" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "needs_review",
|
||||||
"value" : "Renomear"
|
"value" : "Renomear"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ru" : {
|
"ru" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "needs_review",
|
||||||
"value" : "Переименовать"
|
"value" : "Переименовать"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "needs_review",
|
||||||
"value" : "重命名"
|
"value" : "重命名"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,16 +46,16 @@ extension SecureEnclave {
|
|||||||
let auth: AuthenticationRequirement = String(describing: accessControl)
|
let auth: AuthenticationRequirement = String(describing: accessControl)
|
||||||
.contains("DeviceOwnerAuthentication") ? .presenceRequired : .unknown
|
.contains("DeviceOwnerAuthentication") ? .presenceRequired : .unknown
|
||||||
let parsed = try CryptoKit.SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: tokenObjectID)
|
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 {
|
guard !migratedPublicKeys.contains(parsed.publicKey.x963Representation) else {
|
||||||
logger.log("Skipping \(name), public key already present. Marking as migrated.")
|
logger.log("Skipping \(name), public key already present. Marking as migrated.")
|
||||||
try markMigrated(secret: secret)
|
try markMigrated(secret: secret, oldID: id)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logger.log("Migrating \(name).")
|
logger.log("Migrating \(name).")
|
||||||
try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes)
|
try store.saveKey(tokenObjectID, name: name, attributes: secret.attributes)
|
||||||
logger.log("Migrated \(name).")
|
logger.log("Migrated \(name).")
|
||||||
try markMigrated(secret: secret)
|
try markMigrated(secret: secret, oldID: id)
|
||||||
migrated = true
|
migrated = true
|
||||||
}
|
}
|
||||||
if migrated {
|
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([
|
let updateQuery = KeychainDictionary([
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrApplicationLabel: secret.id as CFData
|
kSecAttrApplicationLabel: secret.id
|
||||||
])
|
])
|
||||||
|
|
||||||
let newID = secret.id + Constants.migrationMagicNumber
|
let newID = oldID + Constants.migrationMagicNumber
|
||||||
let updatedAttributes = KeychainDictionary([
|
let updatedAttributes = KeychainDictionary([
|
||||||
kSecAttrApplicationLabel: newID as CFData
|
kSecAttrApplicationLabel: newID as CFData
|
||||||
])
|
])
|
||||||
|
@ -6,13 +6,13 @@ extension SecureEnclave {
|
|||||||
/// An implementation of Secret backed by the Secure Enclave.
|
/// An implementation of Secret backed by the Secure Enclave.
|
||||||
public struct Secret: SecretKit.Secret {
|
public struct Secret: SecretKit.Secret {
|
||||||
|
|
||||||
public let id: Data
|
public let id: String
|
||||||
public let name: String
|
public let name: String
|
||||||
public let publicKey: Data
|
public let publicKey: Data
|
||||||
public let attributes: Attributes
|
public let attributes: Attributes
|
||||||
|
|
||||||
init(
|
init(
|
||||||
id: Data,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
publicKey: Data,
|
publicKey: Data,
|
||||||
attributes: Attributes
|
attributes: Attributes
|
||||||
|
@ -48,9 +48,9 @@ extension SecureEnclave {
|
|||||||
kSecClass: Constants.keyClass,
|
kSecClass: Constants.keyClass,
|
||||||
kSecAttrService: Constants.keyTag,
|
kSecAttrService: Constants.keyTag,
|
||||||
kSecUseDataProtectionKeychain: true,
|
kSecUseDataProtectionKeychain: true,
|
||||||
kSecAttrAccount: String(decoding: secret.id, as: UTF8.self),
|
kSecAttrAccount: secret.id,
|
||||||
kSecReturnAttributes: true,
|
kSecReturnAttributes: true,
|
||||||
kSecReturnData: true
|
kSecReturnData: true,
|
||||||
])
|
])
|
||||||
var untyped: CFTypeRef?
|
var untyped: CFTypeRef?
|
||||||
let status = SecItemCopyMatching(queryAttributes, &untyped)
|
let status = SecItemCopyMatching(queryAttributes, &untyped)
|
||||||
@ -143,8 +143,7 @@ extension SecureEnclave {
|
|||||||
kSecClass: Constants.keyClass,
|
kSecClass: Constants.keyClass,
|
||||||
kSecAttrService: Constants.keyTag,
|
kSecAttrService: Constants.keyTag,
|
||||||
kSecUseDataProtectionKeychain: true,
|
kSecUseDataProtectionKeychain: true,
|
||||||
kSecAttrAccount: String(decoding: secret.id, as: UTF8.self),
|
kSecAttrAccount: secret.id,
|
||||||
kSecAttrCanSign: true,
|
|
||||||
])
|
])
|
||||||
let status = SecItemDelete(deleteAttributes)
|
let status = SecItemDelete(deleteAttributes)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
@ -155,12 +154,14 @@ extension SecureEnclave {
|
|||||||
|
|
||||||
public func update(secret: Secret, name: String, attributes: Attributes) async throws {
|
public func update(secret: Secret, name: String, attributes: Attributes) async throws {
|
||||||
let updateQuery = KeychainDictionary([
|
let updateQuery = KeychainDictionary([
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: Constants.keyClass,
|
||||||
kSecAttrApplicationLabel: secret.id as CFData
|
kSecAttrAccount: secret.id,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
let attributes = try JSONEncoder().encode(attributes)
|
||||||
let updatedAttributes = KeychainDictionary([
|
let updatedAttributes = KeychainDictionary([
|
||||||
kSecAttrLabel: name,
|
kSecAttrLabel: name,
|
||||||
|
kSecAttrGeneric: attributes,
|
||||||
])
|
])
|
||||||
|
|
||||||
let status = SecItemUpdate(updateQuery, updatedAttributes)
|
let status = SecItemUpdate(updateQuery, updatedAttributes)
|
||||||
@ -213,10 +214,9 @@ extension SecureEnclave.Store {
|
|||||||
do {
|
do {
|
||||||
let name = $0[kSecAttrLabel] as? String ?? String(localized: "unnamed_secret")
|
let name = $0[kSecAttrLabel] as? String ?? String(localized: "unnamed_secret")
|
||||||
guard let attributesData = $0[kSecAttrGeneric] as? Data,
|
guard let attributesData = $0[kSecAttrGeneric] as? Data,
|
||||||
let idString = $0[kSecAttrAccount] as? String else {
|
let id = $0[kSecAttrAccount] as? String else {
|
||||||
throw MissingAttributesError()
|
throw MissingAttributesError()
|
||||||
}
|
}
|
||||||
let id = Data(idString.utf8)
|
|
||||||
let attributes = try JSONDecoder().decode(Attributes.self, from: attributesData)
|
let attributes = try JSONDecoder().decode(Attributes.self, from: attributesData)
|
||||||
let keyData = $0[kSecValueData] as! Data
|
let keyData = $0[kSecValueData] as! Data
|
||||||
let publicKey: Data
|
let publicKey: Data
|
||||||
|
@ -8,6 +8,7 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
var dismissalBlock: (Bool) -> ()
|
var dismissalBlock: (Bool) -> ()
|
||||||
|
|
||||||
@State private var confirm = ""
|
@State private var confirm = ""
|
||||||
|
@State var errorText: String?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@ -31,6 +32,11 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let errorText {
|
||||||
|
Text(verbatim: errorText)
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
.font(.callout)
|
||||||
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Button(.deleteConfirmationDeleteButton, action: delete)
|
Button(.deleteConfirmationDeleteButton, action: delete)
|
||||||
@ -50,8 +56,12 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
|
|
||||||
func delete() {
|
func delete() {
|
||||||
Task {
|
Task {
|
||||||
try! await store.delete(secret: secret)
|
do {
|
||||||
|
try await store.delete(secret: secret)
|
||||||
dismissalBlock(true)
|
dismissalBlock(true)
|
||||||
|
} catch {
|
||||||
|
errorText = error.localizedDescription
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
|
|
||||||
@State private var name: String
|
@State private var name: String
|
||||||
@State private var publicKeyAttribution: String
|
@State private var publicKeyAttribution: String
|
||||||
|
@State var errorText: String?
|
||||||
|
|
||||||
init(store: StoreType, secret: StoreType.SecretType, dismissalBlock: @escaping (Bool) -> ()) {
|
init(store: StoreType, secret: StoreType.SecretType, dismissalBlock: @escaping (Bool) -> ()) {
|
||||||
self.store = store
|
self.store = store
|
||||||
@ -30,6 +31,11 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let errorText {
|
||||||
|
Text(verbatim: errorText)
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
.font(.callout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Button(.editSaveButton, action: rename)
|
Button(.editSaveButton, action: rename)
|
||||||
@ -37,7 +43,8 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
.keyboardShortcut(.return)
|
.keyboardShortcut(.return)
|
||||||
Button(.editCancelButton) {
|
Button(.editCancelButton) {
|
||||||
dismissalBlock(false)
|
dismissalBlock(false)
|
||||||
}.keyboardShortcut(.cancelAction)
|
}
|
||||||
|
.keyboardShortcut(.cancelAction)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
@ -50,8 +57,12 @@ struct EditSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
attributes.publicKeyAttribution = publicKeyAttribution
|
attributes.publicKeyAttribution = publicKeyAttribution
|
||||||
}
|
}
|
||||||
Task {
|
Task {
|
||||||
try? await store.update(secret: secret, name: name, attributes: attributes)
|
do {
|
||||||
|
try await store.update(secret: secret, name: name, attributes: attributes)
|
||||||
dismissalBlock(true)
|
dismissalBlock(true)
|
||||||
|
} catch {
|
||||||
|
errorText = error.localizedDescription
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user