package org.ovirt.engine.core.uutils.crypto; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.FileSystems; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.cert.CertPath; import java.security.cert.CertPathBuilder; import java.security.cert.CertPathBuilderException; import java.security.cert.CertStore; import java.security.cert.Certificate; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.PKIXBuilderParameters; import java.security.cert.TrustAnchor; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; /** * Certificate chain related tools. * Example: * <pre> * CertificateChain.completeChain(CertificateChain.getSSLPeerCertificates(new URL("https://www.google.com")), null) * </pre> */ public class CertificateChain { /** * Returns trust anchors out of key store. * @param keystore KeyStore to use. * @return TrustAnchor */ public static Set<TrustAnchor> keyStoreToTrustAnchors(KeyStore keystore) throws KeyStoreException { Set<TrustAnchor> ret = new HashSet<>(); for (String alias : Collections.list(keystore.aliases())) { try { KeyStore.Entry entry = keystore.getEntry(alias, null); if (entry instanceof KeyStore.TrustedCertificateEntry) { Certificate c = ((KeyStore.TrustedCertificateEntry)entry).getTrustedCertificate(); if (c instanceof X509Certificate) { c.verify(c.getPublicKey()); ret.add(new TrustAnchor((X509Certificate)c, null)); } } } catch(Exception e) { // ignore } } return ret; } /** * Returns trust anchors for the default java key store. * @return TrustAnchor */ public static Set<TrustAnchor> getDefaultTrustAnchors() throws GeneralSecurityException, IOException { try ( InputStream is = new FileInputStream( System.getProperty( "javax.net.ssl.trustStore", FileSystems.getDefault().getPath( System.getProperty("java.home"), "lib", "security", "cacerts" ).toString() ) ) ) { KeyStore trustStore = KeyStore.getInstance( System.getProperty( "javax.net.ssl.trustStoreType", KeyStore.getDefaultType() ) ); trustStore.load( is, System.getProperty( "javax.net.ssl.trustStorePassword", "changeit" ).toCharArray() ); return keyStoreToTrustAnchors(trustStore); } } /** * Builds CertsPath object out of chain candidate. * Throws CertPathBuilderException exception if fails among other exceptions. * @param chain chain candidate, first end certificate last issuer. * @param trustAnchors trust anchors to use. * @return CertPath */ public static CertPath buildCertPath( List<Certificate> chain, Set<TrustAnchor> trustAnchors ) throws GeneralSecurityException { X509CertSelector selector = new X509CertSelector(); selector.setCertificate((X509Certificate)chain.get(0)); PKIXBuilderParameters pkixParams = new PKIXBuilderParameters( trustAnchors, selector ); pkixParams.setRevocationEnabled(false); pkixParams.setMaxPathLength(-1); pkixParams.addCertStore( CertStore.getInstance( "Collection", new CollectionCertStoreParameters(chain) ) ); return CertPathBuilder.getInstance("PKIX").build(pkixParams).getCertPath(); } /** * Complete certificate chain candidate up to root if possible. * @param chain chain candidate, first end certificate last issuer. * @param extraTrustAnchors extra trust anchors to use. * @return Built chain */ public static List<Certificate> completeChain( List<Certificate> chain, Set<TrustAnchor> extraTrustAnchors ) throws GeneralSecurityException, IOException { List<Certificate> ret = chain; if (ret != null) { Certificate top = ret.get(ret.size()-1); boolean topIsRoot = false; try { top.verify(top.getPublicKey()); topIsRoot = true; } catch(Exception e) { // ignore } if (!topIsRoot && ret.get(0) instanceof X509Certificate) { try { Set<TrustAnchor> trustAnchors = getDefaultTrustAnchors(); if (extraTrustAnchors != null) { trustAnchors.addAll(extraTrustAnchors); } ret = new ArrayList<>(buildCertPath(ret, trustAnchors).getCertificates()); top = ret.get(ret.size()-1); for (TrustAnchor t : trustAnchors) { try { Certificate c= t.getTrustedCert(); top.verify(c.getPublicKey()); ret.add(c); break; } catch (Exception e) { // ignore } } } catch (CertPathBuilderException e) { // ignore } } } return ret; } /** * Retrieve SSL peer certificate. * @param url URL to use. * @return Chain received from peer. */ public static List<Certificate> getSSLPeerCertificates(URL url) throws GeneralSecurityException, IOException { List<Certificate> ret = null; if ("https".equals(url.getProtocol())) { SSLContext ctx = SSLContext.getInstance("TLS"); // // handshake may fail due to various of reasons // we need peer certificate, we do not care about any // other information. // so we collect the peer certificate out of the server // hello message which triggers the trust manager early // during handshake. // final List<Certificate> tmcerts = new ArrayList<>(); ctx.init( null, new TrustManager[]{ new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; } public void checkClientTrusted( X509Certificate[] certs, String authType ) { } public void checkServerTrusted( X509Certificate[] certs, String authType ) { tmcerts.addAll(Arrays.asList(certs)); } } }, null ); try ( SSLSocket sock = (SSLSocket)ctx.getSocketFactory().createSocket( url.getHost(), url.getPort() != -1 ? url.getPort() : url.getDefaultPort() ) ) { sock.setSoTimeout(60*1000); try { sock.startHandshake(); } catch (Exception e) { // ignore get whatever we can from trust manager } if (!tmcerts.isEmpty()) { ret = tmcerts; } } } return ret; } }