diff --git a/framework/bundles/org.eclipse.ecf.ssl/src/org/eclipse/ecf/internal/ssl/CertificateChainSorter.java b/framework/bundles/org.eclipse.ecf.ssl/src/org/eclipse/ecf/internal/ssl/CertificateChainSorter.java new file mode 100644 index 000000000..4b4fd1fa7 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.ssl/src/org/eclipse/ecf/internal/ssl/CertificateChainSorter.java @@ -0,0 +1,101 @@ +package org.eclipse.ecf.internal.ssl; + +import java.security.Principal; +import java.security.cert.X509Certificate; +import java.util.*; + +/** + * Simple implementation to sort a list of certificates and get a certificate + * chain. Some servers send the certificates unordered. Until TLSv1.2 this was + * not allowed. TLSv1.3 allows it (see RFC 8446 section 4.4.2). + * + * This is not at all fully complaint with the RFC 8446 as it does not + * (currently) handles some cases like multiple chain paths or certificates with + * the same CN (Common Name) that are in a specific order (see + * {@link org.eclipse.ecf.tests.ssl.ECFTrustManagerTest#secondCertSharingCNWithIntermediateCA_AfterProperOne()} + */ +public class CertificateChainSorter { + + /** + * Sorts the certificates to obtain the certificate chain in the proper + * order. Certificates that are not part of the chain are not considered + * (i.e. not included in the result). + * + * @param certificates The original list of certificates in the order + * provided by the server. + * + * @return a new X509Certificate array with the certificate chain in proper + * order. If no certificates are provided or just one, then it returns the + * same array provided. + */ + public static X509Certificate[] sortCertificates(X509Certificate[] certificates) { + if (certificates == null || certificates.length <= 1) { + return certificates; + } + + Map subjects = createIndexBySubject(certificates); + + List sortedChain = buildSortedChain(certificates[0], subjects); + + return sortedChain.toArray(new X509Certificate[] {}); + } + + /** + * Builds an index from the provided certificates using the Subject DN as + * the key. + * + * @param certificates The original certificate list provided by the server. + * + * @return a new map where the Subject DN is the key and the certificate the + * value. + */ + private static Map createIndexBySubject(X509Certificate[] certificates) { + Map subjects = new HashMap(); + + for (int i = 0; i < certificates.length; i++) { + X509Certificate currentCert = certificates[i]; + + subjects.put(currentCert.getSubjectDN(), currentCert); + } + + return subjects; + } + + /** + * Builds a certificate chain from the ones provided by the server starting + * with the first certificate from the original list (which should be the + * end-entity certificate per RFC 8446 section 4.4.2). + * + * @param endEntityCertificate The end-entity certificate which MUST be the + * first one of the provided by the server. + * @param subjects An SubjectDN based index of the certificates provided by + * the server. + * + * @return A new list with the certificate chain calculated. + */ + private static List buildSortedChain(X509Certificate endEntityCertificate, + Map subjects) { + List sortedChain = new ArrayList(); + + X509Certificate currentCert = endEntityCertificate; + X509Certificate issuer; + + sortedChain.add(currentCert); + + do { + issuer = subjects.get(currentCert.getIssuerDN()); + + if (issuer != null) { + sortedChain.add(issuer); + currentCert = issuer; + } + } while (issuer != null && !isRootCertificate(issuer)); + + return sortedChain; + } + + private static boolean isRootCertificate(X509Certificate certificate) { + return certificate != null && certificate.getSubjectDN().equals(certificate.getIssuerDN()); + } + +} \ No newline at end of file diff --git a/framework/bundles/org.eclipse.ecf.ssl/src/org/eclipse/ecf/internal/ssl/ECFTrustManager.java b/framework/bundles/org.eclipse.ecf.ssl/src/org/eclipse/ecf/internal/ssl/ECFTrustManager.java index 2ff79e50d..0bff7b819 100644 --- a/framework/bundles/org.eclipse.ecf.ssl/src/org/eclipse/ecf/internal/ssl/ECFTrustManager.java +++ b/framework/bundles/org.eclipse.ecf.ssl/src/org/eclipse/ecf/internal/ssl/ECFTrustManager.java @@ -21,6 +21,8 @@ public class ECFTrustManager implements X509TrustManager, BundleActivator { + public static final String SORT_CERTS_EXPERIMENTAL_FLAG = "org.eclipse.ecf.internal.ssl.ECFTrustManager.experimental.sortCerts"; + private static volatile BundleContext context; private volatile ServiceTracker trustEngineTracker = null; private ServiceRegistration socketFactoryRegistration; @@ -28,6 +30,11 @@ public class ECFTrustManager implements X509TrustManager, BundleActivator { public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException { + + if (Boolean.getBoolean(SORT_CERTS_EXPERIMENTAL_FLAG)) { + certs = CertificateChainSorter.sortCertificates(certs); + } + // verify the cert chain verify(certs, authType);