From eabcbc953004704737b56a01c466ac275736485e Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Sun, 1 Dec 2024 16:44:48 +0100 Subject: [PATCH 1/5] Add Touch ID provider --- src/main/java/module-info.java | 3 +- .../keychain/MacSystemKeychainAccess.java | 5 ++ .../macos/keychain/TouchIdKeychainAccess.java | 75 +++++++++++++++++++ ...ons.keychain.TouchIdKeychainAccessProvider | 1 + .../MacIntegrationsBundle.properties | 3 +- 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java create mode 100644 src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.TouchIdKeychainAccessProvider diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 4df03f3..889f37f 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -5,6 +5,7 @@ import org.cryptomator.integrations.uiappearance.UiAppearanceProvider; import org.cryptomator.macos.autostart.MacAutoStartProvider; import org.cryptomator.macos.keychain.MacSystemKeychainAccess; +import org.cryptomator.macos.keychain.TouchIdKeychainAccess; import org.cryptomator.macos.revealpath.OpenCmdRevealPathService; import org.cryptomator.macos.tray.MacTrayIntegrationProvider; import org.cryptomator.macos.uiappearance.MacUiAppearanceProvider; @@ -14,7 +15,7 @@ requires org.slf4j; provides AutoStartProvider with MacAutoStartProvider; - provides KeychainAccessProvider with MacSystemKeychainAccess; + provides KeychainAccessProvider with MacSystemKeychainAccess, TouchIdKeychainAccess; provides RevealPathService with OpenCmdRevealPathService; provides TrayIntegrationProvider with MacTrayIntegrationProvider; provides UiAppearanceProvider with MacUiAppearanceProvider; diff --git a/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java b/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java index 201d7fe..e752a35 100644 --- a/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java +++ b/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java @@ -34,6 +34,11 @@ public String displayName() { return Localization.get().getString("org.cryptomator.macos.keychain.displayName"); } + @Override + public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { + keychain.storePassword(SERVICE_NAME, key, passphrase, false); + } + @Override public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean requireOsAuthentication) throws KeychainAccessException { keychain.storePassword(SERVICE_NAME, key, passphrase, requireOsAuthentication); diff --git a/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java b/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java new file mode 100644 index 0000000..da7f5ce --- /dev/null +++ b/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java @@ -0,0 +1,75 @@ +package org.cryptomator.macos.keychain; + +import org.cryptomator.integrations.common.OperatingSystem; +import org.cryptomator.integrations.common.Priority; +import org.cryptomator.integrations.keychain.KeychainAccessException; +import org.cryptomator.integrations.keychain.KeychainAccessProvider; +import org.cryptomator.macos.common.Localization; + +/** + * Stores passwords in the macOS system keychain. Requires an authenticated user to do so. + * Authentication is done via TouchID or password as a fallback, when TouchID is not available. + *

+ * Items are stored in the default keychain with the service name Cryptomator, unless configured otherwise + * using the system property cryptomator.integrationsMac.keychainServiceName. + */ +@Priority(1000) +@OperatingSystem(OperatingSystem.Value.MAC) +public class TouchIdKeychainAccess implements KeychainAccessProvider { + + private static final String SERVICE_NAME = System.getProperty("cryptomator.integrationsMac.keychainServiceName", "Cryptomator"); + + private final MacKeychain keychain; + + public TouchIdKeychainAccess() { + this(new MacKeychain()); + } + + // visible for testing + TouchIdKeychainAccess(MacKeychain keychain) { + this.keychain = keychain; + } + + @Override + public String displayName() { + return Localization.get().getString("org.cryptomator.macos.keychain.touchIdDisplayName"); + } + + @Override + public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { + keychain.storePassword(SERVICE_NAME, key, passphrase, true); + } + + @Override + public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean requireOsAuthentication) throws KeychainAccessException { + keychain.storePassword(SERVICE_NAME, key, passphrase, requireOsAuthentication); + } + + @Override + public char[] loadPassphrase(String key) { + return keychain.loadPassword(SERVICE_NAME, key); + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public boolean isLocked() { + return false; + } + + @Override + public void deletePassphrase(String key) throws KeychainAccessException { + keychain.deletePassword(SERVICE_NAME, key); + } + + @Override + public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException { + if (keychain.deletePassword(SERVICE_NAME, key)) { + keychain.storePassword(SERVICE_NAME, key, passphrase, true); + } + } + +} diff --git a/src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.TouchIdKeychainAccessProvider b/src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.TouchIdKeychainAccessProvider new file mode 100644 index 0000000..b4ede7f --- /dev/null +++ b/src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.TouchIdKeychainAccessProvider @@ -0,0 +1 @@ +org.cryptomator.macos.keychain.TouchIdKeychainAccess \ No newline at end of file diff --git a/src/main/resources/MacIntegrationsBundle.properties b/src/main/resources/MacIntegrationsBundle.properties index bc2eb19..7b3099c 100644 --- a/src/main/resources/MacIntegrationsBundle.properties +++ b/src/main/resources/MacIntegrationsBundle.properties @@ -1 +1,2 @@ -org.cryptomator.macos.keychain.displayName=macOS Keychain \ No newline at end of file +org.cryptomator.macos.keychain.displayName=macOS Keychain +org.cryptomator.macos.keychain.touchIdDisplayName=Touch ID \ No newline at end of file From f54f55060fccb2e1f8e8d9b6d3fd17a848142977 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Mon, 2 Dec 2024 13:52:11 +0100 Subject: [PATCH 2/5] One provider configuration file is enough --- ...rg.cryptomator.integrations.keychain.KeychainAccessProvider | 3 ++- ...tomator.integrations.keychain.TouchIdKeychainAccessProvider | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.TouchIdKeychainAccessProvider diff --git a/src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider b/src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider index 6af6c14..c1a5aa9 100644 --- a/src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider +++ b/src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.KeychainAccessProvider @@ -1 +1,2 @@ -org.cryptomator.macos.keychain.MacSystemKeychainAccess \ No newline at end of file +org.cryptomator.macos.keychain.MacSystemKeychainAccess +org.cryptomator.macos.keychain.TouchIdKeychainAccess \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.TouchIdKeychainAccessProvider b/src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.TouchIdKeychainAccessProvider deleted file mode 100644 index b4ede7f..0000000 --- a/src/main/resources/META-INF/services/org.cryptomator.integrations.keychain.TouchIdKeychainAccessProvider +++ /dev/null @@ -1 +0,0 @@ -org.cryptomator.macos.keychain.TouchIdKeychainAccess \ No newline at end of file From c9092658994e4231fc9ce521579e5eab0f2b7d96 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Thu, 12 Dec 2024 07:26:44 +0100 Subject: [PATCH 3/5] Higher Touch ID priority --- .../org/cryptomator/macos/keychain/TouchIdKeychainAccess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java b/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java index da7f5ce..d5537fe 100644 --- a/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java +++ b/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java @@ -13,7 +13,7 @@ * Items are stored in the default keychain with the service name Cryptomator, unless configured otherwise * using the system property cryptomator.integrationsMac.keychainServiceName. */ -@Priority(1000) +@Priority(1010) @OperatingSystem(OperatingSystem.Value.MAC) public class TouchIdKeychainAccess implements KeychainAccessProvider { From b1cdd160acbed7d3bb0967d4b603ee17090f5641 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Thu, 12 Dec 2024 08:12:50 +0100 Subject: [PATCH 4/5] Add check, whether Touch ID is available on the device --- ...rg_cryptomator_macos_keychain_MacKeychain_Native.h | 8 ++++++++ .../org/cryptomator/macos/keychain/MacKeychain.java | 11 +++++++++++ .../macos/keychain/TouchIdKeychainAccess.java | 2 +- ...rg_cryptomator_macos_keychain_MacKeychain_Native.m | 11 +++++++++++ .../macos/keychain/KeychainAccessProviderTest.java | 2 +- 5 files changed, 32 insertions(+), 2 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 7864238..177ca4e 100644 --- a/src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h +++ b/src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h @@ -31,6 +31,14 @@ JNIEXPORT jbyteArray JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_000 JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_deletePassword (JNIEnv *, jobject, jbyteArray, jbyteArray); +/* + * Class: org_cryptomator_macos_keychain_MacKeychain_Native + * Method: isTouchIDavailable + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_isTouchIDavailable + (JNIEnv *, jobject); + #ifdef __cplusplus } #endif diff --git a/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java b/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java index 49cf008..ce9a63f 100644 --- a/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java +++ b/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java @@ -104,6 +104,15 @@ public boolean deletePassword(String serviceName, String account) throws Keychai } } + /** + * Tests whether biometric authentication via Touch ID is supported and allowed on the device + * + * @return true if biometric authentication is available, false otherwise + */ + public boolean isTouchIDavailable() { + return Native.INSTANCE.isTouchIDavailable(); + } + // initialization-on-demand pattern, as loading the .dylib is an expensive operation private static class Native { static final Native INSTANCE = new Native(); @@ -117,6 +126,8 @@ private Native() { public native byte[] loadPassword(byte[] service, byte[] account); public native int deletePassword(byte[] service, byte[] account); + + public native boolean isTouchIDavailable(); } } diff --git a/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java b/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java index d5537fe..650193e 100644 --- a/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java +++ b/src/main/java/org/cryptomator/macos/keychain/TouchIdKeychainAccess.java @@ -52,7 +52,7 @@ public char[] loadPassphrase(String key) { @Override public boolean isSupported() { - return true; + return keychain.isTouchIDavailable(); } @Override 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 24d1a5e..1d63acc 100644 --- a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m +++ b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m @@ -123,3 +123,14 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati (*env)->ReleaseByteArrayElements(env, key, keyStr, JNI_ABORT); return status; } + +JNIEXPORT jboolean JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_isTouchIDavailable(JNIEnv *env, jobject thisObj) { + NSError *error = nil; + LAContext *context = getSharedLAContext(); + if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) { + return JNI_TRUE; + } else { + NSLog(@"Touch ID is not available: %@", error.localizedDescription); + return JNI_FALSE; + } +} diff --git a/src/test/java/org/cryptomator/macos/keychain/KeychainAccessProviderTest.java b/src/test/java/org/cryptomator/macos/keychain/KeychainAccessProviderTest.java index 9b32693..19c0ad2 100644 --- a/src/test/java/org/cryptomator/macos/keychain/KeychainAccessProviderTest.java +++ b/src/test/java/org/cryptomator/macos/keychain/KeychainAccessProviderTest.java @@ -12,7 +12,7 @@ public class KeychainAccessProviderTest { public void testLoadMacSystemKeychainAccess() { var provider = KeychainAccessProvider.get().findAny(); Assertions.assertTrue(provider.isPresent()); - Assertions.assertInstanceOf(MacSystemKeychainAccess.class, provider.get()); + Assertions.assertInstanceOf(TouchIdKeychainAccess.class, provider.get()); } } From f88d9d5ff3a97a684a6488a5f79d41c5341d3f3d Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Thu, 12 Dec 2024 10:49:34 +0100 Subject: [PATCH 5/5] Both KeychainAccessProviders need to be possible --- .../macos/keychain/KeychainAccessProviderTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/cryptomator/macos/keychain/KeychainAccessProviderTest.java b/src/test/java/org/cryptomator/macos/keychain/KeychainAccessProviderTest.java index 19c0ad2..6d8d6b6 100644 --- a/src/test/java/org/cryptomator/macos/keychain/KeychainAccessProviderTest.java +++ b/src/test/java/org/cryptomator/macos/keychain/KeychainAccessProviderTest.java @@ -12,7 +12,9 @@ public class KeychainAccessProviderTest { public void testLoadMacSystemKeychainAccess() { var provider = KeychainAccessProvider.get().findAny(); Assertions.assertTrue(provider.isPresent()); - Assertions.assertInstanceOf(TouchIdKeychainAccess.class, provider.get()); + Assertions.assertTrue( + provider.get() instanceof TouchIdKeychainAccess + || provider.get() instanceof MacSystemKeychainAccess); } }