Skip to content

Commit 9d8fddb

Browse files
authored
Merge pull request #58 from purejava/new-touch-id-provider
Add Touch ID provider
2 parents 6cc701b + f88d9d5 commit 9d8fddb

File tree

9 files changed

+119
-4
lines changed

9 files changed

+119
-4
lines changed

src/main/headers/org_cryptomator_macos_keychain_MacKeychain_Native.h

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/java/module-info.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
66
import org.cryptomator.macos.autostart.MacAutoStartProvider;
77
import org.cryptomator.macos.keychain.MacSystemKeychainAccess;
8+
import org.cryptomator.macos.keychain.TouchIdKeychainAccess;
89
import org.cryptomator.macos.revealpath.OpenCmdRevealPathService;
910
import org.cryptomator.macos.tray.MacTrayIntegrationProvider;
1011
import org.cryptomator.macos.uiappearance.MacUiAppearanceProvider;
@@ -14,7 +15,7 @@
1415
requires org.slf4j;
1516

1617
provides AutoStartProvider with MacAutoStartProvider;
17-
provides KeychainAccessProvider with MacSystemKeychainAccess;
18+
provides KeychainAccessProvider with MacSystemKeychainAccess, TouchIdKeychainAccess;
1819
provides RevealPathService with OpenCmdRevealPathService;
1920
provides TrayIntegrationProvider with MacTrayIntegrationProvider;
2021
provides UiAppearanceProvider with MacUiAppearanceProvider;

src/main/java/org/cryptomator/macos/keychain/MacKeychain.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,15 @@ public boolean deletePassword(String serviceName, String account) throws Keychai
104104
}
105105
}
106106

107+
/**
108+
* Tests whether biometric authentication via Touch ID is supported and allowed on the device
109+
*
110+
* @return <code>true</code> if biometric authentication is available, <code>false</code> otherwise
111+
*/
112+
public boolean isTouchIDavailable() {
113+
return Native.INSTANCE.isTouchIDavailable();
114+
}
115+
107116
// initialization-on-demand pattern, as loading the .dylib is an expensive operation
108117
private static class Native {
109118
static final Native INSTANCE = new Native();
@@ -117,6 +126,8 @@ private Native() {
117126
public native byte[] loadPassword(byte[] service, byte[] account);
118127

119128
public native int deletePassword(byte[] service, byte[] account);
129+
130+
public native boolean isTouchIDavailable();
120131
}
121132

122133
}

src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ public String displayName() {
3434
return Localization.get().getString("org.cryptomator.macos.keychain.displayName");
3535
}
3636

37+
@Override
38+
public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
39+
keychain.storePassword(SERVICE_NAME, key, passphrase, false);
40+
}
41+
3742
@Override
3843
public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean requireOsAuthentication) throws KeychainAccessException {
3944
keychain.storePassword(SERVICE_NAME, key, passphrase, requireOsAuthentication);
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.cryptomator.macos.keychain;
2+
3+
import org.cryptomator.integrations.common.OperatingSystem;
4+
import org.cryptomator.integrations.common.Priority;
5+
import org.cryptomator.integrations.keychain.KeychainAccessException;
6+
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
7+
import org.cryptomator.macos.common.Localization;
8+
9+
/**
10+
* Stores passwords in the macOS system keychain. Requires an authenticated user to do so.
11+
* Authentication is done via TouchID or password as a fallback, when TouchID is not available.
12+
* <p>
13+
* Items are stored in the default keychain with the service name <code>Cryptomator</code>, unless configured otherwise
14+
* using the system property <code>cryptomator.integrationsMac.keychainServiceName</code>.
15+
*/
16+
@Priority(1010)
17+
@OperatingSystem(OperatingSystem.Value.MAC)
18+
public class TouchIdKeychainAccess implements KeychainAccessProvider {
19+
20+
private static final String SERVICE_NAME = System.getProperty("cryptomator.integrationsMac.keychainServiceName", "Cryptomator");
21+
22+
private final MacKeychain keychain;
23+
24+
public TouchIdKeychainAccess() {
25+
this(new MacKeychain());
26+
}
27+
28+
// visible for testing
29+
TouchIdKeychainAccess(MacKeychain keychain) {
30+
this.keychain = keychain;
31+
}
32+
33+
@Override
34+
public String displayName() {
35+
return Localization.get().getString("org.cryptomator.macos.keychain.touchIdDisplayName");
36+
}
37+
38+
@Override
39+
public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
40+
keychain.storePassword(SERVICE_NAME, key, passphrase, true);
41+
}
42+
43+
@Override
44+
public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean requireOsAuthentication) throws KeychainAccessException {
45+
keychain.storePassword(SERVICE_NAME, key, passphrase, requireOsAuthentication);
46+
}
47+
48+
@Override
49+
public char[] loadPassphrase(String key) {
50+
return keychain.loadPassword(SERVICE_NAME, key);
51+
}
52+
53+
@Override
54+
public boolean isSupported() {
55+
return keychain.isTouchIDavailable();
56+
}
57+
58+
@Override
59+
public boolean isLocked() {
60+
return false;
61+
}
62+
63+
@Override
64+
public void deletePassphrase(String key) throws KeychainAccessException {
65+
keychain.deletePassword(SERVICE_NAME, key);
66+
}
67+
68+
@Override
69+
public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
70+
if (keychain.deletePassword(SERVICE_NAME, key)) {
71+
keychain.storePassword(SERVICE_NAME, key, passphrase, true);
72+
}
73+
}
74+
75+
}

src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,14 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati
123123
(*env)->ReleaseByteArrayElements(env, key, keyStr, JNI_ABORT);
124124
return status;
125125
}
126+
127+
JNIEXPORT jboolean JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_isTouchIDavailable(JNIEnv *env, jobject thisObj) {
128+
NSError *error = nil;
129+
LAContext *context = getSharedLAContext();
130+
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
131+
return JNI_TRUE;
132+
} else {
133+
NSLog(@"Touch ID is not available: %@", error.localizedDescription);
134+
return JNI_FALSE;
135+
}
136+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
org.cryptomator.macos.keychain.MacSystemKeychainAccess
1+
org.cryptomator.macos.keychain.MacSystemKeychainAccess
2+
org.cryptomator.macos.keychain.TouchIdKeychainAccess
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
org.cryptomator.macos.keychain.displayName=macOS Keychain
1+
org.cryptomator.macos.keychain.displayName=macOS Keychain
2+
org.cryptomator.macos.keychain.touchIdDisplayName=Touch ID

src/test/java/org/cryptomator/macos/keychain/KeychainAccessProviderTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ public class KeychainAccessProviderTest {
1212
public void testLoadMacSystemKeychainAccess() {
1313
var provider = KeychainAccessProvider.get().findAny();
1414
Assertions.assertTrue(provider.isPresent());
15-
Assertions.assertInstanceOf(MacSystemKeychainAccess.class, provider.get());
15+
Assertions.assertTrue(
16+
provider.get() instanceof TouchIdKeychainAccess
17+
|| provider.get() instanceof MacSystemKeychainAccess);
1618
}
1719

1820
}

0 commit comments

Comments
 (0)