diff --git a/jss.spec b/jss.spec index 5b9970c7e..839d271cc 100644 --- a/jss.spec +++ b/jss.spec @@ -6,7 +6,7 @@ Summary: Java Security Services (JSS) URL: http://www.dogtagpki.org/wiki/JSS License: MPLv1.1 or GPLv2+ or LGPLv2+ -Version: 4.5.2 +Version: 4.5.3 Release: 1%{?_timestamp}%{?_commit_id}%{?dist} # global _phase -a1 diff --git a/org/mozilla/jss/netscape/security/util/Cert.java b/org/mozilla/jss/netscape/security/util/Cert.java index ccbbcc086..daec5a417 100644 --- a/org/mozilla/jss/netscape/security/util/Cert.java +++ b/org/mozilla/jss/netscape/security/util/Cert.java @@ -21,15 +21,25 @@ import java.security.cert.CertificateException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang.ArrayUtils; import org.mozilla.jss.crypto.SignatureAlgorithm; - import org.mozilla.jss.netscape.security.pkcs.PKCS7; import org.mozilla.jss.netscape.security.x509.X509CRLImpl; import org.mozilla.jss.netscape.security.x509.X509CertImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Cert { + private static Logger logger = LoggerFactory.getLogger(Cert.class); + public static final String HEADER = "-----BEGIN CERTIFICATE-----"; public static final String FOOTER = "-----END CERTIFICATE-----"; @@ -204,4 +214,130 @@ public static byte[] parseCertificate(String cert) { String b64 = stripBrackets(encoded); return Utils.base64decode(b64); } + + /** + * Sorts certificate chain from root to leaf. + * + * This method sorts an array of certificates (e.g. from a PKCS #7 + * data) that represents a certificate chain from root to leaf + * according to the subject DNs and issuer DNs. + * + * The input array is a set of certificates that are part of a + * chain but not in specific order. + * + * The result is a new array that contains the certificate chain + * sorted from root to leaf. The input array is unchanged. + * + * @param certs input array of certificates + * @return new array containing sorted certificates + */ + public static java.security.cert.X509Certificate[] sortCertificateChain(java.security.cert.X509Certificate[] certs) throws Exception { + + // lookup map: subject DN -> cert + Map certMap = new LinkedHashMap<>(); + + // hierarchy map: subject DN -> issuer DN + Map parentMap = new HashMap<>(); + + // reverse hierarchy map: issuer DN -> subject DN + Map childMap = new HashMap<>(); + + // build maps + for (java.security.cert.X509Certificate cert : certs) { + + String subjectDN = cert.getSubjectDN().toString(); + String issuerDN = cert.getIssuerDN().toString(); + + if (certMap.containsKey(subjectDN)) { + throw new Exception("Duplicate certificate: " + subjectDN); + } + + certMap.put(subjectDN, cert); + + // ignore self-signed certificate + if (subjectDN.equals(issuerDN)) continue; + + if (childMap.containsKey(issuerDN)) { + throw new Exception("Branched chain: " + issuerDN); + } + + parentMap.put(subjectDN, issuerDN); + childMap.put(issuerDN, subjectDN); + } + + if (logger.isDebugEnabled()) { + logger.debug("Certificates:"); + for (String subjectDN : certMap.keySet()) { + logger.debug(" - " + subjectDN); + + String parent = parentMap.get(subjectDN); + if (parent != null) logger.debug(" parent: " + parent); + + String child = childMap.get(subjectDN); + if (child != null) logger.debug(" child: " + child); + } + } + + // find leaf cert + List leafCerts = new ArrayList<>(); + + for (String subjectDN : certMap.keySet()) { + + // if cert has a child, skip + if (childMap.containsKey(subjectDN)) continue; + + // found leaf cert + leafCerts.add(subjectDN); + } + + if (leafCerts.isEmpty()) { + throw new Exception("Unable to find leaf certificate"); + } + + if (leafCerts.size() > 1) { + StringBuilder sb = new StringBuilder(); + for (String subjectDN : leafCerts) { + if (sb.length() > 0) sb.append(", "); + sb.append("[" + subjectDN + "]"); + } + throw new Exception("Multiple leaf certificates: " + sb); + } + + // build sorted chain + LinkedList chain = new LinkedList<>(); + + // start from leaf + String current = leafCerts.get(0); + + while (current != null) { + + java.security.cert.X509Certificate cert = certMap.get(current); + + if (cert == null) { + // incomplete chain + break; + } + + // add to the beginning of chain + chain.addFirst(cert); + + // follow parent to root + current = parentMap.get(current); + } + + return chain.toArray(new java.security.cert.X509Certificate[chain.size()]); + } + + public static java.security.cert.X509Certificate[] sortCertificateChain( + java.security.cert.X509Certificate[] certs, + boolean reverse) throws Exception { + + certs = sortCertificateChain(certs); + + if (reverse) { + ArrayUtils.reverse(certs); + } + + return certs; + } }