8301154: SunPKCS11 KeyStore deleteEntry results in dangling PrivateKey entries
Reviewed-by: weijun, hchao
This commit is contained in:
parent
ed0e956fc2
commit
6b27dad76e
src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11
test/jdk/sun/security/pkcs11/KeyStore
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2023, 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
|
||||
@ -784,7 +784,7 @@ final class P11KeyStore extends KeyStoreSpi {
|
||||
writeDisabled = true;
|
||||
}
|
||||
if (debug != null) {
|
||||
dumpTokenMap();
|
||||
dumpTokenMap(debug);
|
||||
debug.println("P11KeyStore load. Entry count: " +
|
||||
aliasMap.size());
|
||||
}
|
||||
@ -866,7 +866,7 @@ final class P11KeyStore extends KeyStoreSpi {
|
||||
writeDisabled = true;
|
||||
}
|
||||
if (debug != null) {
|
||||
dumpTokenMap();
|
||||
dumpTokenMap(debug);
|
||||
}
|
||||
} catch (LoginException | KeyStoreException | PKCS11Exception e) {
|
||||
throw new IOException("load failed", e);
|
||||
@ -1152,7 +1152,7 @@ final class P11KeyStore extends KeyStoreSpi {
|
||||
|
||||
mapLabels();
|
||||
if (debug != null) {
|
||||
dumpTokenMap();
|
||||
dumpTokenMap(debug);
|
||||
}
|
||||
} catch (PKCS11Exception | CertificateException pe) {
|
||||
throw new KeyStoreException(pe);
|
||||
@ -1229,7 +1229,7 @@ final class P11KeyStore extends KeyStoreSpi {
|
||||
next.getIssuerX500Principal().getEncoded()) };
|
||||
long[] ch = findObjects(session, attrs);
|
||||
|
||||
if (ch == null || ch.length == 0) {
|
||||
if (ch.length == 0) {
|
||||
// done
|
||||
break;
|
||||
} else {
|
||||
@ -1980,91 +1980,86 @@ final class P11KeyStore extends KeyStoreSpi {
|
||||
return false;
|
||||
}
|
||||
|
||||
X509Certificate endCert = loadCert(session, h.handle);
|
||||
token.p11.C_DestroyObject(session.id(), h.handle);
|
||||
if (debug != null) {
|
||||
debug.println("destroyChain destroyed end entity cert " +
|
||||
"with CKA_ID [" +
|
||||
getIDNullSafe(cka_id) +
|
||||
"]");
|
||||
}
|
||||
|
||||
// build chain following issuer->subject links
|
||||
|
||||
X509Certificate next = endCert;
|
||||
while (true) {
|
||||
|
||||
if (next.getSubjectX500Principal().equals
|
||||
(next.getIssuerX500Principal())) {
|
||||
// self-signed - done
|
||||
break;
|
||||
}
|
||||
long currHdl = h.handle;
|
||||
boolean checkPrivKey = false;
|
||||
while (currHdl != 0L) {
|
||||
X509Certificate cert = loadCert(session, currHdl);
|
||||
boolean selfSigned = cert.getSubjectX500Principal().equals
|
||||
(cert.getIssuerX500Principal());
|
||||
|
||||
// only delete if both of the followings are true
|
||||
// 1) no other certs depend on it
|
||||
// 2) not corresponds to any private key
|
||||
CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] {
|
||||
ATTR_TOKEN_TRUE,
|
||||
ATTR_CLASS_CERT,
|
||||
new CK_ATTRIBUTE(CKA_SUBJECT,
|
||||
next.getIssuerX500Principal().getEncoded()) };
|
||||
long[] ch = findObjects(session, attrs);
|
||||
ATTR_TOKEN_TRUE,
|
||||
ATTR_CLASS_CERT,
|
||||
new CK_ATTRIBUTE(CKA_ISSUER,
|
||||
cert.getSubjectX500Principal().getEncoded())
|
||||
};
|
||||
boolean destroyIt = true;
|
||||
|
||||
if (ch == null || ch.length == 0) {
|
||||
// done
|
||||
break;
|
||||
} else {
|
||||
// if more than one found, use first
|
||||
if (debug != null && ch.length > 1) {
|
||||
debug.println("destroyChain found " +
|
||||
ch.length +
|
||||
" certificate entries for subject [" +
|
||||
next.getIssuerX500Principal() +
|
||||
"] in token - using first entry");
|
||||
}
|
||||
|
||||
next = loadCert(session, ch[0]);
|
||||
|
||||
// only delete if not part of any other chain
|
||||
long[] dependents = findObjects(session, attrs);
|
||||
if (dependents.length > 1 ||
|
||||
(!selfSigned && dependents.length == 1)) {
|
||||
destroyIt = false;
|
||||
}
|
||||
|
||||
if (destroyIt && checkPrivKey) {
|
||||
// proceed with checking if there is a private key
|
||||
attrs = new CK_ATTRIBUTE[] {
|
||||
ATTR_TOKEN_TRUE,
|
||||
ATTR_CLASS_CERT,
|
||||
new CK_ATTRIBUTE(CKA_ISSUER,
|
||||
next.getSubjectX500Principal().getEncoded()) };
|
||||
long[] issuers = findObjects(session, attrs);
|
||||
|
||||
boolean destroyIt = false;
|
||||
if (issuers == null || issuers.length == 0) {
|
||||
// no other certs with this issuer -
|
||||
// destroy it
|
||||
destroyIt = true;
|
||||
} else if (issuers.length == 1) {
|
||||
X509Certificate iCert = loadCert(session, issuers[0]);
|
||||
if (next.equals(iCert)) {
|
||||
// only cert with issuer is itself (self-signed) -
|
||||
// destroy it
|
||||
destroyIt = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (destroyIt) {
|
||||
token.p11.C_DestroyObject(session.id(), ch[0]);
|
||||
if (debug != null) {
|
||||
debug.println
|
||||
("destroyChain destroyed cert in chain " +
|
||||
"with subject [" +
|
||||
next.getSubjectX500Principal() + "]");
|
||||
}
|
||||
} else {
|
||||
if (debug != null) {
|
||||
debug.println("destroyChain did not destroy " +
|
||||
"shared cert in chain with subject [" +
|
||||
next.getSubjectX500Principal() + "]");
|
||||
}
|
||||
new CK_ATTRIBUTE(CKA_ID),
|
||||
};
|
||||
token.p11.C_GetAttributeValue(session.id(), currHdl, attrs);
|
||||
byte[] currId = attrs[0].getByteArray();
|
||||
if (currId != null) {
|
||||
attrs = new CK_ATTRIBUTE[] {
|
||||
ATTR_TOKEN_TRUE,
|
||||
ATTR_CLASS_PKEY,
|
||||
new CK_ATTRIBUTE(CKA_ID, currId)
|
||||
};
|
||||
long[] privKeys = findObjects(session, attrs);
|
||||
destroyIt = privKeys.length == 0;
|
||||
}
|
||||
}
|
||||
if (destroyIt) {
|
||||
token.p11.C_DestroyObject(session.id(), currHdl);
|
||||
if (debug != null) {
|
||||
debug.println("destroyChain destroyed cert in chain " +
|
||||
"with subject [" +
|
||||
cert.getSubjectX500Principal() + "]");
|
||||
}
|
||||
} else {
|
||||
if (debug != null) {
|
||||
debug.println("destroyChain did not destroy " +
|
||||
"shared cert in chain with subject [" +
|
||||
cert.getSubjectX500Principal() + "]");
|
||||
}
|
||||
}
|
||||
if (selfSigned) {
|
||||
break; // done
|
||||
}
|
||||
attrs = new CK_ATTRIBUTE[] {
|
||||
ATTR_TOKEN_TRUE,
|
||||
ATTR_CLASS_CERT,
|
||||
new CK_ATTRIBUTE(CKA_SUBJECT,
|
||||
cert.getIssuerX500Principal().getEncoded())
|
||||
};
|
||||
long[] ch = findObjects(session, attrs);
|
||||
if (ch.length == 0) {
|
||||
break;
|
||||
}
|
||||
// if more than one found, use first
|
||||
if (debug != null && ch.length > 1) {
|
||||
debug.println("destroyChain found " +
|
||||
ch.length +
|
||||
" certificate entries for subject [" +
|
||||
cert.getIssuerX500Principal() +
|
||||
"] in token - using first entry");
|
||||
}
|
||||
currHdl = ch[0];
|
||||
checkPrivKey = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} finally {
|
||||
token.releaseSession(session);
|
||||
}
|
||||
@ -2646,14 +2641,14 @@ final class P11KeyStore extends KeyStoreSpi {
|
||||
aliasMap.putAll(sKeyMap);
|
||||
}
|
||||
|
||||
private void dumpTokenMap() {
|
||||
private void dumpTokenMap(Debug debug) {
|
||||
Set<String> aliases = aliasMap.keySet();
|
||||
System.out.println("Token Alias Map:");
|
||||
debug.println("Token Alias Map:");
|
||||
if (aliases.isEmpty()) {
|
||||
System.out.println(" [empty]");
|
||||
debug.println(" [empty]");
|
||||
} else {
|
||||
for (String s : aliases) {
|
||||
System.out.println(" " + s + aliasMap.get(s));
|
||||
debug.println(" " + s + aliasMap.get(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2667,6 +2662,7 @@ final class P11KeyStore extends KeyStoreSpi {
|
||||
|
||||
private static final long[] LONG0 = new long[0];
|
||||
|
||||
// return an empty array if no match
|
||||
private static long[] findObjects(Session session, CK_ATTRIBUTE[] attrs)
|
||||
throws PKCS11Exception {
|
||||
Token token = session.token;
|
||||
|
231
test/jdk/sun/security/pkcs11/KeyStore/CertChainRemoval.java
Normal file
231
test/jdk/sun/security/pkcs11/KeyStore/CertChainRemoval.java
Normal file
@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @bug 8301154
|
||||
* @summary test cert chain deletion logic w/ NSS PKCS11 KeyStore
|
||||
* @library /test/lib ..
|
||||
* @run testng/othervm CertChainRemoval
|
||||
*/
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.Provider;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
||||
public class CertChainRemoval extends PKCS11Test {
|
||||
|
||||
private static final Path TEST_DATA_PATH = Path.of(BASE)
|
||||
.resolve("CertChainRemoval");
|
||||
private static final String DIR = TEST_DATA_PATH.toString();
|
||||
|
||||
private record KeyStoreInfo(File file, String type, char[] passwd) {}
|
||||
|
||||
private static final KeyStoreInfo TEMP = new KeyStoreInfo(
|
||||
new File(DIR, "temp.ks"),
|
||||
"JKS",
|
||||
new char[] { 'c', 'h', 'a', 'n', 'g', 'e', 'i', 't' });
|
||||
|
||||
private static final KeyStoreInfo PKCS11KS = new KeyStoreInfo(
|
||||
null,
|
||||
"PKCS11",
|
||||
new char[] { 't', 'e', 's', 't', '1', '2' });
|
||||
|
||||
@BeforeClass
|
||||
public void setUp() throws Exception {
|
||||
copyNssCertKeyToClassesDir();
|
||||
setCommonSystemProps();
|
||||
// if temp keystore already exists; skip the creation
|
||||
if (!TEMP.file.exists()) {
|
||||
createKeyStore(TEMP);
|
||||
}
|
||||
System.setProperty("CUSTOM_P11_CONFIG",
|
||||
TEST_DATA_PATH.resolve("p11-nss.txt").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
main(new CertChainRemoval());
|
||||
}
|
||||
|
||||
private static void printKeyStore(String header, KeyStore ks)
|
||||
throws KeyStoreException {
|
||||
System.out.println(header);
|
||||
Enumeration enu = ks.aliases();
|
||||
int count = 0;
|
||||
while (enu.hasMoreElements()) {
|
||||
count++;
|
||||
System.out.println("Entry# " + count +
|
||||
" = " + (String)enu.nextElement());
|
||||
}
|
||||
System.out.println("========");
|
||||
}
|
||||
|
||||
private static void checkEntry(KeyStore ks, String alias,
|
||||
Certificate[] expChain) throws KeyStoreException {
|
||||
Certificate c = ks.getCertificate(alias);
|
||||
Certificate[] chain = ks.getCertificateChain(alias);
|
||||
if (expChain == null) {
|
||||
if (c != null || (chain != null && chain.length != 0)) {
|
||||
throw new RuntimeException("Fail: " + alias + " not removed");
|
||||
}
|
||||
} else {
|
||||
if (!c.equals(expChain[0]) || !Arrays.equals(chain, expChain)) {
|
||||
System.out.println("expChain: " + expChain.length);
|
||||
System.out.println("actualChain: " + chain.length);
|
||||
throw new RuntimeException("Fail: " + alias +
|
||||
" chain check diff");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void main(Provider p) throws Exception {
|
||||
KeyStore sunks = KeyStore.getInstance(TEMP.type, "SUN");
|
||||
sunks.load(new FileInputStream(TEMP.file), TEMP.passwd);
|
||||
printKeyStore("Starting with: ", sunks);
|
||||
|
||||
KeyStore p11ks;
|
||||
try {
|
||||
p11ks = KeyStore.getInstance(PKCS11KS.type, p);
|
||||
p11ks.load(null, PKCS11KS.passwd);
|
||||
printKeyStore("Initial PKCS11 KeyStore: ", p11ks);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Skip test, due to " + e);
|
||||
return;
|
||||
}
|
||||
|
||||
// get the necessary keys from the temp keystore
|
||||
Key pk1PrivKey = sunks.getKey("pk1", TEMP.passwd);
|
||||
Certificate pk1Cert = sunks.getCertificate("pk1");
|
||||
Key caPrivKey = sunks.getKey("ca1", TEMP.passwd);
|
||||
Certificate ca1Cert = sunks.getCertificate("ca1");
|
||||
Key rootPrivKey = sunks.getKey("root", TEMP.passwd);
|
||||
Certificate rootCert = sunks.getCertificate("root");
|
||||
|
||||
Certificate[] pk1Chain = { pk1Cert, ca1Cert, rootCert };
|
||||
Certificate[] ca1Chain = { ca1Cert, rootCert };
|
||||
Certificate[] rootChain = { rootCert };
|
||||
|
||||
// populate keystore with "pk1" and "ca", then delete "pk1"
|
||||
System.out.println("Add pk1, ca1 and root, then delete pk1");
|
||||
p11ks.setKeyEntry("pk1", pk1PrivKey, null, pk1Chain);
|
||||
p11ks.setKeyEntry("ca1", caPrivKey, null, ca1Chain);
|
||||
p11ks.setKeyEntry("root", rootPrivKey, null, rootChain);
|
||||
p11ks.deleteEntry("pk1");
|
||||
|
||||
// reload the keystore
|
||||
p11ks.store(null, PKCS11KS.passwd);
|
||||
p11ks.load(null, PKCS11KS.passwd);
|
||||
printKeyStore("Reload#1: ca1 / root", p11ks);
|
||||
|
||||
// should only have "ca1" and "root"
|
||||
checkEntry(p11ks, "pk1", null);
|
||||
checkEntry(p11ks, "ca1", ca1Chain);
|
||||
checkEntry(p11ks, "root", rootChain);
|
||||
|
||||
// now add "pk1" and delete "ca1"
|
||||
System.out.println("Now add pk1 and delete ca1");
|
||||
p11ks.setKeyEntry("pk1", pk1PrivKey, null, pk1Chain);
|
||||
p11ks.deleteEntry("ca1");
|
||||
|
||||
// reload the keystore
|
||||
p11ks.store(null, PKCS11KS.passwd);
|
||||
p11ks.load(null, PKCS11KS.passwd);
|
||||
printKeyStore("Reload#2: pk1 / root", p11ks);
|
||||
|
||||
// should only have "pk1" and "root" now
|
||||
checkEntry(p11ks, "pk1", pk1Chain);
|
||||
checkEntry(p11ks, "ca1", null);
|
||||
checkEntry(p11ks, "root", rootChain);
|
||||
|
||||
// now delete "root"
|
||||
System.out.println("Now delete root");
|
||||
p11ks.deleteEntry("root");
|
||||
|
||||
// reload the keystore
|
||||
p11ks.store(null, PKCS11KS.passwd);
|
||||
p11ks.load(null, PKCS11KS.passwd);
|
||||
printKeyStore("Reload#3: pk1", p11ks);
|
||||
|
||||
// should only have "pk1" now
|
||||
checkEntry(p11ks, "pk1", pk1Chain);
|
||||
checkEntry(p11ks, "ca1", null);
|
||||
checkEntry(p11ks, "root", null);
|
||||
|
||||
// now delete "pk1"
|
||||
System.out.println("Now delete pk1");
|
||||
p11ks.deleteEntry("pk1");
|
||||
|
||||
// reload the keystore
|
||||
p11ks.store(null, PKCS11KS.passwd);
|
||||
p11ks.load(null, PKCS11KS.passwd);
|
||||
printKeyStore("Reload#4: ", p11ks);
|
||||
|
||||
// should have nothing now
|
||||
checkEntry(p11ks, "pk1", null);
|
||||
checkEntry(p11ks, "ca1", null);
|
||||
checkEntry(p11ks, "root", null);
|
||||
|
||||
System.out.println("Test Passed");
|
||||
}
|
||||
|
||||
private static void createKeyStore(KeyStoreInfo ksi) throws Exception {
|
||||
System.out.println("Creating keypairs and storing them into " +
|
||||
ksi.file.getAbsolutePath());
|
||||
String keyGenOptions = " -keyalg RSA -keysize 2048 ";
|
||||
String keyStoreOptions = " -keystore " + ksi.file.getAbsolutePath() +
|
||||
" -storetype " + ksi.type + " -storepass " +
|
||||
new String(ksi.passwd);
|
||||
|
||||
String[] aliases = { "ROOT", "CA1", "PK1" };
|
||||
for (String n : aliases) {
|
||||
SecurityTools.keytool("-genkeypair -alias " + n +
|
||||
" -dname CN=" + n + keyGenOptions + keyStoreOptions);
|
||||
String issuer = switch (n) {
|
||||
case "CA1"-> "ROOT";
|
||||
case "PK1"-> "CA1";
|
||||
default-> null;
|
||||
};
|
||||
if (issuer != null) {
|
||||
// export CSR and issue the cert using the issuer
|
||||
SecurityTools.keytool("-certreq -alias " + n +
|
||||
" -file tmp.req" + keyStoreOptions);
|
||||
SecurityTools.keytool("-gencert -alias " + issuer +
|
||||
" -infile tmp.req -outfile tmp.cert -validity 3650" +
|
||||
keyStoreOptions);
|
||||
SecurityTools.keytool("-importcert -alias " + n +
|
||||
" -file tmp.cert" + keyStoreOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
name = nss
|
||||
|
||||
slot = 2
|
||||
|
||||
library = ${pkcs11test.nss.lib}
|
||||
|
||||
nssArgs = "configdir='${pkcs11test.nss.db}' certPrefix='' keyPrefix='' secmod='secmod.db'"
|
||||
|
||||
attributes = compatibility
|
BIN
test/jdk/sun/security/pkcs11/KeyStore/CertChainRemoval/temp.ks
Normal file
BIN
test/jdk/sun/security/pkcs11/KeyStore/CertChainRemoval/temp.ks
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user