8270946: X509CertImpl.getFingerprint should not return the empty String

Reviewed-by: weijun
This commit is contained in:
Sean Mullan 2021-07-27 13:49:03 +00:00
parent 45d277feb0
commit fc80a6b493
6 changed files with 216 additions and 36 deletions

View File

@ -70,8 +70,14 @@ public class AnchorCertificates {
if (alias.contains(" [jdk")) {
X509Certificate cert = (X509Certificate) cacerts
.getCertificate(alias);
certs.add(X509CertImpl.getFingerprint(HASH, cert));
certIssuers.add(cert.getSubjectX500Principal());
String fp =
X509CertImpl.getFingerprint(HASH, cert, debug);
// only add trust anchor if fingerprint can
// be calculated
if (fp != null) {
certs.add(fp);
certIssuers.add(cert.getSubjectX500Principal());
}
}
}
}
@ -93,8 +99,8 @@ public class AnchorCertificates {
* @return true if the certificate is a JDK trust anchor
*/
public static boolean contains(X509Certificate cert) {
String key = X509CertImpl.getFingerprint(HASH, cert);
boolean result = certs.contains(key);
String key = X509CertImpl.getFingerprint(HASH, cert, debug);
boolean result = (key == null ? false : certs.contains(key));
if (result && debug != null) {
debug.println("AnchorCertificate.contains: matched " +
cert.getSubjectX500Principal());

View File

@ -28,7 +28,6 @@ import java.io.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import java.util.Properties;
import jdk.internal.util.StaticProperty;
@ -80,17 +79,9 @@ public final class UntrustedCertificates {
if (algorithm == null) {
return false;
}
String key;
if (cert instanceof X509CertImpl) {
key = ((X509CertImpl)cert).getFingerprint(algorithm);
} else {
try {
key = new X509CertImpl(cert.getEncoded()).getFingerprint(algorithm);
} catch (CertificateException cee) {
return false;
}
}
return props.containsKey(key);
// if fingerprint cannot be calculated, also treat it as untrusted
String key = X509CertImpl.getFingerprint(algorithm, cert, debug);
return (key == null || props.containsKey(key));
}
private UntrustedCertificates() {}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2021, 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
@ -32,6 +32,7 @@ import java.util.Date;
import java.util.Map;
import java.util.Set;
import sun.security.util.Debug;
import sun.security.x509.X509CertImpl;
/**
@ -40,6 +41,8 @@ import sun.security.x509.X509CertImpl;
*/
final class SymantecTLSPolicy {
private static final Debug debug = Debug.getInstance("certpath");
// SHA-256 certificate fingerprints of distrusted roots
private static final Set<String> FINGERPRINTS = Set.of(
// cacerts alias: geotrustglobalca
@ -154,14 +157,24 @@ final class SymantecTLSPolicy {
static void checkDistrust(X509Certificate[] chain)
throws ValidatorException {
X509Certificate anchor = chain[chain.length-1];
if (FINGERPRINTS.contains(fingerprint(anchor))) {
String fp = fingerprint(anchor);
if (fp == null) {
throw new ValidatorException("Cannot generate fingerprint for "
+ "trust anchor of TLS server certificate");
}
if (FINGERPRINTS.contains(fp)) {
Date notBefore = chain[0].getNotBefore();
LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(),
ZoneOffset.UTC);
// check if chain goes through one of the subCAs
if (chain.length > 2) {
X509Certificate subCA = chain[chain.length-2];
LocalDate distrustDate = EXEMPT_SUBCAS.get(fingerprint(subCA));
fp = fingerprint(subCA);
if (fp == null) {
throw new ValidatorException("Cannot generate fingerprint "
+ "for intermediate CA of TLS server certificate");
}
LocalDate distrustDate = EXEMPT_SUBCAS.get(fp);
if (distrustDate != null) {
// reject if certificate is issued after specified date
checkNotBefore(ldNotBefore, distrustDate, anchor);
@ -174,9 +187,7 @@ final class SymantecTLSPolicy {
}
private static String fingerprint(X509Certificate cert) {
return (cert instanceof X509CertImpl)
? ((X509CertImpl)cert).getFingerprint("SHA-256")
: X509CertImpl.getFingerprint("SHA-256", cert);
return X509CertImpl.getFingerprint("SHA-256", cert, debug);
}
private static void checkNotBefore(LocalDate notBeforeDate,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2021, 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
@ -1917,25 +1917,57 @@ public class X509CertImpl extends X509Certificate implements DerEncoder {
private ConcurrentHashMap<String,String> fingerprints =
new ConcurrentHashMap<>(2);
public String getFingerprint(String algorithm) {
private String getFingerprint(String algorithm, Debug debug) {
return fingerprints.computeIfAbsent(algorithm,
x -> getFingerprint(x, this));
x -> {
try {
return getFingerprintInternal(x, getEncodedInternal(), debug);
} catch (CertificateEncodingException e) {
if (debug != null) {
debug.println("Cannot encode certificate: " + e);
}
return null;
}
});
}
private static String getFingerprintInternal(String algorithm,
byte[] encodedCert, Debug debug) {
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] digest = md.digest(encodedCert);
return HexFormat.of().withUpperCase().formatHex(digest);
} catch (NoSuchAlgorithmException e) {
if (debug != null) {
debug.println("Cannot create " + algorithm
+ " MessageDigest: " + e);
}
return null;
}
}
/**
* Gets the requested finger print of the certificate. The result
* Gets the requested fingerprint of the certificate. The result
* only contains 0-9 and A-F. No small case, no colon.
*
* @param algorithm the MessageDigest algorithm
* @param cert the X509Certificate
* @return the fingerprint, or null if it cannot be calculated because
* of an exception
*/
public static String getFingerprint(String algorithm,
X509Certificate cert) {
try {
byte[] encCertInfo = cert.getEncoded();
MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] digest = md.digest(encCertInfo);
return HexFormat.of().withUpperCase().formatHex(digest);
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
// ignored
X509Certificate cert, Debug debug) {
if (cert instanceof X509CertImpl) {
return ((X509CertImpl)cert).getFingerprint(algorithm, debug);
} else {
try {
return getFingerprintInternal(algorithm, cert.getEncoded(), debug);
} catch (CertificateEncodingException e) {
if (debug != null) {
debug.println("Cannot encode certificate: " + e);
}
return null;
}
}
return "";
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2021, 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 8270946
* @library /test/lib
* @modules java.base/sun.security.x509
* java.base/sun.security.util
* @summary Check that X509CertImpl.getFingerprint does not return null when
* there are errors calculating the fingerprint
*/
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import sun.security.x509.X509CertImpl;
import sun.security.util.Debug;
import jdk.test.lib.Asserts;
import jdk.test.lib.security.CertUtils;
public class GetFingerprintError {
private static final Debug dbg = Debug.getInstance("certpath");
public static void main(String[] args) throws Exception {
X509Certificate cert = CertUtils.getCertFromString(CertUtils.RSA_CERT);
// test invalid MessageDigest algorithm
Asserts.assertNull(X509CertImpl.getFingerprint("NoSuchAlg", cert, dbg));
// test cert with bad encoding
X509Certificate fcert = new X509CertificateWithBadEncoding(cert);
Asserts.assertNull(X509CertImpl.getFingerprint("SHA-256", fcert, dbg));
}
private static class X509CertificateWithBadEncoding
extends CertUtils.ForwardingX509Certificate {
private X509CertificateWithBadEncoding(X509Certificate cert) {
super(cert);
}
@Override
public byte[] getEncoded() throws CertificateEncodingException {
throw new CertificateEncodingException();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2021, 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
@ -35,19 +35,28 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CRLException;
import java.security.cert.CertPath;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathValidator;
import java.security.cert.CertStore;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathBuilderResult;
@ -59,6 +68,7 @@ import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -630,4 +640,67 @@ public class CertUtils {
throw new RuntimeException("Cannot read file", e);
}
}
/**
* This class is useful for overriding one or more methods of an
* X509Certificate for testing purposes.
*/
public static class ForwardingX509Certificate extends X509Certificate {
private final X509Certificate cert;
public ForwardingX509Certificate(X509Certificate cert) {
this.cert = cert;
}
public Set<String> getCriticalExtensionOIDs() {
return cert.getCriticalExtensionOIDs();
}
public byte[] getExtensionValue(String oid) {
return cert.getExtensionValue(oid);
}
public Set<String> getNonCriticalExtensionOIDs() {
return cert.getNonCriticalExtensionOIDs();
}
public boolean hasUnsupportedCriticalExtension() {
return cert.hasUnsupportedCriticalExtension();
}
public void checkValidity() throws CertificateExpiredException,
CertificateNotYetValidException { /* always pass */ }
public void checkValidity(Date date) throws CertificateExpiredException,
CertificateNotYetValidException { /* always pass */ }
public int getVersion() { return cert.getVersion(); }
public BigInteger getSerialNumber() { return cert.getSerialNumber(); }
public Principal getIssuerDN() { return cert.getIssuerDN(); }
public Principal getSubjectDN() { return cert.getSubjectDN(); }
public Date getNotBefore() { return cert.getNotBefore(); }
public Date getNotAfter() { return cert.getNotAfter(); }
public byte[] getTBSCertificate() throws CertificateEncodingException {
return cert.getTBSCertificate();
}
public byte[] getSignature() { return cert.getSignature(); }
public String getSigAlgName() { return cert.getSigAlgName(); }
public String getSigAlgOID() { return cert.getSigAlgOID(); }
public byte[] getSigAlgParams() { return cert.getSigAlgParams(); }
public boolean[] getIssuerUniqueID() {
return cert.getIssuerUniqueID();
}
public boolean[] getSubjectUniqueID() {
return cert.getSubjectUniqueID();
}
public boolean[] getKeyUsage() { return cert.getKeyUsage(); }
public int getBasicConstraints() { return cert.getBasicConstraints(); }
public byte[] getEncoded() throws CertificateEncodingException {
return cert.getEncoded();
}
public void verify(PublicKey key) throws CertificateException,
InvalidKeyException, NoSuchAlgorithmException,
NoSuchProviderException, SignatureException {
cert.verify(key);
}
public void verify(PublicKey key, String sigProvider) throws
CertificateException, InvalidKeyException, NoSuchAlgorithmException,
NoSuchProviderException, SignatureException {
cert.verify(key, sigProvider);
}
public PublicKey getPublicKey() { return cert.getPublicKey(); }
public String toString() { return cert.toString(); }
}
}