Skip to content

Commit f11bb89

Browse files
Alexey BakhtinRealCLanger
authored andcommitted
8278449: Improve keychain support
Reviewed-by: andrew Backport-of: 2376bb88eff3ae6922c4cae276e1d703a520853d
1 parent 38b7732 commit f11bb89

File tree

4 files changed

+260
-61
lines changed

4 files changed

+260
-61
lines changed

src/java.base/macosx/classes/apple/security/KeychainStore.java

Lines changed: 138 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -68,6 +68,25 @@ static class TrustedCertEntry {
6868

6969
Certificate cert;
7070
long certRef; // SecCertificateRef for this key
71+
72+
// Each KeyStore.TrustedCertificateEntry have 2 attributes:
73+
// 1. "trustSettings" -> trustSettings.toString()
74+
// 2. "2.16.840.1.113894.746875.1.1" -> trustedKeyUsageValue
75+
// The 1st one is mainly for debugging use. The 2nd one is similar
76+
// to the attribute with the same key in a PKCS12KeyStore.
77+
78+
// The SecTrustSettingsCopyTrustSettings() output for this certificate
79+
// inside the KeyChain in its original array of CFDictionaryRef objects
80+
// structure with values dumped as strings. For each trust, an extra
81+
// entry "SecPolicyOid" is added whose value is the OID for this trust.
82+
// The extra entries are used to construct trustedKeyUsageValue.
83+
List<Map<String, String>> trustSettings;
84+
85+
// One or more OIDs defined in http://oidref.com/1.2.840.113635.100.1.
86+
// It can also be "2.5.29.37.0" for a self-signed certificate with
87+
// an empty trust settings. This value is never empty. When there are
88+
// multiple OID values, it takes the form of "[1.1.1, 1.1.2]".
89+
String trustedKeyUsageValue;
7190
};
7291

7392
/**
@@ -300,6 +319,35 @@ public Certificate engineGetCertificate(String alias) {
300319
}
301320
}
302321

322+
private record LocalAttr(String name, String value)
323+
implements KeyStore.Entry.Attribute {
324+
325+
@Override
326+
public String getName() {
327+
return name;
328+
}
329+
330+
@Override
331+
public String getValue() {
332+
return value;
333+
}
334+
}
335+
336+
@Override
337+
public KeyStore.Entry engineGetEntry(String alias, KeyStore.ProtectionParameter protParam)
338+
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
339+
if (engineIsCertificateEntry(alias)) {
340+
Object entry = entries.get(alias.toLowerCase());
341+
if (entry instanceof TrustedCertEntry tEntry) {
342+
return new KeyStore.TrustedCertificateEntry(
343+
tEntry.cert, Set.of(
344+
new LocalAttr(KnownOIDs.ORACLE_TrustedKeyUsage.value(), tEntry.trustedKeyUsageValue),
345+
new LocalAttr("trustSettings", tEntry.trustSettings.toString())));
346+
}
347+
}
348+
return super.engineGetEntry(alias, protParam);
349+
}
350+
303351
/**
304352
* Returns the creation date of the entry identified by the given alias.
305353
*
@@ -453,55 +501,12 @@ public void engineSetKeyEntry(String alias, byte[] key,
453501
}
454502

455503
/**
456-
* Assigns the given certificate to the given alias.
457-
*
458-
* <p>If the given alias already exists in this keystore and identifies a
459-
* <i>trusted certificate entry</i>, the certificate associated with it is
460-
* overridden by the given certificate.
461-
*
462-
* @param alias the alias name
463-
* @param cert the certificate
464-
*
465-
* @exception KeyStoreException if the given alias already exists and does
466-
* not identify a <i>trusted certificate entry</i>, or this operation
467-
* fails for some other reason.
504+
* Adding trusted certificate entry is not supported.
468505
*/
469506
public void engineSetCertificateEntry(String alias, Certificate cert)
470-
throws KeyStoreException
471-
{
472-
permissionCheck();
473-
474-
synchronized(entries) {
475-
476-
Object entry = entries.get(alias.toLowerCase());
477-
if ((entry != null) && (entry instanceof KeyEntry)) {
478-
throw new KeyStoreException
479-
("Cannot overwrite key entry with certificate");
480-
}
481-
482-
// This will be slow, but necessary. Enumerate the values and then see if the cert matches the one in the trusted cert entry.
483-
// Security framework doesn't support the same certificate twice in a keychain.
484-
Collection<Object> allValues = entries.values();
485-
486-
for (Object value : allValues) {
487-
if (value instanceof TrustedCertEntry) {
488-
TrustedCertEntry tce = (TrustedCertEntry)value;
489-
if (tce.cert.equals(cert)) {
490-
throw new KeyStoreException("Keychain does not support mulitple copies of same certificate.");
491-
}
492-
}
493-
}
494-
495-
TrustedCertEntry trustedCertEntry = new TrustedCertEntry();
496-
trustedCertEntry.cert = cert;
497-
trustedCertEntry.date = new Date();
498-
String lowerAlias = alias.toLowerCase();
499-
if (entries.get(lowerAlias) != null) {
500-
deletedEntries.put(lowerAlias, entries.get(lowerAlias));
501-
}
502-
entries.put(lowerAlias, trustedCertEntry);
503-
addedEntries.put(lowerAlias, trustedCertEntry);
504-
}
507+
throws KeyStoreException {
508+
throw new KeyStoreException("Cannot set trusted certificate entry." +
509+
" Use the macOS \"security add-trusted-cert\" command instead.");
505510
}
506511

507512
/**
@@ -680,10 +685,7 @@ public void engineStore(OutputStream stream, char[] password)
680685
String alias = e.nextElement();
681686
Object entry = addedEntries.get(alias);
682687
if (entry instanceof TrustedCertEntry) {
683-
TrustedCertEntry tce = (TrustedCertEntry)entry;
684-
Certificate certElem;
685-
certElem = tce.cert;
686-
tce.certRef = addCertificateToKeychain(alias, certElem);
688+
// Cannot set trusted certificate entry
687689
} else {
688690
KeyEntry keyEntry = (KeyEntry)entry;
689691

@@ -778,9 +780,28 @@ public void engineLoad(InputStream stream, char[] password)
778780
private native void _scanKeychain();
779781

780782
/**
781-
* Callback method from _scanKeychain. If a trusted certificate is found, this method will be called.
783+
* Callback method from _scanKeychain. If a trusted certificate is found,
784+
* this method will be called.
785+
*
786+
* inputTrust is a list of strings in groups. Each group contains key/value
787+
* pairs for one trust setting and ends with a null. Thus the size of the
788+
* whole list is (2 * s_1 + 1) + (2 * s_2 + 1) + ... + (2 * s_n + 1),
789+
* where s_i is the size of mapping for the i'th trust setting,
790+
* and n is the number of trust settings. Ex:
791+
*
792+
* key1 for trust1
793+
* value1 for trust1
794+
* ..
795+
* null (end of trust1)
796+
* key1 for trust2
797+
* value1 for trust2
798+
* ...
799+
* null (end of trust2)
800+
* ...
801+
* null (end if trust_n)
782802
*/
783-
private void createTrustedCertEntry(String alias, long keychainItemRef, long creationDate, byte[] derStream) {
803+
private void createTrustedCertEntry(String alias, List<String> inputTrust,
804+
long keychainItemRef, long creationDate, byte[] derStream) {
784805
TrustedCertEntry tce = new TrustedCertEntry();
785806

786807
try {
@@ -791,6 +812,69 @@ private void createTrustedCertEntry(String alias, long keychainItemRef, long cre
791812
tce.cert = cert;
792813
tce.certRef = keychainItemRef;
793814

815+
tce.trustSettings = new ArrayList<>();
816+
Map<String,String> tmpMap = new LinkedHashMap<>();
817+
for (int i = 0; i < inputTrust.size(); i++) {
818+
if (inputTrust.get(i) == null) {
819+
tce.trustSettings.add(tmpMap);
820+
if (i < inputTrust.size() - 1) {
821+
// Prepare an empty map for the next trust setting.
822+
// Do not just clear(), must be a new object.
823+
// Only create if not at end of list.
824+
tmpMap = new LinkedHashMap<>();
825+
}
826+
} else {
827+
tmpMap.put(inputTrust.get(i), inputTrust.get(i+1));
828+
i++;
829+
}
830+
}
831+
832+
boolean isSelfSigned;
833+
try {
834+
cert.verify(cert.getPublicKey());
835+
isSelfSigned = true;
836+
} catch (Exception e) {
837+
isSelfSigned = false;
838+
}
839+
if (tce.trustSettings.isEmpty()) {
840+
if (isSelfSigned) {
841+
// If a self-signed certificate has an empty trust settings,
842+
// trust it for all purposes
843+
tce.trustedKeyUsageValue = KnownOIDs.anyExtendedKeyUsage.value();
844+
} else {
845+
// Otherwise, return immediately. The certificate is not
846+
// added into entries.
847+
return;
848+
}
849+
} else {
850+
List<String> values = new ArrayList<>();
851+
for (var oneTrust : tce.trustSettings) {
852+
var result = oneTrust.get("kSecTrustSettingsResult");
853+
// https://developer.apple.com/documentation/security/sectrustsettingsresult?language=objc
854+
// 1 = kSecTrustSettingsResultTrustRoot, 2 = kSecTrustSettingsResultTrustAsRoot
855+
// If missing, a default value of kSecTrustSettingsResultTrustRoot is assumed
856+
// for self-signed certificates (see doc for SecTrustSettingsCopyTrustSettings).
857+
// Note that the same SecPolicyOid can appear in multiple trust settings
858+
// for different kSecTrustSettingsAllowedError and/or kSecTrustSettingsPolicyString.
859+
if ((result == null && isSelfSigned)
860+
|| "1".equals(result) || "2".equals(result)) {
861+
// When no kSecTrustSettingsPolicy, it means everything
862+
String oid = oneTrust.getOrDefault("SecPolicyOid",
863+
KnownOIDs.anyExtendedKeyUsage.value());
864+
if (!values.contains(oid)) {
865+
values.add(oid);
866+
}
867+
}
868+
}
869+
if (values.isEmpty()) {
870+
return;
871+
}
872+
if (values.size() == 1) {
873+
tce.trustedKeyUsageValue = values.get(0);
874+
} else {
875+
tce.trustedKeyUsageValue = values.toString();
876+
}
877+
}
794878
// Make a creation date.
795879
if (creationDate != 0)
796880
tce.date = new Date(creationDate);

src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -368,6 +368,14 @@ static void addIdentitiesToKeystore(JNIEnv *env, jobject keyStore)
368368
}
369369
}
370370

371+
#define ADD(list, str) { \
372+
jobject localeObj = (*env)->NewStringUTF(env, [str UTF8String]); \
373+
(*env)->CallBooleanMethod(env, list, jm_listAdd, localeObj); \
374+
(*env)->DeleteLocalRef(env, localeObj); \
375+
}
376+
377+
#define ADDNULL(list) (*env)->CallBooleanMethod(env, list, jm_listAdd, NULL)
378+
371379
static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
372380
{
373381
// Search the user keychain list for all X509 certificates.
@@ -379,8 +387,15 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
379387
jclass jc_KeychainStore = (*env)->FindClass(env, "apple/security/KeychainStore");
380388
CHECK_NULL(jc_KeychainStore);
381389
jmethodID jm_createTrustedCertEntry = (*env)->GetMethodID(
382-
env, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;JJ[B)V");
390+
env, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;Ljava/util/List;JJ[B)V");
383391
CHECK_NULL(jm_createTrustedCertEntry);
392+
jclass jc_arrayListClass = (*env)->FindClass(env, "java/util/ArrayList");
393+
CHECK_NULL(jc_arrayListClass);
394+
jmethodID jm_arrayListCons = (*env)->GetMethodID(env, jc_arrayListClass, "<init>", "()V");
395+
CHECK_NULL(jm_arrayListCons);
396+
jmethodID jm_listAdd = (*env)->GetMethodID(env, jc_arrayListClass, "add", "(Ljava/lang/Object;)Z");
397+
CHECK_NULL(jm_listAdd);
398+
384399
do {
385400
searchResult = SecKeychainSearchCopyNext(keychainItemSearch, &theItem);
386401

@@ -401,12 +416,50 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
401416
goto errOut;
402417
}
403418

419+
// Only add certificates with trusted settings
420+
CFArrayRef trustSettings;
421+
if (SecTrustSettingsCopyTrustSettings(certRef, kSecTrustSettingsDomainUser, &trustSettings)
422+
== errSecItemNotFound) {
423+
continue;
424+
}
425+
426+
// See KeychainStore::createTrustedCertEntry for content of inputTrust
427+
jobject inputTrust = (*env)->NewObject(env, jc_arrayListClass, jm_arrayListCons);
428+
CHECK_NULL(inputTrust);
429+
430+
// Dump everything inside trustSettings into inputTrust
431+
CFIndex count = CFArrayGetCount(trustSettings);
432+
for (int i = 0; i < count; i++) {
433+
CFDictionaryRef oneTrust = (CFDictionaryRef) CFArrayGetValueAtIndex(trustSettings, i);
434+
CFIndex size = CFDictionaryGetCount(oneTrust);
435+
const void * keys [size];
436+
const void * values [size];
437+
CFDictionaryGetKeysAndValues(oneTrust, keys, values);
438+
for (int j = 0; j < size; j++) {
439+
NSString* s = [NSString stringWithFormat:@"%@", keys[j]];
440+
ADD(inputTrust, s);
441+
s = [NSString stringWithFormat:@"%@", values[j]];
442+
ADD(inputTrust, s);
443+
}
444+
SecPolicyRef certPolicy;
445+
certPolicy = (SecPolicyRef)CFDictionaryGetValue(oneTrust, kSecTrustSettingsPolicy);
446+
if (certPolicy != NULL) {
447+
CFDictionaryRef policyDict = SecPolicyCopyProperties(certPolicy);
448+
ADD(inputTrust, @"SecPolicyOid");
449+
NSString* s = [NSString stringWithFormat:@"%@", CFDictionaryGetValue(policyDict, @"SecPolicyOid")];
450+
ADD(inputTrust, s);
451+
CFRelease(policyDict);
452+
}
453+
ADDNULL(inputTrust);
454+
}
455+
CFRelease(trustSettings);
456+
404457
// Find the creation date.
405458
jlong creationDate = getModDateFromItem(env, theItem);
406459

407460
// Call back to the Java object to create Java objects corresponding to this security object.
408461
jlong nativeRef = ptr_to_jlong(certRef);
409-
(*env)->CallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, nativeRef, creationDate, certData);
462+
(*env)->CallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, inputTrust, nativeRef, creationDate, certData);
410463
JNU_CHECK_EXCEPTION(env);
411464
}
412465
} while (searchResult == noErr);
@@ -522,8 +575,8 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
522575
/*
523576
* Class: apple_security_KeychainStore
524577
* Method: _addItemToKeychain
525-
* Signature: (Ljava/lang/String;[B)I
526-
*/
578+
* Signature: (Ljava/lang/String;Z[B[C)J
579+
*/
527580
JNIEXPORT jlong JNICALL Java_apple_security_KeychainStore__1addItemToKeychain
528581
(JNIEnv *env, jobject this, jstring alias, jboolean isCertificate, jbyteArray rawDataObj, jcharArray passwordObj)
529582
{

src/java.base/share/classes/sun/security/tools/keytool/Main.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -2208,6 +2208,9 @@ private void doPrintEntry(String label, String alias, PrintStream out)
22082208
out.println(mf);
22092209
dumpCert(cert, out);
22102210
} else if (debug) {
2211+
for (var attr : keyStore.getEntry(alias, null).getAttributes()) {
2212+
System.out.println("Attribute " + attr.getName() + ": " + attr.getValue());
2213+
}
22112214
out.println(cert.toString());
22122215
} else {
22132216
out.println("trustedCertEntry, ");

0 commit comments

Comments
 (0)