mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-22 12:41:27 +00:00
Feature: Use user and system certificate stores on macOS (#3837)
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
package org.cryptomator.networking;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.KeyStoreSpi;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class CombinedKeyStoreSpi extends KeyStoreSpi {
|
||||
|
||||
private final KeyStore primary;
|
||||
private final KeyStore fallback;
|
||||
|
||||
public static CombinedKeyStoreSpi create(KeyStore primary, KeyStore fallback) {
|
||||
checkIfLoaded(primary);
|
||||
checkIfLoaded(fallback);
|
||||
return new CombinedKeyStoreSpi(primary, fallback);
|
||||
}
|
||||
|
||||
private static void checkIfLoaded(KeyStore s) {
|
||||
try {
|
||||
s.aliases();
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalArgumentException("Keystore %s is not loaded.".formatted(s.getType()));
|
||||
}
|
||||
}
|
||||
|
||||
private CombinedKeyStoreSpi(KeyStore primary, KeyStore fallback) {
|
||||
this.primary = primary;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
try {
|
||||
Key key = primary.getKey(alias, password);
|
||||
if (key == null) {
|
||||
key = fallback.getKey(alias, password);
|
||||
}
|
||||
return key;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] engineGetCertificateChain(String alias) {
|
||||
try {
|
||||
Certificate[] chain = primary.getCertificateChain(alias);
|
||||
if (chain == null) {
|
||||
chain = fallback.getCertificateChain(alias);
|
||||
}
|
||||
return chain;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate engineGetCertificate(String alias) {
|
||||
try {
|
||||
Certificate cert = primary.getCertificate(alias);
|
||||
if (cert == null) {
|
||||
cert = fallback.getCertificate(alias);
|
||||
}
|
||||
return cert;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date engineGetCreationDate(String alias) {
|
||||
try {
|
||||
Date date = primary.getCreationDate(alias);
|
||||
if (date == null) {
|
||||
date = fallback.getCreationDate(alias);
|
||||
}
|
||||
return date;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineDeleteEntry(String alias) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> engineAliases() {
|
||||
var aliases = new LinkedHashSet<String>();
|
||||
try {
|
||||
primary.aliases().asIterator().forEachRemaining(aliases::add);
|
||||
fallback.aliases().asIterator().forEachRemaining(aliases::add);
|
||||
return Collections.enumeration(aliases);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineContainsAlias(String alias) {
|
||||
try {
|
||||
return primary.containsAlias(alias) || fallback.containsAlias(alias);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int engineSize() {
|
||||
var aliases = engineAliases();
|
||||
var i = new AtomicInteger(0);
|
||||
aliases.asIterator().forEachRemaining(_ -> i.incrementAndGet());
|
||||
return i.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsKeyEntry(String alias) {
|
||||
try {
|
||||
return primary.isKeyEntry(alias) || fallback.isKeyEntry(alias);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsCertificateEntry(String alias) {
|
||||
try {
|
||||
return primary.isCertificateEntry(alias) || fallback.isCertificateEntry(alias);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String engineGetCertificateAlias(Certificate cert) {
|
||||
try {
|
||||
String alias = primary.getCertificateAlias(cert);
|
||||
if (alias == null) {
|
||||
alias = fallback.getCertificateAlias(cert);
|
||||
}
|
||||
return alias;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
|
||||
// Nothing to do; the real keystores are already loaded.
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
/**
|
||||
@@ -16,6 +17,16 @@ public class SSLContextWithMacKeychain extends SSLContextDifferentTrustStoreBase
|
||||
|
||||
@Override
|
||||
KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
|
||||
return KeyStore.getInstance("KeychainStore-ROOT");
|
||||
var userKeyStore = KeyStore.getInstance("KeychainStore");
|
||||
var systemRootKeyStore = KeyStore.getInstance("KeychainStore-ROOT");
|
||||
userKeyStore.load(null);
|
||||
systemRootKeyStore.load(null);
|
||||
try {
|
||||
CombinedKeyStoreSpi spi = CombinedKeyStoreSpi.create(userKeyStore, systemRootKeyStore);
|
||||
Provider dummyProvider = new Provider("CombinedKeyStoreProvider", "1.0", "Provides a combined, read-only KeyStore") {};
|
||||
return new KeyStore(spi, dummyProvider, "CombinedKeyStoreProvider") {};
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new KeyStoreException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user