Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8270946: X509CertImpl.getFingerprint should not return the empty String #206

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -70,8 +70,14 @@ public Void run() {
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 Void run() {
* @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());
@@ -28,7 +28,6 @@
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 static boolean isUntrusted(X509Certificate cert) {
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() {}
@@ -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.Map;
import java.util.Set;

import sun.security.util.Debug;
import sun.security.x509.X509CertImpl;

/**
@@ -40,6 +41,8 @@
*/
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 @@
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 @@ static void checkDistrust(X509Certificate[] chain)
}

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,
@@ -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 static boolean isSelfSigned(X509Certificate cert,
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 "";
}
}
@@ -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();
}
}
}
@@ -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.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.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 @@ private static String readFile(String relativeFilePath,
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(); }
}
}