From 148356d9852b1929f6b2e78a050ef53303bb6ced Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Sun, 28 Jul 2024 17:46:36 +0200 Subject: [PATCH 1/8] Add access keychain as an authenticated user --- pom.xml | 2 +- ...omator_macos_keychain_MacKeychain_Native.h | 8 +++ .../macos/keychain/MacKeychain.java | 14 +++++ .../keychain/MacSystemKeychainAccess.java | 5 ++ ...omator_macos_keychain_MacKeychain_Native.m | 56 +++++++++++++++++++ 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 065d806..37ba49f 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 17 - 1.3.1 + 1.4.0-SNAPSHOT 2.0.13 diff --git a/src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h b/src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h index 6718362..27ad952 100644 --- a/src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h +++ b/src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h @@ -15,6 +15,14 @@ extern "C" { JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePassword (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray); +/* + * Class: org_cryptomator_macos_keychain_MacKeychain_Native + * Method: storePasswordForAuthenticatedUser + * Signature: ([B[B[B)I + */ +JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePasswordForAuthenticatedUser + (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray); + /* * Class: org_cryptomator_macos_keychain_MacKeychain_Native * Method: loadPassword diff --git a/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java b/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java index 12422bb..ad3d984 100644 --- a/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java +++ b/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java @@ -34,6 +34,18 @@ public void storePassword(String serviceName, String account, CharSequence passw } } + public void storePassphraseForAuthenticatedUser(String serviceName, String account, CharSequence password) throws KeychainAccessException { + ByteBuffer pwBuf = UTF_8.encode(CharBuffer.wrap(password)); + byte[] pwBytes = new byte[pwBuf.remaining()]; + pwBuf.get(pwBytes); + int errorCode = Native.INSTANCE.storePasswordForAuthenticatedUser(serviceName.getBytes(UTF_8), account.getBytes(UTF_8), pwBytes); + Arrays.fill(pwBytes, (byte) 0x00); + Arrays.fill(pwBuf.array(), (byte) 0x00); + if (errorCode != OSSTATUS_SUCCESS) { + throw new KeychainAccessException("Failed to store password. Error code " + errorCode); + } + } + /** * Loads the password associated with the specified key from the system keychain. * @@ -113,6 +125,8 @@ private Native() { public native int storePassword(byte[] service, byte[] account, byte[] value); + public native int storePasswordForAuthenticatedUser(byte[] service, byte[] account, byte[] value); + public native byte[] loadPassword(byte[] service, byte[] account); public native int deletePassword(byte[] service, byte[] account); diff --git a/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java b/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java index c2f676e..7a9af18 100644 --- a/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java +++ b/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java @@ -39,6 +39,11 @@ public void storePassphrase(String key, String displayName, CharSequence passphr keychain.storePassword(SERVICE_NAME, key, passphrase); } + @Override + public void storePassphraseForAuthenticatedUser(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { + keychain.storePassphraseForAuthenticatedUser(SERVICE_NAME, key, passphrase); + } + @Override public char[] loadPassphrase(String key) { return keychain.loadPassword(SERVICE_NAME, key); diff --git a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m index 5309050..eeadd79 100644 --- a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m +++ b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m @@ -10,6 +10,19 @@ #import #import +SecAccessControlRef createAccessControl(void) { + SecAccessControlCreateFlags flags = kSecAccessControlUserPresence; + + SecAccessControlRef accessControl = SecAccessControlCreateWithFlags( + kCFAllocatorDefault, + kSecAttrAccessibleWhenUnlocked, + flags, + NULL // Ignore any error + ); + + return accessControl; +} + JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePassword(JNIEnv *env, jobject thisObj, jbyteArray service, jbyteArray key, jbyteArray password) { jbyte *serviceStr = (*env)->GetByteArrayElements(env, service, NULL); jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL); @@ -52,6 +65,49 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati return status; } +JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePasswordForAuthenticatedUser(JNIEnv *env, jobject thisObj, jbyteArray service, jbyteArray key, jbyteArray password) { + jbyte *serviceStr = (*env)->GetByteArrayElements(env, service, NULL); + jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL); + jbyte *pwStr = (*env)->GetByteArrayElements(env, password, NULL); + jsize length = (*env)->GetArrayLength(env, password); + + // find existing: + NSDictionary *query = @{ + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecReturnAttributes: @YES, + (__bridge id)kSecReturnData: @YES, + (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne + }; + CFDictionaryRef result = NULL; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); + if (status == errSecSuccess && result != NULL) { + // update existing: + NSDictionary *attributesToUpdate = @{ + (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] + }; + status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributesToUpdate); + } else if (status == errSecItemNotFound) { + // add new: + NSDictionary *attributes = @{ + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)createAccessControl(), + (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] + }; + status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL); + } else { + NSLog(@"Error storing item in keychain. Status code: %d", (int)status); + } + + (*env)->ReleaseByteArrayElements(env, service, serviceStr, JNI_ABORT); + (*env)->ReleaseByteArrayElements(env, key, keyStr, JNI_ABORT); + (*env)->ReleaseByteArrayElements(env, password, pwStr, JNI_ABORT); + return status; +} + JNIEXPORT jbyteArray JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_loadPassword(JNIEnv *env, jobject thisObj, jbyteArray service, jbyteArray key) { jbyte *serviceStr = (*env)->GetByteArrayElements(env, service, NULL); jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL); From 9befe7f732d85fa636cf7bb60b9ea5fc72125378 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Sat, 3 Aug 2024 17:26:54 +0200 Subject: [PATCH 2/8] Manage amount of touch id prompts --- ...omator_macos_keychain_MacKeychain_Native.m | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m index eeadd79..2236823 100644 --- a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m +++ b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m @@ -9,6 +9,17 @@ #import "org_cryptomator_macos_keychain_MacKeychain_Native.h" #import #import +#import + +static LAContext *sharedContext = nil; + +LAContext* getSharedLAContext(void) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedContext = [[LAContext alloc] init]; + }); + return sharedContext; +} SecAccessControlRef createAccessControl(void) { SecAccessControlCreateFlags flags = kSecAccessControlUserPresence; @@ -71,6 +82,8 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati jbyte *pwStr = (*env)->GetByteArrayElements(env, password, NULL); jsize length = (*env)->GetArrayLength(env, password); + LAContext *context = getSharedLAContext(); + // find existing: NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, @@ -78,7 +91,8 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], (__bridge id)kSecReturnAttributes: @YES, (__bridge id)kSecReturnData: @YES, - (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne + (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne, + (__bridge id)kSecUseAuthenticationContext: context }; CFDictionaryRef result = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); @@ -112,6 +126,8 @@ JNIEXPORT jbyteArray JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_000 jbyte *serviceStr = (*env)->GetByteArrayElements(env, service, NULL); jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL); + LAContext *context = getSharedLAContext(); + // find existing: NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, @@ -119,7 +135,8 @@ JNIEXPORT jbyteArray JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_000 (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], (__bridge id)kSecReturnAttributes: @YES, (__bridge id)kSecReturnData: @YES, - (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne + (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne, + (__bridge id)kSecUseAuthenticationContext: context }; CFDictionaryRef result = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); @@ -143,12 +160,14 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati jbyte *serviceStr = (*env)->GetByteArrayElements(env, service, NULL); jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL); + LAContext *context = getSharedLAContext(); + // find existing: NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne + (__bridge id)kSecUseAuthenticationContext: context }; CFDictionaryRef result = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); From 84c93b65a9e7f0bce8a0cc2f77cfc31265faec56 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Sat, 3 Aug 2024 18:36:19 +0200 Subject: [PATCH 3/8] Fix delete item from keychain --- .../org_cryptomator_macos_keychain_MacKeychain_Native.m | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m index 2236823..69f9305 100644 --- a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m +++ b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m @@ -169,12 +169,8 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], (__bridge id)kSecUseAuthenticationContext: context }; - CFDictionaryRef result = NULL; - OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); - if (status == errSecSuccess && result != NULL) { - // delete: - status = SecItemDelete((__bridge CFDictionaryRef)query); - } else if (status != errSecItemNotFound) { + OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); + if (status != errSecSuccess) { NSLog(@"Error deleting item from keychain. Status code: %d", (int)status); } From 6ea3ddc8dc2a0d16835b5595b349bfadeee0ba5c Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Wed, 14 Aug 2024 10:50:34 +0200 Subject: [PATCH 4/8] Use overloaded method storePassword instead of a new one --- ...omator_macos_keychain_MacKeychain_Native.h | 12 +-- .../macos/keychain/MacKeychain.java | 29 ++---- .../keychain/MacSystemKeychainAccess.java | 11 +-- ...omator_macos_keychain_MacKeychain_Native.m | 98 ++++++++----------- .../macos/keychain/MacKeychainTest.java | 2 +- .../keychain/MacSystemKeychainAccessTest.java | 10 +- 6 files changed, 59 insertions(+), 103 deletions(-) diff --git a/src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h b/src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h index 27ad952..7864238 100644 --- a/src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h +++ b/src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h @@ -10,18 +10,10 @@ extern "C" { /* * Class: org_cryptomator_macos_keychain_MacKeychain_Native * Method: storePassword - * Signature: ([B[B[B)I + * Signature: ([B[B[BZ)I */ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePassword - (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray); - -/* - * Class: org_cryptomator_macos_keychain_MacKeychain_Native - * Method: storePasswordForAuthenticatedUser - * Signature: ([B[B[B)I - */ -JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePasswordForAuthenticatedUser - (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray); + (JNIEnv *, jobject, jbyteArray, jbyteArray, jbyteArray, jboolean); /* * Class: org_cryptomator_macos_keychain_MacKeychain_Native diff --git a/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java b/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java index ad3d984..eef8a50 100644 --- a/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java +++ b/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java @@ -17,28 +17,17 @@ class MacKeychain { /** * Associates the specified password with the specified key in the system keychain. * - * @param serviceName Service name - * @param account Unique account identifier - * @param password Passphrase to store + * @param serviceName Service name + * @param account Unique account identifier + * @param password Passphrase to store + * @param requireOsAuthentication Defines, whether the user needs to authenticate to store a passphrase * @see SecKeychainAddGenericPassword */ - public void storePassword(String serviceName, String account, CharSequence password) throws KeychainAccessException { + public void storePassword(String serviceName, String account, CharSequence password, boolean requireOsAuthentication) throws KeychainAccessException { ByteBuffer pwBuf = UTF_8.encode(CharBuffer.wrap(password)); byte[] pwBytes = new byte[pwBuf.remaining()]; pwBuf.get(pwBytes); - int errorCode = Native.INSTANCE.storePassword(serviceName.getBytes(UTF_8), account.getBytes(UTF_8), pwBytes); - Arrays.fill(pwBytes, (byte) 0x00); - Arrays.fill(pwBuf.array(), (byte) 0x00); - if (errorCode != OSSTATUS_SUCCESS) { - throw new KeychainAccessException("Failed to store password. Error code " + errorCode); - } - } - - public void storePassphraseForAuthenticatedUser(String serviceName, String account, CharSequence password) throws KeychainAccessException { - ByteBuffer pwBuf = UTF_8.encode(CharBuffer.wrap(password)); - byte[] pwBytes = new byte[pwBuf.remaining()]; - pwBuf.get(pwBytes); - int errorCode = Native.INSTANCE.storePasswordForAuthenticatedUser(serviceName.getBytes(UTF_8), account.getBytes(UTF_8), pwBytes); + int errorCode = Native.INSTANCE.storePassword(serviceName.getBytes(UTF_8), account.getBytes(UTF_8), pwBytes, requireOsAuthentication); Arrays.fill(pwBytes, (byte) 0x00); Arrays.fill(pwBuf.array(), (byte) 0x00); if (errorCode != OSSTATUS_SUCCESS) { @@ -87,7 +76,7 @@ private boolean tryMigratePassword(String account) { if (pwBytes == null) { return false; } - int errorCode = Native.INSTANCE.storePassword(newServiceName, account.getBytes(UTF_8), pwBytes); + int errorCode = Native.INSTANCE.storePassword(newServiceName, account.getBytes(UTF_8), pwBytes, false); Arrays.fill(pwBytes, (byte) 0x00); if (errorCode != OSSTATUS_SUCCESS) { return false; @@ -123,9 +112,7 @@ private Native() { NativeLibLoader.loadLib(); } - public native int storePassword(byte[] service, byte[] account, byte[] value); - - public native int storePasswordForAuthenticatedUser(byte[] service, byte[] account, byte[] value); + public native int storePassword(byte[] service, byte[] account, byte[] value, boolean requireOsAuthentication); public native byte[] loadPassword(byte[] service, byte[] account); diff --git a/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java b/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java index 7a9af18..201d7fe 100644 --- a/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java +++ b/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java @@ -35,13 +35,8 @@ public String displayName() { } @Override - public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { - keychain.storePassword(SERVICE_NAME, key, passphrase); - } - - @Override - public void storePassphraseForAuthenticatedUser(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { - keychain.storePassphraseForAuthenticatedUser(SERVICE_NAME, key, passphrase); + public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean requireOsAuthentication) throws KeychainAccessException { + keychain.storePassword(SERVICE_NAME, key, passphrase, requireOsAuthentication); } @Override @@ -67,7 +62,7 @@ public void deletePassphrase(String key) throws KeychainAccessException { @Override public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { if (keychain.deletePassword(SERVICE_NAME, key)) { - keychain.storePassword(SERVICE_NAME, key, passphrase); + keychain.storePassword(SERVICE_NAME, key, passphrase, false); } } diff --git a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m index 69f9305..1581f7e 100644 --- a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m +++ b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m @@ -34,66 +34,38 @@ SecAccessControlRef createAccessControl(void) { return accessControl; } -JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePassword(JNIEnv *env, jobject thisObj, jbyteArray service, jbyteArray key, jbyteArray password) { +JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePassword(JNIEnv *env, jobject thisObj, jbyteArray service, jbyteArray key, jbyteArray password, jboolean requireOsAuthentication) { jbyte *serviceStr = (*env)->GetByteArrayElements(env, service, NULL); jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL); jbyte *pwStr = (*env)->GetByteArrayElements(env, password, NULL); jsize length = (*env)->GetArrayLength(env, password); // find existing: - NSDictionary *query = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecReturnAttributes: @YES, - (__bridge id)kSecReturnData: @YES, - (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne - }; - CFDictionaryRef result = NULL; - OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); - if (status == errSecSuccess && result != NULL) { - // update existing: - NSDictionary *attributesToUpdate = @{ - (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] - }; - status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributesToUpdate); - } else if (status == errSecItemNotFound) { - // add new: - NSDictionary *attributes = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] - }; - status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL); + NSDictionary *query = NULL; + + if (requireOsAuthentication) { + LAContext *context = getSharedLAContext(); + + query = @{ + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecReturnAttributes: @YES, + (__bridge id)kSecReturnData: @YES, + (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne, + (__bridge id)kSecUseAuthenticationContext: context + }; } else { - NSLog(@"Error storing item in keychain. Status code: %d", (int)status); + query = @{ + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecReturnAttributes: @YES, + (__bridge id)kSecReturnData: @YES, + (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne + }; } - (*env)->ReleaseByteArrayElements(env, service, serviceStr, JNI_ABORT); - (*env)->ReleaseByteArrayElements(env, key, keyStr, JNI_ABORT); - (*env)->ReleaseByteArrayElements(env, password, pwStr, JNI_ABORT); - return status; -} - -JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePasswordForAuthenticatedUser(JNIEnv *env, jobject thisObj, jbyteArray service, jbyteArray key, jbyteArray password) { - jbyte *serviceStr = (*env)->GetByteArrayElements(env, service, NULL); - jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL); - jbyte *pwStr = (*env)->GetByteArrayElements(env, password, NULL); - jsize length = (*env)->GetArrayLength(env, password); - - LAContext *context = getSharedLAContext(); - - // find existing: - NSDictionary *query = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecReturnAttributes: @YES, - (__bridge id)kSecReturnData: @YES, - (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne, - (__bridge id)kSecUseAuthenticationContext: context - }; CFDictionaryRef result = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); if (status == errSecSuccess && result != NULL) { @@ -104,13 +76,23 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributesToUpdate); } else if (status == errSecItemNotFound) { // add new: - NSDictionary *attributes = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)createAccessControl(), - (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] - }; + NSDictionary *attributes = NULL; + if (requireOsAuthentication) { + attributes = @{ + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)createAccessControl(), + (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] + }; + } else { + attributes = @{ + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] + }; + } status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL); } else { NSLog(@"Error storing item in keychain. Status code: %d", (int)status); diff --git a/src/test/java/org/cryptomator/macos/keychain/MacKeychainTest.java b/src/test/java/org/cryptomator/macos/keychain/MacKeychainTest.java index 8ec41ac..da6daee 100644 --- a/src/test/java/org/cryptomator/macos/keychain/MacKeychainTest.java +++ b/src/test/java/org/cryptomator/macos/keychain/MacKeychainTest.java @@ -17,7 +17,7 @@ public class WithStoredPassword { @BeforeEach public void setup() throws KeychainAccessException { - keychain.storePassword("service", "account", storedPw); + keychain.storePassword("service", "account", storedPw, false); } @Test diff --git a/src/test/java/org/cryptomator/macos/keychain/MacSystemKeychainAccessTest.java b/src/test/java/org/cryptomator/macos/keychain/MacSystemKeychainAccessTest.java index 58aeb36..a3a8642 100644 --- a/src/test/java/org/cryptomator/macos/keychain/MacSystemKeychainAccessTest.java +++ b/src/test/java/org/cryptomator/macos/keychain/MacSystemKeychainAccessTest.java @@ -31,17 +31,17 @@ public void testDisplayName() { public void testStoreSuccess() throws KeychainAccessException { keychainAccess.storePassphrase("key", "pass"); - Mockito.verify(keychain).storePassword("Cryptomator", "key", "pass"); + Mockito.verify(keychain).storePassword("Cryptomator", "key", "pass", false); } @Test @DisplayName("storePassphrase() fails") public void testStoreError() throws KeychainAccessException { KeychainAccessException e = new KeychainAccessException("fail."); - Mockito.doThrow(e).when(keychain).storePassword(Mockito.eq("Cryptomator"), Mockito.any(), Mockito.any()); + Mockito.doThrow(e).when(keychain).storePassword(Mockito.eq("Cryptomator"), Mockito.any(), Mockito.any(), Mockito.eq(false)); KeychainAccessException thrown = Assertions.assertThrows(KeychainAccessException.class, () -> { - keychainAccess.storePassphrase("key", "pass"); + keychainAccess.storePassphrase("key", "", "pass", false); }); Assertions.assertSame(thrown, e); } @@ -93,7 +93,7 @@ public void testChangeSuccess() throws KeychainAccessException { keychainAccess.changePassphrase("key", "newpass"); - Mockito.verify(keychain).storePassword("Cryptomator", "key", "newpass"); + Mockito.verify(keychain).storePassword("Cryptomator", "key", "newpass", false); } @Test @@ -103,7 +103,7 @@ public void testChangeNotFound() throws KeychainAccessException { keychainAccess.changePassphrase("key", "newpass"); - Mockito.verify(keychain, Mockito.never()).storePassword("Cryptomator", "key", "newpass"); + Mockito.verify(keychain, Mockito.never()).storePassword("Cryptomator", "key", "newpass", false); } @Test From 6d2456226bc9ac3170c0d66b930b32d5d1ab8002 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Tue, 20 Aug 2024 08:15:20 +0200 Subject: [PATCH 5/8] New, widened integrations-api --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 37ba49f..1109ffa 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 17 - 1.4.0-SNAPSHOT + 1.4.0-beta3 2.0.13 From a367d5fd71aeb46ee32cb894f8c76ed823f5419f Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 17 Sep 2024 15:35:07 +0200 Subject: [PATCH 6/8] reformatting --- .../macos/keychain/MacKeychain.java | 6 +-- ...omator_macos_keychain_MacKeychain_Native.m | 42 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java b/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java index eef8a50..49cf008 100644 --- a/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java +++ b/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java @@ -17,9 +17,9 @@ class MacKeychain { /** * Associates the specified password with the specified key in the system keychain. * - * @param serviceName Service name - * @param account Unique account identifier - * @param password Passphrase to store + * @param serviceName Service name + * @param account Unique account identifier + * @param password Passphrase to store * @param requireOsAuthentication Defines, whether the user needs to authenticate to store a passphrase * @see SecKeychainAddGenericPassword */ diff --git a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m index 1581f7e..7100d24 100644 --- a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m +++ b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m @@ -46,24 +46,24 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati if (requireOsAuthentication) { LAContext *context = getSharedLAContext(); - query = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecReturnAttributes: @YES, - (__bridge id)kSecReturnData: @YES, - (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne, - (__bridge id)kSecUseAuthenticationContext: context - }; + query = @{ + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecReturnAttributes: @YES, + (__bridge id)kSecReturnData: @YES, + (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne, + (__bridge id)kSecUseAuthenticationContext: context + }; } else { query = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecReturnAttributes: @YES, - (__bridge id)kSecReturnData: @YES, - (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne - }; + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecReturnAttributes: @YES, + (__bridge id)kSecReturnData: @YES, + (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne + }; } CFDictionaryRef result = NULL; @@ -87,11 +87,11 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati }; } else { attributes = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] - }; + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] + }; } status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL); } else { From 49e62f698151c283d6a6fd83d3d42446e42c491c Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Tue, 17 Sep 2024 15:54:31 +0200 Subject: [PATCH 7/8] refactorings --- ...omator_macos_keychain_MacKeychain_Native.m | 75 +++++-------------- 1 file changed, 19 insertions(+), 56 deletions(-) diff --git a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m index 7100d24..24d1a5e 100644 --- a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m +++ b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m @@ -12,8 +12,7 @@ #import static LAContext *sharedContext = nil; - -LAContext* getSharedLAContext(void) { +static LAContext* getSharedLAContext(void) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedContext = [[LAContext alloc] init]; @@ -21,19 +20,6 @@ return sharedContext; } -SecAccessControlRef createAccessControl(void) { - SecAccessControlCreateFlags flags = kSecAccessControlUserPresence; - - SecAccessControlRef accessControl = SecAccessControlCreateWithFlags( - kCFAllocatorDefault, - kSecAttrAccessibleWhenUnlocked, - flags, - NULL // Ignore any error - ); - - return accessControl; -} - JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePassword(JNIEnv *env, jobject thisObj, jbyteArray service, jbyteArray key, jbyteArray password, jboolean requireOsAuthentication) { jbyte *serviceStr = (*env)->GetByteArrayElements(env, service, NULL); jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL); @@ -41,31 +27,18 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati jsize length = (*env)->GetArrayLength(env, password); // find existing: - NSDictionary *query = NULL; - + NSMutableDictionary *query = [@{ + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecReturnAttributes: @YES, + (__bridge id)kSecReturnData: @YES, + (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne + } mutableCopy]; if (requireOsAuthentication) { LAContext *context = getSharedLAContext(); - - query = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecReturnAttributes: @YES, - (__bridge id)kSecReturnData: @YES, - (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne, - (__bridge id)kSecUseAuthenticationContext: context - }; - } else { - query = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecReturnAttributes: @YES, - (__bridge id)kSecReturnData: @YES, - (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne - }; + query[(__bridge id)kSecUseAuthenticationContext] = context; } - CFDictionaryRef result = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result); if (status == errSecSuccess && result != NULL) { @@ -76,22 +49,14 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributesToUpdate); } else if (status == errSecItemNotFound) { // add new: - NSDictionary *attributes = NULL; + NSMutableDictionary *attributes = [@{ + (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], + (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] + } mutableCopy]; if (requireOsAuthentication) { - attributes = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)createAccessControl(), - (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] - }; - } else { - attributes = @{ - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecAttrAccount: [NSString stringWithCString:(char *)keyStr encoding:NSUTF8StringEncoding], - (__bridge id)kSecValueData: [NSData dataWithBytes:pwStr length:length] - }; + attributes[(__bridge id)kSecAttrAccessControl] = (__bridge_transfer id)SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, kSecAccessControlUserPresence, NULL); } status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL); } else { @@ -108,9 +73,8 @@ JNIEXPORT jbyteArray JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_000 jbyte *serviceStr = (*env)->GetByteArrayElements(env, service, NULL); jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL); - LAContext *context = getSharedLAContext(); - // find existing: + LAContext *context = getSharedLAContext(); NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], @@ -142,9 +106,8 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati jbyte *serviceStr = (*env)->GetByteArrayElements(env, service, NULL); jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL); - LAContext *context = getSharedLAContext(); - // find existing: + LAContext *context = getSharedLAContext(); NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: [NSString stringWithCString:(char *)serviceStr encoding:NSUTF8StringEncoding], From 851965ab13083bd92b0d1388e21ba8d605d6d0c7 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Wed, 18 Sep 2024 15:00:18 +0200 Subject: [PATCH 8/8] integrations-api has been released meanwhile --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1109ffa..d042841 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 17 - 1.4.0-beta3 + 1.4.0 2.0.13