Skip to content

Commit

Permalink
Add KeychainStore-ROOT keystore for root certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeybakhtin committed Dec 22, 2023
1 parent db98b7c commit 8fdea0f
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ public Object newInstance(Object ctrParamObj)
try {
if (type.equals("KeyStore")) {
if (algo.equals("KeychainStore")) {
return new KeychainStore();
return new KeychainStore.USER();
} else if (algo.equals("KeychainStore-ROOT")) {
return new KeychainStore.ROOT();
}
}
} catch (Exception ex) {
Expand All @@ -82,7 +84,9 @@ public AppleProvider() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
putService(new ProviderService(p, "KeyStore",
"KeychainStore", "apple.security.KeychainStore"));
"KeychainStore", "apple.security.KeychainStore.USER"));
putService(new ProviderService(p, "KeyStore",
"KeychainStore-ROOT", "apple.security.KeychainStore.ROOT"));
return null;
}
});
Expand Down
103 changes: 79 additions & 24 deletions src/java.base/macosx/classes/apple/security/KeychainStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,72 @@
import sun.security.x509.*;

/**
* This class provides the keystore implementation referred to as "KeychainStore".
* It uses the current user's keychain as its backing storage, and does NOT support
* a file-based implementation.
* This class provides the keystores implementation referred to as
* "KeychainStore" and "KeychainStore-ROOT".
* It uses the current user's and system root keychains accordingly
* as its backing storage, and does NOT support a file-based
* implementation.
*/

public final class KeychainStore extends KeyStoreSpi {
abstract class KeychainStore extends KeyStoreSpi {

/**
* Current user's keychain
*/
public static final class USER extends KeychainStore {
public USER() {
super("USER");
}

}

/**
* System root read-only keychain
*
*/
public static final class ROOT extends KeychainStore {
public ROOT() {
super("ROOT");
}

/**
* Delete operation is not permitted for trusted anchors
*/
public void engineDeleteEntry(String alias)
throws KeyStoreException
{
throw new KeyStoreException("Trusted entry <" + alias + "> can not be removed");
}

/**
* Changes are not permitted for trusted anchors
*/
public void engineSetKeyEntry(String alias, Key key, char[] password,
Certificate[] chain)
throws KeyStoreException
{
throw new KeyStoreException("Trusted entry <" + alias + "> can not be modified");
}

/**
* Changes are not permitted for trusted anchors
*/
public void engineSetKeyEntry(String alias, byte[] key,
Certificate[] chain)
throws KeyStoreException
{
throw new KeyStoreException("Trusted entry <" + alias + "> can not be modified");
}

/**
* Changes are not permitted for trusted anchors
*/
public void engineStore(OutputStream stream, char[] password)
throws IOException, NoSuchAlgorithmException, CertificateException
{
// do nothing, no changes allowed
}
}

// Private keys and their supporting certificate chains
// If a key came from the keychain it has a SecKeyRef and one or more
Expand All @@ -68,7 +128,6 @@ static class TrustedCertEntry {

Certificate cert;
long certRef; // SecCertificateRef for this key
boolean isReadOnly; // kSecTrustSettingsDomainSystem certificates

// Each KeyStore.TrustedCertificateEntry has 2 attributes:
// 1. "trustSettings" -> trustSettings.toString()
Expand Down Expand Up @@ -138,22 +197,25 @@ private static void permissionCheck() {
}
}

private void readOnlyCheck(String alias) throws KeyStoreException {
String lowerAlias = alias.toLowerCase();
if (entries.get(lowerAlias) instanceof TrustedCertEntry oldEntry) {
if (oldEntry.isReadOnly) {
throw new KeyStoreException("Trusted entry <" + alias + "> can not be modified");
}
}
}
private final String storeName;

/**
* Verify the Apple provider in the constructor.
*
* @exception SecurityException if fails to verify
* its own integrity
*/
public KeychainStore() { }
private KeychainStore(String name) {
this.storeName = name;
}

/**
* Returns the name of the keystore.
*/
private String getName()
{
return storeName;
}

/**
* Returns the key associated with the given alias, using the given
Expand Down Expand Up @@ -411,8 +473,6 @@ public void engineSetKeyEntry(String alias, Key key, char[] password,

synchronized(entries) {
try {
readOnlyCheck(alias);

KeyEntry entry = new KeyEntry();
entry.date = new Date();

Expand Down Expand Up @@ -445,8 +505,6 @@ public void engineSetKeyEntry(String alias, Key key, char[] password,

entries.put(lowerAlias, entry);
addedEntries.put(lowerAlias, entry);
} catch (KeyStoreException kse) {
throw kse;
} catch (Exception nsae) {
KeyStoreException ke = new KeyStoreException("Key protection algorithm not found: " + nsae);
ke.initCause(nsae);
Expand Down Expand Up @@ -485,7 +543,6 @@ public void engineSetKeyEntry(String alias, byte[] key,
permissionCheck();

synchronized(entries) {
readOnlyCheck(alias);

// key must be encoded as EncryptedPrivateKeyInfo as defined in
// PKCS#8
Expand Down Expand Up @@ -538,7 +595,6 @@ public void engineDeleteEntry(String alias)

String lowerAlias = alias.toLowerCase(Locale.ROOT);
synchronized(entries) {
readOnlyCheck(alias);
Object entry = entries.remove(lowerAlias);
deletedEntries.put(lowerAlias, entry);
}
Expand Down Expand Up @@ -777,15 +833,15 @@ public void engineLoad(InputStream stream, char[] password)
}

entries.clear();
_scanKeychain();
_scanKeychain(getName());
if (debug != null) {
debug.println("KeychainStore load entry count: " +
entries.size());
}
}
}

private native void _scanKeychain();
private native void _scanKeychain(String name);

/**
* Callback method from _scanKeychain. If a trusted certificate is found,
Expand All @@ -809,7 +865,7 @@ public void engineLoad(InputStream stream, char[] password)
* null (end if trust_n)
*/
private void createTrustedCertEntry(String alias, List<String> inputTrust,
long keychainItemRef, long creationDate, boolean isReadOnly, byte[] derStream) {
long keychainItemRef, long creationDate, byte[] derStream) {
TrustedCertEntry tce = new TrustedCertEntry();

try {
Expand All @@ -819,7 +875,6 @@ private void createTrustedCertEntry(String alias, List<String> inputTrust,
input.close();
tce.cert = cert;
tce.certRef = keychainItemRef;
tce.isReadOnly = isReadOnly;

// Check whether a certificate with same alias already exists and is the same
// If yes, we can return here - the existing entry must have the same
Expand Down
85 changes: 63 additions & 22 deletions src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m
Original file line number Diff line number Diff line change
Expand Up @@ -419,17 +419,14 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
OSStatus err = SecKeychainSearchCreateFromAttributes(NULL, kSecCertificateItemClass, NULL, &keychainItemSearch);
SecKeychainItemRef theItem = NULL;
OSErr searchResult = noErr;
SecTrustSettingsDomain domains[] = {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainSystem};
int numDomains = (int)sizeof(domains);
CFArrayRef currAnchors = NULL;

jclass jc_KeychainStore = (*env)->FindClass(env, "apple/security/KeychainStore");
if (jc_KeychainStore == NULL) {
goto errOut;
}

jmethodID jm_createTrustedCertEntry = (*env)->GetMethodID(
env, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;Ljava/util/List;JJZ[B)V");
env, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;Ljava/util/List;JJ[B)V");
if (jm_createTrustedCertEntry == NULL) {
goto errOut;
}
Expand Down Expand Up @@ -474,10 +471,10 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
// kSecTrustSettingsDomainSystem is ignored because it seems to only contain data for root certificates
jobject inputTrust = NULL;
CFArrayRef trustSettings = NULL;
for (int n = 0; n < numDomains-1; n++) {
for (SecTrustSettingsDomain domain = kSecTrustSettingsDomainUser; domain <= kSecTrustSettingsDomainAdmin; domain++) {
trustSettings = NULL;
// Load user trustSettings into inputTrust
if (SecTrustSettingsCopyTrustSettings(certRef, domains[n], &trustSettings) == errSecSuccess && trustSettings != NULL) {
if (SecTrustSettingsCopyTrustSettings(certRef, domain, &trustSettings) == errSecSuccess && trustSettings != NULL) {
if(inputTrust == NULL) {
inputTrust = (*env)->NewObject(env, jc_arrayListClass, jm_arrayListCons);
}
Expand All @@ -498,18 +495,56 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)

// Call back to the Java object to create Java objects corresponding to this security object.
jlong nativeRef = ptr_to_jlong(certRef);
(*env)->CallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, inputTrust, nativeRef, creationDate, JNI_FALSE, certData);
(*env)->CallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, inputTrust, nativeRef, creationDate, certData);
if ((*env)->ExceptionCheck(env)) {
goto errOut;
}
}
} while (searchResult == noErr);

errOut:
if (keychainItemSearch != NULL) {
CFRelease(keychainItemSearch);
}
}

static void addCertificatesToKeystoreRoot(JNIEnv *env, jobject keyStore)
{
OSStatus err;
SecKeychainItemRef theItem = NULL;
CFArrayRef currAnchors = NULL;

jclass jc_KeychainStore = (*env)->FindClass(env, "apple/security/KeychainStore");
if (jc_KeychainStore == NULL) {
goto errOut;
}

jmethodID jm_createTrustedCertEntry = (*env)->GetMethodID(
env, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;Ljava/util/List;JJ[B)V");
if (jm_createTrustedCertEntry == NULL) {
goto errOut;
}

jclass jc_arrayListClass = (*env)->FindClass(env, "java/util/ArrayList");
if (jc_arrayListClass == NULL) {
goto errOut;
}

jmethodID jm_arrayListCons = (*env)->GetMethodID(env, jc_arrayListClass, "<init>", "()V");
if (jm_arrayListCons == NULL) {
goto errOut;
}

jmethodID jm_listAdd = (*env)->GetMethodID(env, jc_arrayListClass, "add", "(Ljava/lang/Object;)Z");
if (jm_listAdd == NULL) {
goto errOut;
}

// Read Trust Anchors
if(SecTrustCopyAnchorCertificates(&currAnchors) == errSecSuccess) {
CFIndex i, nAnchors = CFArrayGetCount(currAnchors);
CFIndex nAnchors = CFArrayGetCount(currAnchors);

for (i = 0; i < nAnchors; i++) {
for (CFIndex i = 0; i < nAnchors; i++) {
SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(currAnchors, i);
CSSM_DATA currCertificate;
err = SecCertificateGetData(certRef, &currCertificate);
Expand All @@ -530,10 +565,10 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
// kSecTrustSettingsDomainAdmin and kSecTrustSettingsDomainSystem
jobject inputTrust = NULL;
CFArrayRef trustSettings = NULL;
for (int n = 0; n < numDomains; n++) {
for (SecTrustSettingsDomain domain = kSecTrustSettingsDomainUser; domain <= kSecTrustSettingsDomainSystem; domain++) {
trustSettings = NULL;
// Load user trustSettings into inputTrust
if (SecTrustSettingsCopyTrustSettings(certRef, domains[n], &trustSettings) == errSecSuccess && trustSettings != NULL) {
if (SecTrustSettingsCopyTrustSettings(certRef, domain, &trustSettings) == errSecSuccess && trustSettings != NULL) {
if(inputTrust == NULL) {
inputTrust = (*env)->NewObject(env, jc_arrayListClass, jm_arrayListCons);
}
Expand All @@ -545,15 +580,16 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
CFRelease(trustSettings);
}
}
if (inputTrust == NULL)
if (inputTrust == NULL) {
continue;
}

// Find the creation date.
jlong creationDate = getModDateFromItem(env, theItem);
jlong creationDate = getModDateFromItem(env, (SecKeychainItemRef)certRef);

// Call back to the Java object to create Java objects corresponding to this security object.
jlong nativeRef = ptr_to_jlong(certRef);
(*env)->CallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, inputTrust, nativeRef, creationDate, JNI_TRUE, certData);
(*env)->CallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, inputTrust, nativeRef, creationDate, certData);
if ((*env)->ExceptionCheck(env)) {
goto errOut;
}
Expand All @@ -563,9 +599,6 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
}

errOut:
if (keychainItemSearch != NULL) {
CFRelease(keychainItemSearch);
}
if (currAnchors != NULL) {
CFRelease(currAnchors);
}
Expand Down Expand Up @@ -642,10 +675,10 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
/*
* Class: apple_security_KeychainStore
* Method: _scanKeychain
* Signature: ()V
* Signature: (Ljava/lang/String)V
*/
JNIEXPORT void JNICALL Java_apple_security_KeychainStore__1scanKeychain
(JNIEnv *env, jobject this)
(JNIEnv *env, jobject this, jstring name)
{
// Look for 'identities' -- private key and certificate chain pairs -- and add those.
// Search for these first, because a certificate that's found here as part of an identity will show up
Expand All @@ -654,9 +687,17 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)

JNU_CHECK_EXCEPTION(env);

// Scan current keychain for trusted certificates.
addCertificatesToKeystore(env, this);

jboolean isCopy;
const char *name_utf = (*env)->GetStringUTFChars(env, name, &isCopy);
if (name_utf != NULL) {
if (strcmp(name_utf, "ROOT") == 0) {
// Scan Trusted Anchors keychain for trusted certificates.
addCertificatesToKeystoreRoot(env, this);
} else {
// Scan current keychain for trusted certificates.
addCertificatesToKeystore(env, this);
}
}
}

NSString* JavaStringToNSString(JNIEnv *env, jstring jstr) {
Expand Down

0 comments on commit 8fdea0f

Please sign in to comment.