aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey Bakhtin <abakhtin@openjdk.org>2022-03-15 12:25:23 +0300
committerChristoph Langer <clanger@openjdk.org>2022-04-08 08:09:52 +0200
commitbbc66cda9747faa03eeb091a0be9c27c970e9475 (patch)
tree4d2a43295665c3d1834afd9dbe38214bc6e3f1b3
parent16a9af0a12329871e1bce30e10c2c3f7bf5d4942 (diff)
downloadjdk11-bbc66cda9747faa03eeb091a0be9c27c970e9475.tar.gz
8278449: Improve keychain support
Reviewed-by: andrew Backport-of: 2376bb88eff3ae6922c4cae276e1d703a520853d
-rw-r--r--src/java.base/macosx/classes/apple/security/KeychainStore.java223
-rw-r--r--src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m63
-rw-r--r--src/java.base/share/classes/sun/security/tools/keytool/Main.java5
-rw-r--r--test/lib/jdk/test/lib/SecurityTools.java61
4 files changed, 291 insertions, 61 deletions
diff --git a/src/java.base/macosx/classes/apple/security/KeychainStore.java b/src/java.base/macosx/classes/apple/security/KeychainStore.java
index d4409c4daf..74a531dae4 100644
--- a/src/java.base/macosx/classes/apple/security/KeychainStore.java
+++ b/src/java.base/macosx/classes/apple/security/KeychainStore.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -68,6 +68,25 @@ public final class KeychainStore extends KeyStoreSpi {
Certificate cert;
long certRef; // SecCertificateRef for this key
+
+ // Each KeyStore.TrustedCertificateEntry have 2 attributes:
+ // 1. "trustSettings" -> trustSettings.toString()
+ // 2. "2.16.840.1.113894.746875.1.1" -> trustedKeyUsageValue
+ // The 1st one is mainly for debugging use. The 2nd one is similar
+ // to the attribute with the same key in a PKCS12KeyStore.
+
+ // The SecTrustSettingsCopyTrustSettings() output for this certificate
+ // inside the KeyChain in its original array of CFDictionaryRef objects
+ // structure with values dumped as strings. For each trust, an extra
+ // entry "SecPolicyOid" is added whose value is the OID for this trust.
+ // The extra entries are used to construct trustedKeyUsageValue.
+ List<Map<String, String>> trustSettings;
+
+ // One or more OIDs defined in http://oidref.com/1.2.840.113635.100.1.
+ // It can also be "2.5.29.37.0" for a self-signed certificate with
+ // an empty trust settings. This value is never empty. When there are
+ // multiple OID values, it takes the form of "[1.1.1, 1.1.2]".
+ String trustedKeyUsageValue;
};
/**
@@ -310,6 +329,66 @@ public final class KeychainStore extends KeyStoreSpi {
}
}
+ private final class LocalAttr
+ implements KeyStore.Entry.Attribute {
+
+ private String name;
+ private String value;
+
+ public LocalAttr(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Calculates a hash code value for the object.
+ * Objects that are equal will also have the same hashcode.
+ */
+ public int hashCode() {
+ return Objects.hash(name, value);
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof LocalAttr)) {
+ return false;
+ }
+
+ LocalAttr other =
+ (LocalAttr) obj;
+ return (Objects.equals(name, other.getName()) &&
+ Objects.equals(value, other.getValue()));
+ }
+
+ }
+
+ @Override
+ public KeyStore.Entry engineGetEntry(String alias, KeyStore.ProtectionParameter protParam)
+ throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
+ if (engineIsCertificateEntry(alias)) {
+ Object entry = entries.get(alias.toLowerCase());
+ if (entry instanceof TrustedCertEntry) {
+ TrustedCertEntry tEntry = (TrustedCertEntry)entry;
+ return new KeyStore.TrustedCertificateEntry(
+ tEntry.cert, Set.of(
+ new LocalAttr(KnownOIDs.ORACLE_TrustedKeyUsage.value(), tEntry.trustedKeyUsageValue),
+ new LocalAttr("trustSettings", tEntry.trustSettings.toString())));
+ }
+ }
+ return super.engineGetEntry(alias, protParam);
+ }
+
/**
* Returns the creation date of the entry identified by the given alias.
*
@@ -463,55 +542,12 @@ public final class KeychainStore extends KeyStoreSpi {
}
/**
- * Assigns the given certificate to the given alias.
- *
- * <p>If the given alias already exists in this keystore and identifies a
- * <i>trusted certificate entry</i>, the certificate associated with it is
- * overridden by the given certificate.
- *
- * @param alias the alias name
- * @param cert the certificate
- *
- * @exception KeyStoreException if the given alias already exists and does
- * not identify a <i>trusted certificate entry</i>, or this operation
- * fails for some other reason.
+ * Adding trusted certificate entry is not supported.
*/
public void engineSetCertificateEntry(String alias, Certificate cert)
- throws KeyStoreException
- {
- permissionCheck();
-
- synchronized(entries) {
-
- Object entry = entries.get(alias.toLowerCase());
- if ((entry != null) && (entry instanceof KeyEntry)) {
- throw new KeyStoreException
- ("Cannot overwrite key entry with certificate");
- }
-
- // This will be slow, but necessary. Enumerate the values and then see if the cert matches the one in the trusted cert entry.
- // Security framework doesn't support the same certificate twice in a keychain.
- Collection<Object> allValues = entries.values();
-
- for (Object value : allValues) {
- if (value instanceof TrustedCertEntry) {
- TrustedCertEntry tce = (TrustedCertEntry)value;
- if (tce.cert.equals(cert)) {
- throw new KeyStoreException("Keychain does not support mulitple copies of same certificate.");
- }
- }
- }
-
- TrustedCertEntry trustedCertEntry = new TrustedCertEntry();
- trustedCertEntry.cert = cert;
- trustedCertEntry.date = new Date();
- String lowerAlias = alias.toLowerCase();
- if (entries.get(lowerAlias) != null) {
- deletedEntries.put(lowerAlias, entries.get(lowerAlias));
- }
- entries.put(lowerAlias, trustedCertEntry);
- addedEntries.put(lowerAlias, trustedCertEntry);
- }
+ throws KeyStoreException {
+ throw new KeyStoreException("Cannot set trusted certificate entry." +
+ " Use the macOS \"security add-trusted-cert\" command instead.");
}
/**
@@ -690,10 +726,7 @@ public final class KeychainStore extends KeyStoreSpi {
String alias = e.nextElement();
Object entry = addedEntries.get(alias);
if (entry instanceof TrustedCertEntry) {
- TrustedCertEntry tce = (TrustedCertEntry)entry;
- Certificate certElem;
- certElem = tce.cert;
- tce.certRef = addCertificateToKeychain(alias, certElem);
+ // Cannot set trusted certificate entry
} else {
KeyEntry keyEntry = (KeyEntry)entry;
@@ -788,9 +821,28 @@ public final class KeychainStore extends KeyStoreSpi {
private native void _scanKeychain();
/**
- * Callback method from _scanKeychain. If a trusted certificate is found, this method will be called.
+ * Callback method from _scanKeychain. If a trusted certificate is found,
+ * this method will be called.
+ *
+ * inputTrust is a list of strings in groups. Each group contains key/value
+ * pairs for one trust setting and ends with a null. Thus the size of the
+ * whole list is (2 * s_1 + 1) + (2 * s_2 + 1) + ... + (2 * s_n + 1),
+ * where s_i is the size of mapping for the i'th trust setting,
+ * and n is the number of trust settings. Ex:
+ *
+ * key1 for trust1
+ * value1 for trust1
+ * ..
+ * null (end of trust1)
+ * key1 for trust2
+ * value1 for trust2
+ * ...
+ * null (end of trust2)
+ * ...
+ * null (end if trust_n)
*/
- private void createTrustedCertEntry(String alias, long keychainItemRef, long creationDate, byte[] derStream) {
+ private void createTrustedCertEntry(String alias, List<String> inputTrust,
+ long keychainItemRef, long creationDate, byte[] derStream) {
TrustedCertEntry tce = new TrustedCertEntry();
try {
@@ -801,6 +853,69 @@ public final class KeychainStore extends KeyStoreSpi {
tce.cert = cert;
tce.certRef = keychainItemRef;
+ tce.trustSettings = new ArrayList<>();
+ Map<String,String> tmpMap = new LinkedHashMap<>();
+ for (int i = 0; i < inputTrust.size(); i++) {
+ if (inputTrust.get(i) == null) {
+ tce.trustSettings.add(tmpMap);
+ if (i < inputTrust.size() - 1) {
+ // Prepare an empty map for the next trust setting.
+ // Do not just clear(), must be a new object.
+ // Only create if not at end of list.
+ tmpMap = new LinkedHashMap<>();
+ }
+ } else {
+ tmpMap.put(inputTrust.get(i), inputTrust.get(i+1));
+ i++;
+ }
+ }
+
+ boolean isSelfSigned;
+ try {
+ cert.verify(cert.getPublicKey());
+ isSelfSigned = true;
+ } catch (Exception e) {
+ isSelfSigned = false;
+ }
+ if (tce.trustSettings.isEmpty()) {
+ if (isSelfSigned) {
+ // If a self-signed certificate has an empty trust settings,
+ // trust it for all purposes
+ tce.trustedKeyUsageValue = KnownOIDs.anyExtendedKeyUsage.value();
+ } else {
+ // Otherwise, return immediately. The certificate is not
+ // added into entries.
+ return;
+ }
+ } else {
+ List<String> values = new ArrayList<>();
+ for (var oneTrust : tce.trustSettings) {
+ var result = oneTrust.get("kSecTrustSettingsResult");
+ // https://developer.apple.com/documentation/security/sectrustsettingsresult?language=objc
+ // 1 = kSecTrustSettingsResultTrustRoot, 2 = kSecTrustSettingsResultTrustAsRoot
+ // If missing, a default value of kSecTrustSettingsResultTrustRoot is assumed
+ // for self-signed certificates (see doc for SecTrustSettingsCopyTrustSettings).
+ // Note that the same SecPolicyOid can appear in multiple trust settings
+ // for different kSecTrustSettingsAllowedError and/or kSecTrustSettingsPolicyString.
+ if ((result == null && isSelfSigned)
+ || "1".equals(result) || "2".equals(result)) {
+ // When no kSecTrustSettingsPolicy, it means everything
+ String oid = oneTrust.getOrDefault("SecPolicyOid",
+ KnownOIDs.anyExtendedKeyUsage.value());
+ if (!values.contains(oid)) {
+ values.add(oid);
+ }
+ }
+ }
+ if (values.isEmpty()) {
+ return;
+ }
+ if (values.size() == 1) {
+ tce.trustedKeyUsageValue = values.get(0);
+ } else {
+ tce.trustedKeyUsageValue = values.toString();
+ }
+ }
// Make a creation date.
if (creationDate != 0)
tce.date = new Date(creationDate);
diff --git a/src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m b/src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m
index f427737b9a..3f0c1de962 100644
--- a/src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m
+++ b/src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -368,6 +368,14 @@ errOut:
}
}
+#define ADD(list, str) { \
+ jobject localeObj = (*env)->NewStringUTF(env, [str UTF8String]); \
+ (*env)->CallBooleanMethod(env, list, jm_listAdd, localeObj); \
+ (*env)->DeleteLocalRef(env, localeObj); \
+}
+
+#define ADDNULL(list) (*env)->CallBooleanMethod(env, list, jm_listAdd, NULL)
+
static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
{
// Search the user keychain list for all X509 certificates.
@@ -379,8 +387,15 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
jclass jc_KeychainStore = (*env)->FindClass(env, "apple/security/KeychainStore");
CHECK_NULL(jc_KeychainStore);
jmethodID jm_createTrustedCertEntry = (*env)->GetMethodID(
- env, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;JJ[B)V");
+ env, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;Ljava/util/List;JJ[B)V");
CHECK_NULL(jm_createTrustedCertEntry);
+ jclass jc_arrayListClass = (*env)->FindClass(env, "java/util/ArrayList");
+ CHECK_NULL(jc_arrayListClass);
+ jmethodID jm_arrayListCons = (*env)->GetMethodID(env, jc_arrayListClass, "<init>", "()V");
+ CHECK_NULL(jm_arrayListCons);
+ jmethodID jm_listAdd = (*env)->GetMethodID(env, jc_arrayListClass, "add", "(Ljava/lang/Object;)Z");
+ CHECK_NULL(jm_listAdd);
+
do {
searchResult = SecKeychainSearchCopyNext(keychainItemSearch, &theItem);
@@ -401,12 +416,50 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
goto errOut;
}
+ // Only add certificates with trusted settings
+ CFArrayRef trustSettings;
+ if (SecTrustSettingsCopyTrustSettings(certRef, kSecTrustSettingsDomainUser, &trustSettings)
+ == errSecItemNotFound) {
+ continue;
+ }
+
+ // See KeychainStore::createTrustedCertEntry for content of inputTrust
+ jobject inputTrust = (*env)->NewObject(env, jc_arrayListClass, jm_arrayListCons);
+ CHECK_NULL(inputTrust);
+
+ // Dump everything inside trustSettings into inputTrust
+ CFIndex count = CFArrayGetCount(trustSettings);
+ for (int i = 0; i < count; i++) {
+ CFDictionaryRef oneTrust = (CFDictionaryRef) CFArrayGetValueAtIndex(trustSettings, i);
+ CFIndex size = CFDictionaryGetCount(oneTrust);
+ const void * keys [size];
+ const void * values [size];
+ CFDictionaryGetKeysAndValues(oneTrust, keys, values);
+ for (int j = 0; j < size; j++) {
+ NSString* s = [NSString stringWithFormat:@"%@", keys[j]];
+ ADD(inputTrust, s);
+ s = [NSString stringWithFormat:@"%@", values[j]];
+ ADD(inputTrust, s);
+ }
+ SecPolicyRef certPolicy;
+ certPolicy = (SecPolicyRef)CFDictionaryGetValue(oneTrust, kSecTrustSettingsPolicy);
+ if (certPolicy != NULL) {
+ CFDictionaryRef policyDict = SecPolicyCopyProperties(certPolicy);
+ ADD(inputTrust, @"SecPolicyOid");
+ NSString* s = [NSString stringWithFormat:@"%@", CFDictionaryGetValue(policyDict, @"SecPolicyOid")];
+ ADD(inputTrust, s);
+ CFRelease(policyDict);
+ }
+ ADDNULL(inputTrust);
+ }
+ CFRelease(trustSettings);
+
// Find the creation date.
jlong creationDate = getModDateFromItem(env, theItem);
// 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, nativeRef, creationDate, certData);
+ (*env)->CallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, inputTrust, nativeRef, creationDate, certData);
JNU_CHECK_EXCEPTION(env);
}
} while (searchResult == noErr);
@@ -522,8 +575,8 @@ NSString* JavaStringToNSString(JNIEnv *env, jstring jstr) {
/*
* Class: apple_security_KeychainStore
* Method: _addItemToKeychain
- * Signature: (Ljava/lang/String;[B)I
-*/
+ * Signature: (Ljava/lang/String;Z[B[C)J
+ */
JNIEXPORT jlong JNICALL Java_apple_security_KeychainStore__1addItemToKeychain
(JNIEnv *env, jobject this, jstring alias, jboolean isCertificate, jbyteArray rawDataObj, jcharArray passwordObj)
{
diff --git a/src/java.base/share/classes/sun/security/tools/keytool/Main.java b/src/java.base/share/classes/sun/security/tools/keytool/Main.java
index 4499d90d90..3582ca50e1 100644
--- a/src/java.base/share/classes/sun/security/tools/keytool/Main.java
+++ b/src/java.base/share/classes/sun/security/tools/keytool/Main.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -2150,6 +2150,9 @@ public final class Main {
out.println(mf);
dumpCert(cert, out);
} else if (debug) {
+ for (var attr : keyStore.getEntry(alias, null).getAttributes()) {
+ System.out.println("Attribute " + attr.getName() + ": " + attr.getValue());
+ }
out.println(cert.toString());
} else {
out.println("trustedCertEntry, ");
diff --git a/test/lib/jdk/test/lib/SecurityTools.java b/test/lib/jdk/test/lib/SecurityTools.java
index 30da87f2d3..0e4865c5b9 100644
--- a/test/lib/jdk/test/lib/SecurityTools.java
+++ b/test/lib/jdk/test/lib/SecurityTools.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -23,6 +23,7 @@
package jdk.test.lib;
+import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -275,5 +276,63 @@ public class SecurityTools {
}
return result;
}
+
+ // Create a temporary keychain in macOS and use it. The original
+ // keychains will be restored when the object is closed.
+ public static class TemporaryKeychain implements Closeable {
+ // name of new keychain
+ private final String newChain;
+ // names of the original keychains
+ private final List<String> oldChains;
+
+ public TemporaryKeychain(String name) {
+ Path p = Path.of(name + ".keychain-db");
+ newChain = p.toAbsolutePath().toString();
+ try {
+ oldChains = ProcessTools.executeProcess("security", "list-keychains")
+ .shouldHaveExitValue(0)
+ .getStdout()
+ .lines()
+ .map(String::trim)
+ .map(x -> x.startsWith("\"") ? x.substring(1, x.length() - 1) : x)
+ .collect(Collectors.toList());
+ if (!Files.exists(p)) {
+ ProcessTools.executeProcess("security", "create-keychain", "-p", "changeit", newChain)
+ .shouldHaveExitValue(0);
+ }
+ ProcessTools.executeProcess("security", "unlock-keychain", "-p", "changeit", newChain)
+ .shouldHaveExitValue(0);
+ ProcessTools.executeProcess("security", "list-keychains", "-s", newChain)
+ .shouldHaveExitValue(0);
+ } catch (Throwable t) {
+ if (t instanceof RuntimeException) {
+ throw (RuntimeException)t;
+ } else {
+ throw new RuntimeException(t);
+ }
+ }
+ }
+
+ public String chain() {
+ return newChain;
+ }
+
+ @Override
+ public void close() throws IOException {
+ List<String> cmds = new ArrayList<>();
+ cmds.addAll(List.of("security", "list-keychains", "-s"));
+ cmds.addAll(oldChains);
+ try {
+ ProcessTools.executeProcess(cmds.toArray(new String[0]))
+ .shouldHaveExitValue(0);
+ } catch (Throwable t) {
+ if (t instanceof RuntimeException) {
+ throw (RuntimeException)t;
+ } else {
+ throw new RuntimeException(t);
+ }
+ }
+ }
+ }
}