mirror of
https://github.com/openjdk/jdk7u.git
synced 2025-12-10 20:36:33 -06:00
8278449: Improve keychain support
Reviewed-by: andrew Backport-of: 2376bb88eff3ae6922c4cae276e1d703a520853d
This commit is contained in:
parent
8ee045e6ae
commit
3e94375f1f
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 2020, 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
|
||||
@ -36,8 +36,11 @@ import javax.crypto.*;
|
||||
import javax.crypto.spec.*;
|
||||
import javax.security.auth.x500.*;
|
||||
|
||||
import sun.misc.JavaSecurityKeyStoreAccess;
|
||||
import sun.misc.SharedSecrets;
|
||||
import sun.security.pkcs.*;
|
||||
import sun.security.pkcs.EncryptedPrivateKeyInfo;
|
||||
import sun.security.pkcs12.PKCS12Attribute;
|
||||
import sun.security.util.*;
|
||||
import sun.security.x509.*;
|
||||
|
||||
@ -68,6 +71,17 @@ public final class KeychainStore extends KeyStoreSpi {
|
||||
|
||||
Certificate cert;
|
||||
long certRef; // SecCertificateRef for this key
|
||||
|
||||
// Each KeyStore.TrustedCertificateEntry has
|
||||
// "2.16.840.1.113894.746875.1.1" -> trustedKeyUsageValue
|
||||
// attribute similar to the attribute with the same key in
|
||||
// a PKCS12KeyStore.
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -102,6 +116,9 @@ public final class KeychainStore extends KeyStoreSpi {
|
||||
private static final int iterationCount = 1024;
|
||||
private static final int SALT_LEN = 20;
|
||||
|
||||
private static final String OID_EKU_ANY_USAGE = "2.5.29.37.0";
|
||||
private static final String OID_EKU_TRUSTED_USAGE = "2.16.840.1.113894.746875.1.1";
|
||||
|
||||
static {
|
||||
java.security.AccessController.doPrivileged((PrivilegedAction<?>)new sun.security.action.LoadLibraryAction("osx"));
|
||||
try {
|
||||
@ -287,6 +304,24 @@ public final class KeychainStore extends KeyStoreSpi {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore.Entry engineGetEntry(String alias, KeyStore.ProtectionParameter protParam)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
|
||||
if (engineIsCertificateEntry(alias)) {
|
||||
Object entry = entries.get(alias.toLowerCase());
|
||||
JavaSecurityKeyStoreAccess jsksa = SharedSecrets.getJavaSecurityKeyStoreAccess();
|
||||
if (entry instanceof TrustedCertEntry) {
|
||||
TrustedCertEntry tEntry = (TrustedCertEntry)entry;
|
||||
Set<PKCS12Attribute> attrs = new HashSet<PKCS12Attribute>();
|
||||
attrs.add(new PKCS12Attribute(OID_EKU_TRUSTED_USAGE, tEntry.trustedKeyUsageValue));
|
||||
|
||||
return jsksa.constructTrustedCertificateEntry(
|
||||
tEntry.cert, attrs);
|
||||
}
|
||||
}
|
||||
return super.engineGetEntry(alias, protParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the creation date of the entry identified by the given alias.
|
||||
*
|
||||
@ -440,55 +475,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 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.");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -665,10 +657,7 @@ public final class KeychainStore extends KeyStoreSpi {
|
||||
String alias = (String)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;
|
||||
|
||||
@ -759,9 +748,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 {
|
||||
@ -772,6 +780,71 @@ public final class KeychainStore extends KeyStoreSpi {
|
||||
tce.cert = cert;
|
||||
tce.certRef = keychainItemRef;
|
||||
|
||||
List<Map<String, String>> trustSettings = new ArrayList<>();
|
||||
Map<String,String> tmpMap = new LinkedHashMap<>();
|
||||
for (int i = 0; i < inputTrust.size(); i++) {
|
||||
if (inputTrust.get(i) == null) {
|
||||
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 (trustSettings.isEmpty()) {
|
||||
if (isSelfSigned) {
|
||||
// If a self-signed certificate has an empty trust settings,
|
||||
// trust it for all purposes
|
||||
tce.trustedKeyUsageValue = OID_EKU_ANY_USAGE;
|
||||
} else {
|
||||
// Otherwise, return immediately. The certificate is not
|
||||
// added into entries.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
List<String> values = new ArrayList<>();
|
||||
for (Map<String, String> oneTrust : trustSettings) {
|
||||
String 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.get("SecPolicyOid");
|
||||
if (oid == null && !oneTrust.containsKey("SecPolicyOid")) {
|
||||
oid = OID_EKU_ANY_USAGE;
|
||||
}
|
||||
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);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 2017, 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
|
||||
@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
#import "apple_security_KeychainStore.h"
|
||||
#import "jni_util.h"
|
||||
|
||||
#import <Security/Security.h>
|
||||
#import <Security/SecImportExport.h>
|
||||
@ -32,7 +33,7 @@
|
||||
|
||||
|
||||
static JNF_CLASS_CACHE(jc_KeychainStore, "apple/security/KeychainStore");
|
||||
static JNF_MEMBER_CACHE(jm_createTrustedCertEntry, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;JJ[B)V");
|
||||
static JNF_MEMBER_CACHE(jm_createTrustedCertEntry, jc_KeychainStore, "createTrustedCertEntry", "(Ljava/lang/String;Ljava/util/List;JJ[B)V");
|
||||
static JNF_MEMBER_CACHE(jm_createKeyEntry, jc_KeychainStore, "createKeyEntry", "(Ljava/lang/String;JJ[J[[B)V");
|
||||
|
||||
static jstring getLabelFromItem(JNIEnv *env, SecKeychainItemRef inItem)
|
||||
@ -362,6 +363,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.
|
||||
@ -370,10 +379,18 @@ static void addCertificatesToKeystore(JNIEnv *env, jobject keyStore)
|
||||
SecKeychainItemRef theItem = NULL;
|
||||
OSErr searchResult = noErr;
|
||||
|
||||
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);
|
||||
|
||||
if (searchResult == noErr) {
|
||||
CFIndex i, j;
|
||||
// Make a byte array with the DER-encoded contents of the certificate.
|
||||
SecCertificateRef certRef = (SecCertificateRef)theItem;
|
||||
CSSM_DATA currCertificate;
|
||||
@ -390,12 +407,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 (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 (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);
|
||||
JNFCallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, nativeRef, creationDate, certData);
|
||||
JNFCallVoidMethod(env, keyStore, jm_createTrustedCertEntry, alias, inputTrust, nativeRef, creationDate, certData);
|
||||
}
|
||||
} while (searchResult == noErr);
|
||||
|
||||
@ -494,8 +549,8 @@ JNIEXPORT void JNICALL Java_apple_security_KeychainStore__1scanKeychain
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2017, 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
|
||||
@ -71,6 +71,9 @@ import java.security.cert.X509CRLSelector;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import sun.misc.BASE64Encoder;
|
||||
import sun.misc.JavaSecurityKeyStoreAccess;
|
||||
import sun.misc.SharedSecrets;
|
||||
import sun.security.pkcs12.PKCS12Attribute;
|
||||
import sun.security.util.DisabledAlgorithmConstraints;
|
||||
import sun.security.util.KeyUtil;
|
||||
import sun.security.util.NamedCurve;
|
||||
@ -1856,6 +1859,13 @@ public final class Main {
|
||||
out.println(mf);
|
||||
dumpCert(cert, out);
|
||||
} else if (debug) {
|
||||
Entry entry = keyStore.getEntry(alias, null);
|
||||
JavaSecurityKeyStoreAccess jsksa = SharedSecrets.getJavaSecurityKeyStoreAccess();
|
||||
Set<PKCS12Attribute> attrs =
|
||||
jsksa.getTrustedCertificateEntryAttributes((TrustedCertificateEntry)entry);
|
||||
for (PKCS12Attribute attr : attrs) {
|
||||
System.out.println("Attribute " + attr.getName() + ": " + attr.getValue());
|
||||
}
|
||||
out.println(cert.toString());
|
||||
} else {
|
||||
out.println("trustedCertEntry, ");
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
|
||||
package jdk.testlibrary;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@ -136,4 +137,62 @@ public class SecurityTools {
|
||||
builder.append(suffix);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user