Compare commits

..

3 Commits

Author SHA1 Message Date
1947b326a4 Scheudle timer on mian 2024-01-20 19:47:55 -08:00
3df4bcef3c . 2024-01-20 19:08:20 -08:00
18ba03bf03 . 2024-01-20 19:07:00 -08:00
7 changed files with 41 additions and 286 deletions

View File

@ -44,7 +44,7 @@ let package = Package(
.target( .target(
name: "SecureEnclaveSecretKit", name: "SecureEnclaveSecretKit",
dependencies: ["SecretKit"], dependencies: ["SecretKit"],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency"), .unsafeFlags(["-warnings-as-errors"])] swiftSettings: [.unsafeFlags(["-warnings-as-errors"])]
), ),
.target( .target(
name: "SmartCardSecretKit", name: "SmartCardSecretKit",

View File

@ -17,7 +17,7 @@ public protocol Secret: Identifiable, Hashable {
} }
/// The type of algorithm the Secret uses. Currently, only elliptic curve algorithms are supported. /// The type of algorithm the Secret uses. Currently, only elliptic curve algorithms are supported.
public enum Algorithm: Hashable { public enum Algorithm: Hashable, Sendable {
case ellipticCurve case ellipticCurve
case rsa case rsa

View File

@ -5,7 +5,7 @@ import SecretKit
extension SecureEnclave { 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, Sendable {
public let id: Data public let id: Data
public let name: String public let name: String

View File

@ -75,7 +75,7 @@ extension SecureEnclave {
public func delete(secret: Secret) throws { public func delete(secret: Secret) throws {
let deleteAttributes = KeychainDictionary([ let deleteAttributes = KeychainDictionary([
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id kSecAttrApplicationLabel: secret.id as CFData
]) ])
let status = SecItemDelete(deleteAttributes) let status = SecItemDelete(deleteAttributes)
if status != errSecSuccess { if status != errSecSuccess {
@ -87,7 +87,7 @@ extension SecureEnclave {
public func update(secret: Secret, name: String) throws { public func update(secret: Secret, name: String) throws {
let updateQuery = KeychainDictionary([ let updateQuery = KeychainDictionary([
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id kSecAttrApplicationLabel: secret.id as CFData
]) ])
let updatedAttributes = KeychainDictionary([ let updatedAttributes = KeychainDictionary([
@ -114,7 +114,7 @@ extension SecureEnclave {
let attributes = KeychainDictionary([ let attributes = KeychainDictionary([
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrApplicationLabel: secret.id, kSecAttrApplicationLabel: secret.id as CFData,
kSecAttrKeyType: Constants.keyType, kSecAttrKeyType: Constants.keyType,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
kSecAttrApplicationTag: Constants.keyTag, kSecAttrApplicationTag: Constants.keyTag,
@ -145,7 +145,7 @@ extension SecureEnclave {
let attributes = KeychainDictionary([ let attributes = KeychainDictionary([
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrApplicationLabel: secret.id, kSecAttrApplicationLabel: secret.id as CFData,
kSecAttrKeyType: Constants.keyType, kSecAttrKeyType: Constants.keyType,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
kSecAttrApplicationTag: Constants.keyTag, kSecAttrApplicationTag: Constants.keyTag,
@ -180,7 +180,7 @@ extension SecureEnclave {
public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) throws { public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) throws {
let newContext = LAContext() let newContext = LAContext()
newContext.touchIDAuthenticationAllowableReuseDuration = duration newContext.touchIDAuthenticationAllowableReuseDuration = max(duration, LATouchIDAuthenticationMaximumAllowableReuseDuration)
newContext.localizedCancelTitle = String(localized: "auth_context_request_deny_button") newContext.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
let formatter = DateComponentsFormatter() let formatter = DateComponentsFormatter()
@ -196,6 +196,23 @@ extension SecureEnclave {
guard success else { return } guard success else { return }
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration) let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
self?.persistedAuthenticationContexts[secret] = context self?.persistedAuthenticationContexts[secret] = context
// Contexts will expire within LATouchIDAuthenticationMaximumAllowableReuseDuration unless we periodically refresh them
if duration > LATouchIDAuthenticationMaximumAllowableReuseDuration {
DispatchQueue.main.async {
Timer.scheduledTimer(withTimeInterval: LATouchIDAuthenticationMaximumAllowableReuseDuration - 10, repeats: true) { [weak self] timer in
print("Refreshing context")
guard let refreshContext = self?.persistedAuthenticationContexts[secret] else { return }
guard refreshContext.valid else {
timer.invalidate()
return
}
refreshContext.context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "Refresh") { success, _ in
guard success else { return }
print("Refreshed")
}
}
}
}
} }
} }
@ -304,8 +321,8 @@ extension SecureEnclave.Store {
extension SecureEnclave { extension SecureEnclave {
enum Constants { enum Constants {
static let keyTag = Data("com.maxgoedjen.secretive.secureenclave.key".utf8) static let keyTag = "com.maxgoedjen.secretive.secureenclave.key".data(using: .utf8)! as CFData
static let keyType = kSecAttrKeyTypeECSECPrimeRandom as String static let keyType = kSecAttrKeyTypeECSECPrimeRandom
static let unauthenticatedThreshold: TimeInterval = 0.05 static let unauthenticatedThreshold: TimeInterval = 0.05
} }

View File

@ -55,7 +55,7 @@ extension SmartCard {
let attributes = KeychainDictionary([ let attributes = KeychainDictionary([
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrApplicationLabel: secret.id, kSecAttrApplicationLabel: secret.id as CFData,
kSecAttrTokenID: tokenID, kSecAttrTokenID: tokenID,
kSecUseAuthenticationContext: context, kSecUseAuthenticationContext: context,
kSecReturnRef: true kSecReturnRef: true
@ -217,7 +217,7 @@ extension SmartCard.Store {
let attributes = KeychainDictionary([ let attributes = KeychainDictionary([
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrApplicationLabel: secret.id, kSecAttrApplicationLabel: secret.id as CFData,
kSecAttrTokenID: tokenID, kSecAttrTokenID: tokenID,
kSecUseAuthenticationContext: context, kSecUseAuthenticationContext: context,
kSecReturnRef: true kSecReturnRef: true

View File

@ -433,7 +433,6 @@
fr, fr,
de, de,
"pt-BR", "pt-BR",
fi,
); );
mainGroup = 50617D7623FCE48D0099B055; mainGroup = 50617D7623FCE48D0099B055;
productRefGroup = 50617D8023FCE48E0099B055 /* Products */; productRefGroup = 50617D8023FCE48E0099B055 /* Products */;
@ -612,14 +611,13 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = ""; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks";
SDKROOT = macosx; SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO; STRIP_INSTALLED_PRODUCT = NO;
STRIP_SWIFT_SYMBOLS = NO; STRIP_SWIFT_SYMBOLS = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = complete;
}; };
name = Debug; name = Debug;
}; };
@ -672,14 +670,13 @@
MACOSX_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = ""; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks";
SDKROOT = macosx; SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO; STRIP_INSTALLED_PRODUCT = NO;
STRIP_SWIFT_SYMBOLS = NO; STRIP_SWIFT_SYMBOLS = NO;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_STRICT_CONCURRENCY = complete;
}; };
name = Release; name = Release;
}; };
@ -707,6 +704,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
@ -735,6 +733,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host"; PROVISIONING_PROFILE_SPECIFIER = "Secretive - Host";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
@ -837,14 +836,13 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = ""; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks";
SDKROOT = macosx; SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO; STRIP_INSTALLED_PRODUCT = NO;
STRIP_SWIFT_SYMBOLS = NO; STRIP_SWIFT_SYMBOLS = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = complete;
}; };
name = Test; name = Test;
}; };
@ -868,6 +866,7 @@
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.Host;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Test; name = Test;
@ -912,6 +911,7 @@
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Test; name = Test;
@ -936,6 +936,7 @@
MARKETING_VERSION = 1; MARKETING_VERSION = 1;
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
@ -962,6 +963,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent; PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.SecretAgent;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent"; PROVISIONING_PROFILE_SPECIFIER = "Secretive - Secret Agent";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;

View File

@ -15,12 +15,6 @@
"value" : "Agent Is Not Running" "value" : "Agent Is Not Running"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Agentti ei ole käynnissä"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -61,28 +55,22 @@
"value" : "SecretAgent is a process that runs in the background to sign requests, so you don't need to keep Secretive open all the time.\n\n**You can close Secretive, and everything will still keep working.**" "value" : "SecretAgent is a process that runs in the background to sign requests, so you don't need to keep Secretive open all the time.\n\n**You can close Secretive, and everything will still keep working.**"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "SecretAgent on taustaprosessi joka allekirjoittaa pyyntöjä, joten Secretiveä ei tarvitse pitää käynnissä koko aikaa.**Voit sulkea Secretiven, ja kaikki toimii yhä.**"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "SecretAgent est un processus qui s'exécute en arrière-plan pour signer les demandes, de sorte que vous n'ayez pas besoin de garder Secretive ouvert en permanence.\n\n**Vous pouvez fermer Secretive, et tout continuera à fonctionner.**" "value" : "SecretAgent est un processus qui s'exécute en arrière-plan pour signer les demandes, de sorte que vous n'ayez pas besoin de garder Secretive ouvert en permanence.\n\n**Vous pouvez fermer Secretive, et tout continuera à fonctionner."
} }
}, },
"it" : { "it" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "SecretAgent è un processo che viene eseguito in background per firmare le richieste, così che non devi tenere aperto Secretive tutto il tempo.\n\n**Puoi chiudere Secretive, e continuerà a funzionare.**" "value" : "SecretAgent è un processo che viene eseguito in background per firmare le richieste, così che non devi tenere aperto Secretive tutto il tempo.\\n\\n**Puoi chiudere Secretive, e continuerà a funzionare.**"
} }
}, },
"pt-BR" : { "pt-BR" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
"value" : "SecretAgent é um processo que roda em background para assinar requisições para que você não precise manter o Secretive aberto a todo momento.\n\n**Você pode fechar o Secretive e tudo continuará funcionando.**" "value" : "SecretAgent é um processo que roda em background para assinar requisições para que você não precise manter o Secretive aberto a todo momento.\n\n**Você pode fechar o Secretive e tudo continuará funcionando."
} }
}, },
"zh-Hans" : { "zh-Hans" : {
@ -107,12 +95,6 @@
"value" : "Secret Agent is Running" "value" : "Secret Agent is Running"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "SecretAgent on käynnissä"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -153,12 +135,6 @@
"value" : "Agent is Running" "value" : "Agent is Running"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Agentti on käynnissä"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -199,12 +175,6 @@
"value" : "Setup Secretive" "value" : "Setup Secretive"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Asenna Secretive"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -245,12 +215,6 @@
"value" : "Help" "value" : "Help"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Apua"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -291,12 +255,6 @@
"value" : "New Secret" "value" : "New Secret"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Uusi salaisuus"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -337,12 +295,6 @@
"value" : "Setup Secretive" "value" : "Setup Secretive"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Asenna Secretive"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -383,12 +335,6 @@
"value" : "Secretive needs to be in your Applications folder to work properly. Please move it and relaunch." "value" : "Secretive needs to be in your Applications folder to work properly. Please move it and relaunch."
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Secretiven tulee olla Ohjelmat-kansiossa toimiakseen oikein. Ole hyvä ja käynnistä se uudelleen siirrettyäsi sen."
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -429,12 +375,6 @@
"value" : "Secretive Is Not in Applications Folder" "value" : "Secretive Is Not in Applications Folder"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Secretive ei ole Ohjelmat-kansiossa"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -477,12 +417,6 @@
"value" : "unlock secret \"%1$@\" for %2$@" "value" : "unlock secret \"%1$@\" for %2$@"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "avaa salaisuuden \"%1$@\" lukitus ajaksi %2$@"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -525,12 +459,6 @@
"value" : "unlock secret \"%1$@\"" "value" : "unlock secret \"%1$@\""
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "avaa salaisuuden \"%1$@\" lukitus"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -573,12 +501,6 @@
"value" : "decrypt data using secret \"%1$@\"" "value" : "decrypt data using secret \"%1$@\""
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "pura salaus käyttäen salaisuutta \"%1$@\""
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -621,12 +543,6 @@
"value" : "Deny" "value" : "Deny"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kiellä"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -669,12 +585,6 @@
"value" : "encrypt data using secret \"%1$@\"" "value" : "encrypt data using secret \"%1$@\""
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "salaa käyttäen salaisuutta \"%1$@\""
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -717,12 +627,6 @@
"value" : "sign a request from \"%1$@\" using secret \"%2$@\"" "value" : "sign a request from \"%1$@\" using secret \"%2$@\""
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "allekirjoita pyyntö lähteestä \"%1$@\" käyttäen salaisuutta \"%2$@\""
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -765,12 +669,6 @@
"value" : "verify a signature using secret \"%1$@\"" "value" : "verify a signature using secret \"%1$@\""
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "varmista allekirjoitus käyttäen salaisuutta \"%1$@\""
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -811,12 +709,6 @@
"value" : "Click to Copy" "value" : "Click to Copy"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Klikkaa kopioidaksesi"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -857,12 +749,6 @@
"value" : "Copied" "value" : "Copied"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kopioitu"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -903,12 +789,6 @@
"value" : "Cancel" "value" : "Cancel"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Peru"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -949,12 +829,6 @@
"value" : "Create" "value" : "Create"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Luo"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -995,12 +869,6 @@
"value" : "Name:" "value" : "Name:"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nimi:"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1041,12 +909,6 @@
"value" : "Shhhhh" "value" : "Shhhhh"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hysssss"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1087,12 +949,6 @@
"value" : "No authentication is required while your Mac is unlocked, but you will be notified when a secret is used." "value" : "No authentication is required while your Mac is unlocked, but you will be notified when a secret is used."
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Todennusta ei vaadita Macisi ollessa lukitsematta, mutta saat ilmoituksen kun salaisuutta käytetään."
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1133,12 +989,6 @@
"value" : "Notify" "value" : "Notify"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ilmoita"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1179,12 +1029,6 @@
"value" : "You will be required to authenticate using Touch ID, Apple Watch, or password before each use." "value" : "You will be required to authenticate using Touch ID, Apple Watch, or password before each use."
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Touch ID-, Apple Watch- tai salasanatodennus vaaditaan joka käytöllä."
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1225,12 +1069,6 @@
"value" : "Require Authentication" "value" : "Require Authentication"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vaadi todennus"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1271,12 +1109,6 @@
"value" : "Create a New Secret" "value" : "Create a New Secret"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Luo uusi salaisuus"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1317,12 +1149,6 @@
"value" : "Don't Delete" "value" : "Don't Delete"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Älä poista"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1363,12 +1189,6 @@
"value" : "Confirm Name:" "value" : "Confirm Name:"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vahvista nimi:"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1409,12 +1229,6 @@
"value" : "Delete" "value" : "Delete"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Poista"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1455,12 +1269,6 @@
"value" : "If you delete %1$@, you will not be able to recover it. Type \"%2$@\" to confirm." "value" : "If you delete %1$@, you will not be able to recover it. Type \"%2$@\" to confirm."
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Jos poistat kohteen %1$@, sitä ei pysty palauttamaan. Kirjoita \"%2$@\" vahvistaaksesi poiston."
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1501,12 +1309,6 @@
"value" : "Delete %1$@?" "value" : "Delete %1$@?"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Poista %1$@?"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1547,12 +1349,6 @@
"value" : "Create a new one by clicking here." "value" : "Create a new one by clicking here."
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Luo uusi klikkaamalla tästä."
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1593,12 +1389,6 @@
"value" : "No Secrets" "value" : "No Secrets"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ei salaisuuksia"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1639,12 +1429,6 @@
"value" : "No Secrets" "value" : "No Secrets"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ei salaisuuksia"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1685,12 +1469,6 @@
"value" : "Use your Smart Card's management tool to create a secret." "value" : "Use your Smart Card's management tool to create a secret."
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Käytä älykorttisi hallintatyökalua luodaksesi salaisuuden."
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1731,12 +1509,6 @@
"value" : "Secretive supports EC256, EC384, RSA1024, and RSA2048 keys." "value" : "Secretive supports EC256, EC384, RSA1024, and RSA2048 keys."
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Secretive tukee EC256-, EC384-, RSA1024- ja RSA2048-avaimia."
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1777,12 +1549,6 @@
"value" : "No Secrets" "value" : "No Secrets"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ei salaisuuksia"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1823,12 +1589,6 @@
"value" : "Your Mac doesn't have a Secure Enclave, and there's not a compatible Smart Card inserted." "value" : "Your Mac doesn't have a Secure Enclave, and there's not a compatible Smart Card inserted."
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Macissasi ei ole Secure Enclavea, eikä tuettu älykortti ole syötettynä."
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1869,12 +1629,6 @@
"value" : "No Secure Storage Available" "value" : "No Secure Storage Available"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Turvasäilö ei saatavilla"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1915,12 +1669,6 @@
"value" : "If you're looking to add one to your Mac, the YubiKey 5 Series are great." "value" : "If you're looking to add one to your Mac, the YubiKey 5 Series are great."
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Jos haluat lisätä Maciisi sellaisen, YubiKey 5 -sarja on mainio."
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -1963,12 +1711,6 @@
"value" : "Leave Unlocked" "value" : "Leave Unlocked"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Jätä lukitsematta"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",
@ -2011,12 +1753,6 @@
"value" : "Do Not Unlock" "value" : "Do Not Unlock"
} }
}, },
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Älä avaa lukitusta"
}
},
"fr" : { "fr" : {
"stringUnit" : { "stringUnit" : {
"state" : "translated", "state" : "translated",