diff --git a/pom.xml b/pom.xml
index 065d806..d042841 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,7 +30,7 @@
17
- 1.3.1
+ 1.4.0
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..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,10 +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);
+ (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 12422bb..49cf008 100644
--- a/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java
+++ b/src/main/java/org/cryptomator/macos/keychain/MacKeychain.java
@@ -17,16 +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);
+ 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) {
@@ -75,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;
@@ -111,7 +112,7 @@ private Native() {
NativeLibLoader.loadLib();
}
- public native int storePassword(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 c2f676e..201d7fe 100644
--- a/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java
+++ b/src/main/java/org/cryptomator/macos/keychain/MacSystemKeychainAccess.java
@@ -35,8 +35,8 @@ public String displayName() {
}
@Override
- public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
- keychain.storePassword(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
@@ -62,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 5309050..24d1a5e 100644
--- a/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m
+++ b/src/main/native/org_cryptomator_macos_keychain_MacKeychain_Native.m
@@ -9,22 +9,36 @@
#import "org_cryptomator_macos_keychain_MacKeychain_Native.h"
#import
#import
+#import
-JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Native_storePassword(JNIEnv *env, jobject thisObj, jbyteArray service, jbyteArray key, jbyteArray password) {
+static LAContext *sharedContext = nil;
+static LAContext* getSharedLAContext(void) {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ sharedContext = [[LAContext alloc] init];
+ });
+ return sharedContext;
+}
+
+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 = @{
+ 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)kSecUseAuthenticationContext] = context;
+ }
CFDictionaryRef result = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecSuccess && result != NULL) {
@@ -35,12 +49,15 @@ 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 = @{
+ 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)kSecAttrAccessControl] = (__bridge_transfer id)SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, kSecAccessControlUserPresence, NULL);
+ }
status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
} else {
NSLog(@"Error storing item in keychain. Status code: %d", (int)status);
@@ -57,13 +74,15 @@ JNIEXPORT jbyteArray JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_000
jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL);
// find existing:
+ LAContext *context = getSharedLAContext();
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)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
+ (__bridge id)kSecUseAuthenticationContext: context
};
CFDictionaryRef result = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
@@ -88,18 +107,15 @@ JNIEXPORT jint JNICALL Java_org_cryptomator_macos_keychain_MacKeychain_00024Nati
jbyte *keyStr = (*env)->GetByteArrayElements(env, key, NULL);
// find existing:
+ LAContext *context = getSharedLAContext();
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);
- 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);
}
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