Java tutorial
/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package net.java.sip.communicator.impl.certificate; import java.beans.*; import java.io.*; import java.lang.reflect.*; import java.net.*; import java.security.*; import java.security.KeyStore.Builder; import java.security.cert.*; import java.security.cert.Certificate; import java.util.*; import javax.net.ssl.*; import javax.security.auth.callback.*; import net.java.sip.communicator.service.certificate.*; import net.java.sip.communicator.service.credentialsstorage.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.httputil.*; import net.java.sip.communicator.util.Logger; import org.bouncycastle.asn1.*; import org.bouncycastle.asn1.x509.*; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.x509.extension.*; import org.jitsi.service.configuration.*; import org.jitsi.service.resources.*; import org.jitsi.util.*; /** * Implementation of the CertificateService. It asks the user to trust a * certificate when the automatic verification fails. * * @author Ingo Bauersachs * @author Damian Minkov */ public class CertificateServiceImpl implements CertificateService, PropertyChangeListener { // ------------------------------------------------------------------------ // static data // ------------------------------------------------------------------------ private final List<KeyStoreType> supportedTypes = new LinkedList<KeyStoreType>() { /** * Serial version UID. */ private static final long serialVersionUID = 0L; { if (!OSUtils.IS_WINDOWS64) { add(new KeyStoreType("PKCS11", new String[] { ".dll", ".so" }, false)); } add(new KeyStoreType("PKCS12", new String[] { ".p12", ".pfx" }, true)); add(new KeyStoreType(KeyStore.getDefaultType(), new String[] { ".ks", ".jks" }, true)); } }; // ------------------------------------------------------------------------ // services // ------------------------------------------------------------------------ private static final Logger logger = Logger.getLogger(CertificateServiceImpl.class); private final ResourceManagementService R = CertificateVerificationActivator.getResources(); private final ConfigurationService config = CertificateVerificationActivator.getConfigurationService(); private final CredentialsStorageService credService = CertificateVerificationActivator.getCredService(); // ------------------------------------------------------------------------ // properties // ------------------------------------------------------------------------ /** * Base property name for the storage of certificate user preferences. */ private final static String PNAME_CERT_TRUST_PREFIX = "net.java.sip.communicator.impl.certservice"; /** Hash algorithm for the cert thumbprint*/ private final static String THUMBPRINT_HASH_ALGORITHM = "SHA1"; // ------------------------------------------------------------------------ // fields // ------------------------------------------------------------------------ /** * Stores the certificates that are trusted as long as this service lives. */ private Map<String, List<String>> sessionAllowedCertificates = new HashMap<String, List<String>>(); /** * Caches retrievals of AIA information (downloaded certs or failures). */ private Map<URI, AiaCacheEntry> aiaCache = new HashMap<URI, AiaCacheEntry>(); // ------------------------------------------------------------------------ // Map access helpers // ------------------------------------------------------------------------ /** * Helper method to avoid accessing null-lists in the session allowed * certificate map * * @param propName the key to access * @return the list for the given list or a new, empty list put in place for * the key */ private List<String> getSessionCertEntry(String propName) { List<String> entry = sessionAllowedCertificates.get(propName); if (entry == null) { entry = new LinkedList<String>(); sessionAllowedCertificates.put(propName, entry); } return entry; } /** * AIA cache retrieval entry. */ private static class AiaCacheEntry { Date cacheDate; X509Certificate cert; AiaCacheEntry(Date cacheDate, X509Certificate cert) { this.cacheDate = cacheDate; this.cert = cert; } } // ------------------------------------------------------------------------ // Truststore configuration // ------------------------------------------------------------------------ /** * Initializes a new <tt>CertificateServiceImpl</tt> instance. */ public CertificateServiceImpl() { setTrustStore(); config.addPropertyChangeListener(PNAME_TRUSTSTORE_TYPE, this); System.setProperty("com.sun.security.enableCRLDP", config.getString(PNAME_REVOCATION_CHECK_ENABLED, "false")); System.setProperty("com.sun.net.ssl.checkRevocation", config.getString(PNAME_REVOCATION_CHECK_ENABLED, "false")); Security.setProperty("ocsp.enable", config.getString(PNAME_OCSP_ENABLED, "false")); } public void propertyChange(PropertyChangeEvent evt) { setTrustStore(); } private void setTrustStore() { String tsType = (String) config.getProperty(PNAME_TRUSTSTORE_TYPE); String tsFile = (String) config.getProperty(PNAME_TRUSTSTORE_FILE); String tsPassword = credService.loadPassword(PNAME_TRUSTSTORE_PASSWORD); // use the OS store as default store on Windows if (tsType == null && !"meta:default".equals(tsType) && OSUtils.IS_WINDOWS) { tsType = "Windows-ROOT"; config.setProperty(PNAME_TRUSTSTORE_TYPE, tsType); } if (tsType != null && !"meta:default".equals(tsType)) System.setProperty("javax.net.ssl.trustStoreType", tsType); else System.getProperties().remove("javax.net.ssl.trustStoreType"); if (tsFile != null) System.setProperty("javax.net.ssl.trustStore", tsFile); else System.getProperties().remove("javax.net.ssl.trustStore"); if (tsPassword != null) System.setProperty("javax.net.ssl.trustStorePassword", tsPassword); else System.getProperties().remove("javax.net.ssl.trustStorePassword"); } /** * Appends an index number to the alias of each entry in the KeyStore. * * The Windows TrustStore might contain multiple entries with the same * "Friendly Name", which is directly used as the "Alias" for the KeyStore. * As all operations of the KeyStore operate with these non-unique names, * PKIX path building could fail and in the end lead to certificate warnings * for perfectly valid certificates. * * @throws Exception when the aliases could not be renamed. */ private static int keyStoreAppendIndex(KeyStore ks) throws Exception { Field keyStoreSpiField = ks.getClass().getDeclaredField("keyStoreSpi"); keyStoreSpiField.setAccessible(true); KeyStoreSpi keyStoreSpi = (KeyStoreSpi) keyStoreSpiField.get(ks); if ("sun.security.mscapi.KeyStore$ROOT".equals(keyStoreSpi.getClass().getName())) { Field entriesField = keyStoreSpi.getClass().getEnclosingClass().getDeclaredField("entries"); entriesField.setAccessible(true); Collection<?> entries = (Collection<?>) entriesField.get(keyStoreSpi); int i = 0; for (Object entry : entries) { Field aliasField = entry.getClass().getDeclaredField("alias"); aliasField.setAccessible(true); String alias = (String) aliasField.get(entry); aliasField.set(entry, alias.concat("_").concat(Integer.toString(i++))); } return i; } return -1; } // ------------------------------------------------------------------------ // Client authentication configuration // ------------------------------------------------------------------------ /* * (non-Javadoc) * * @see net.java.sip.communicator.service.certificate.CertificateService# * getSupportedKeyStoreTypes() */ public List<KeyStoreType> getSupportedKeyStoreTypes() { return supportedTypes; } /* * (non-Javadoc) * * @see net.java.sip.communicator.service.certificate.CertificateService# * getClientAuthCertificateConfigs() */ public List<CertificateConfigEntry> getClientAuthCertificateConfigs() { List<CertificateConfigEntry> map = new LinkedList<CertificateConfigEntry>(); for (String propName : config.getPropertyNamesByPrefix(PNAME_CLIENTAUTH_CERTCONFIG_BASE, false)) { String propValue = config.getString(propName); if (propValue == null || !propName.endsWith(propValue)) continue; String pnBase = PNAME_CLIENTAUTH_CERTCONFIG_BASE + "." + propValue; CertificateConfigEntry e = new CertificateConfigEntry(); e.setId(propValue); e.setAlias(config.getString(pnBase + ".alias")); e.setDisplayName(config.getString(pnBase + ".displayName")); e.setKeyStore(config.getString(pnBase + ".keyStore")); e.setSavePassword(config.getBoolean(pnBase + ".savePassword", false)); if (e.isSavePassword()) { e.setKeyStorePassword(credService.loadPassword(pnBase)); } String type = config.getString(pnBase + ".keyStoreType"); for (KeyStoreType kt : getSupportedKeyStoreTypes()) { if (kt.getName().equals(type)) { e.setKeyStoreType(kt); break; } } map.add(e); } return map; } /* * (non-Javadoc) * * @see net.java.sip.communicator.service.certificate.CertificateService# * setClientAuthCertificateConfig * (net.java.sip.communicator.service.certificate.CertificateConfigEntry) */ public void setClientAuthCertificateConfig(CertificateConfigEntry e) { if (e.getId() == null) e.setId("conf" + Math.abs(new Random().nextInt())); String pn = PNAME_CLIENTAUTH_CERTCONFIG_BASE + "." + e.getId(); config.setProperty(pn, e.getId()); config.setProperty(pn + ".alias", e.getAlias()); config.setProperty(pn + ".displayName", e.getDisplayName()); config.setProperty(pn + ".keyStore", e.getKeyStore()); config.setProperty(pn + ".savePassword", e.isSavePassword()); if (e.isSavePassword()) credService.storePassword(pn, e.getKeyStorePassword()); else credService.removePassword(pn); config.setProperty(pn + ".keyStoreType", e.getKeyStoreType()); } /* * (non-Javadoc) * * @see net.java.sip.communicator.service.certificate.CertificateService# * removeClientAuthCertificateConfig(java.lang.String) */ public void removeClientAuthCertificateConfig(String id) { for (String p : config.getPropertyNamesByPrefix(PNAME_CLIENTAUTH_CERTCONFIG_BASE + "." + id, true)) { config.removeProperty(p); } config.removeProperty(PNAME_CLIENTAUTH_CERTCONFIG_BASE + "." + id); } // ------------------------------------------------------------------------ // Certificate trust handling // ------------------------------------------------------------------------ /* * (non-Javadoc) * * @see net.java.sip.communicator.service.certificate.CertificateService# * addCertificateToTrust(java.security.cert.Certificate, java.lang.String, * int) */ public void addCertificateToTrust(Certificate cert, String trustFor, int trustMode) throws CertificateException { String propName = PNAME_CERT_TRUST_PREFIX + ".param." + trustFor; String thumbprint = getThumbprint(cert, THUMBPRINT_HASH_ALGORITHM); switch (trustMode) { case DO_NOT_TRUST: throw new IllegalArgumentException( "Cannot add a certificate to trust when " + "no trust is requested."); case TRUST_ALWAYS: String current = config.getString(propName); String newValue = thumbprint; if (current != null) newValue += "," + thumbprint; config.setProperty(propName, newValue); break; case TRUST_THIS_SESSION_ONLY: getSessionCertEntry(propName).add(thumbprint); break; } } /* * (non-Javadoc) * * @see net.java.sip.communicator.service.certificate.CertificateService# * getSSLContext() */ public SSLContext getSSLContext() throws GeneralSecurityException { return getSSLContext(getTrustManager((Iterable<String>) null)); } /* * (non-Javadoc) * * @see net.java.sip.communicator.service.certificate.CertificateService# * getSSLContext(javax.net.ssl.X509TrustManager) */ public SSLContext getSSLContext(X509TrustManager trustManager) throws GeneralSecurityException { try { KeyStore ks = KeyStore .getInstance(System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType())); KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword"); if (System.getProperty("javax.net.ssl.keyStore") != null) { ks.load(new FileInputStream(System.getProperty("javax.net.ssl.keyStore")), null); } else { ks.load(null, null); } kmFactory.init(ks, keyStorePassword == null ? null : keyStorePassword.toCharArray()); return getSSLContext(kmFactory.getKeyManagers(), trustManager); } catch (Exception e) { throw new GeneralSecurityException("Cannot init SSLContext", e); } } private Builder loadKeyStore(final CertificateConfigEntry entry) throws KeyStoreException { final File f = new File(entry.getKeyStore()); final KeyStoreType kt = entry.getKeyStoreType(); if ("PKCS11".equals(kt.getName())) { String config = "name=" + f.getName() + "\nlibrary=" + f.getAbsoluteFile(); try { Class<?> pkcs11c = Class.forName("sun.security.pkcs11.SunPKCS11"); Constructor<?> c = pkcs11c.getConstructor(InputStream.class); Provider p = (Provider) c.newInstance(new ByteArrayInputStream(config.getBytes())); Security.insertProviderAt(p, 0); } catch (Exception e) { logger.error( "Tried to access the PKCS11 provider on an " + "unsupported platform or the load failed", e); } } KeyStore.Builder ksBuilder = KeyStore.Builder.newInstance(kt.getName(), null, f, new KeyStore.CallbackHandlerProtection(new CallbackHandler() { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback cb : callbacks) { if (!(cb instanceof PasswordCallback)) throw new UnsupportedCallbackException(cb); PasswordCallback pwcb = (PasswordCallback) cb; if (entry.isSavePassword()) { pwcb.setPassword(entry.getKeyStorePassword().toCharArray()); return; } else { AuthenticationWindowService authenticationWindowService = CertificateVerificationActivator .getAuthenticationWindowService(); if (authenticationWindowService == null) { logger.error("No AuthenticationWindowService " + "implementation"); throw new IOException("User cancel"); } AuthenticationWindowService.AuthenticationWindow aw = authenticationWindowService .create(f.getName(), null, kt.getName(), false, false, null, null, null, null, null, null, null); aw.setAllowSavePassword(false); aw.setVisible(true); if (!aw.isCanceled()) pwcb.setPassword(aw.getPassword()); else throw new IOException("User cancel"); } } } })); return ksBuilder; } /* * (non-Javadoc) * * @see net.java.sip.communicator.service.certificate.CertificateService# * getSSLContext(java.lang.String, javax.net.ssl.X509TrustManager) */ public SSLContext getSSLContext(String clientCertConfig, X509TrustManager trustManager) throws GeneralSecurityException { try { if (clientCertConfig == null) return getSSLContext(trustManager); CertificateConfigEntry entry = null; for (CertificateConfigEntry e : getClientAuthCertificateConfigs()) { if (e.getId().equals(clientCertConfig)) { entry = e; break; } } if (entry == null) throw new GeneralSecurityException( "Client certificate config with id <" + clientCertConfig + "> not found."); final KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509"); kmf.init(new KeyStoreBuilderParameters(loadKeyStore(entry))); return getSSLContext(kmf.getKeyManagers(), trustManager); } catch (Exception e) { throw new GeneralSecurityException("Cannot init SSLContext", e); } } /* * (non-Javadoc) * * @see net.java.sip.communicator.service.certificate.CertificateService# * getSSLContext(javax.net.ssl.KeyManager[], javax.net.ssl.X509TrustManager) */ public SSLContext getSSLContext(KeyManager[] keyManagers, X509TrustManager trustManager) throws GeneralSecurityException { try { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, new TrustManager[] { trustManager }, null); return sslContext; } catch (Exception e) { throw new GeneralSecurityException("Cannot init SSLContext", e); } } /* * (non-Javadoc) * * @see * net.java.sip.communicator.service.certificate * .CertificateService#getTrustManager(java.lang.Iterable) */ public X509TrustManager getTrustManager(Iterable<String> identitiesToTest) throws GeneralSecurityException { return getTrustManager(identitiesToTest, new EMailAddressMatcher(), new BrowserLikeHostnameMatcher()); } /* * (non-Javadoc) * * @see * net.java.sip.communicator.service.certificate.CertificateService * #getTrustManager(java.lang.String) */ public X509TrustManager getTrustManager(String identityToTest) throws GeneralSecurityException { return getTrustManager(Arrays.asList(new String[] { identityToTest }), new EMailAddressMatcher(), new BrowserLikeHostnameMatcher()); } /* * (non-Javadoc) * * @see * net.java.sip.communicator.service.certificate.CertificateService * #getTrustManager(java.lang.String, * net.java.sip.communicator.service.certificate.CertificateMatcher, * net.java.sip.communicator.service.certificate.CertificateMatcher) */ public X509TrustManager getTrustManager(String identityToTest, CertificateMatcher clientVerifier, CertificateMatcher serverVerifier) throws GeneralSecurityException { return getTrustManager(Arrays.asList(new String[] { identityToTest }), clientVerifier, serverVerifier); } /* * (non-Javadoc) * * @see * net.java.sip.communicator.service.certificate.CertificateService * #getTrustManager(java.lang.Iterable, * net.java.sip.communicator.service.certificate.CertificateMatcher, * net.java.sip.communicator.service.certificate.CertificateMatcher) */ public X509TrustManager getTrustManager(final Iterable<String> identitiesToTest, final CertificateMatcher clientVerifier, final CertificateMatcher serverVerifier) throws GeneralSecurityException { // obtain the default X509 trust manager X509TrustManager defaultTm = null; TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); //workaround for https://bugs.openjdk.java.net/browse/JDK-6672015 KeyStore ks = null; String tsType = System.getProperty("javax.net.ssl.trustStoreType", null); if ("Windows-ROOT".equals(tsType)) { try { ks = KeyStore.getInstance(tsType); ks.load(null, null); int numEntries = keyStoreAppendIndex(ks); logger.info( "Using Windows-ROOT. Aliases sucessfully renamed on " + numEntries + " root certificates."); } catch (Exception e) { logger.error("Could not rename Windows-ROOT aliases", e); } } tmFactory.init(ks); for (TrustManager m : tmFactory.getTrustManagers()) { if (m instanceof X509TrustManager) { defaultTm = (X509TrustManager) m; break; } } if (defaultTm == null) throw new GeneralSecurityException("No default X509 trust manager found"); final X509TrustManager tm = defaultTm; return new X509TrustManager() { private boolean serverCheck; public X509Certificate[] getAcceptedIssuers() { return tm.getAcceptedIssuers(); } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { serverCheck = true; checkCertTrusted(chain, authType); } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { serverCheck = false; checkCertTrusted(chain, authType); } private void checkCertTrusted(X509Certificate[] chain, String authType) throws CertificateException { // check and default configurations for property // if missing default is null - false String defaultAlwaysTrustMode = CertificateVerificationActivator.getResources() .getSettingsString(CertificateService.PNAME_ALWAYS_TRUST); if (config.getBoolean(PNAME_ALWAYS_TRUST, Boolean.parseBoolean(defaultAlwaysTrustMode))) return; try { // check the certificate itself (issuer, validity) try { chain = tryBuildChain(chain); } catch (Exception e) { } // don't care and take the chain as is if (serverCheck) tm.checkServerTrusted(chain, authType); else tm.checkClientTrusted(chain, authType); if (identitiesToTest == null || !identitiesToTest.iterator().hasNext()) return; else if (serverCheck) serverVerifier.verify(identitiesToTest, chain[0]); else clientVerifier.verify(identitiesToTest, chain[0]); // ok, globally valid cert } catch (CertificateException e) { String thumbprint = getThumbprint(chain[0], THUMBPRINT_HASH_ALGORITHM); String message = null; List<String> propNames = new LinkedList<String>(); List<String> storedCerts = new LinkedList<String>(); String appName = R.getSettingsString("service.gui.APPLICATION_NAME"); if (identitiesToTest == null || !identitiesToTest.iterator().hasNext()) { String propName = PNAME_CERT_TRUST_PREFIX + ".server." + thumbprint; propNames.add(propName); message = R.getI18NString("service.gui." + "CERT_DIALOG_DESCRIPTION_TXT_NOHOST", new String[] { appName }); // get the thumbprints from the permanent allowances String hashes = config.getString(propName); if (hashes != null) for (String h : hashes.split(",")) storedCerts.add(h); // get the thumbprints from the session allowances List<String> sessionCerts = sessionAllowedCertificates.get(propName); if (sessionCerts != null) storedCerts.addAll(sessionCerts); } else { if (serverCheck) { message = R.getI18NString("service.gui." + "CERT_DIALOG_DESCRIPTION_TXT", new String[] { appName, identitiesToTest.toString() }); } else { message = R.getI18NString("service.gui." + "CERT_DIALOG_PEER_DESCRIPTION_TXT", new String[] { appName, identitiesToTest.toString() }); } for (String identity : identitiesToTest) { String propName = PNAME_CERT_TRUST_PREFIX + ".param." + identity; propNames.add(propName); // get the thumbprints from the permanent allowances String hashes = config.getString(propName); if (hashes != null) for (String h : hashes.split(",")) storedCerts.add(h); // get the thumbprints from the session allowances List<String> sessionCerts = sessionAllowedCertificates.get(propName); if (sessionCerts != null) storedCerts.addAll(sessionCerts); } } if (!storedCerts.contains(thumbprint)) { switch (verify(chain, message)) { case DO_NOT_TRUST: logger.info("Untrusted certificate", e); throw new CertificateException("The peer provided certificate with Subject <" + chain[0].getSubjectDN() + "> is not trusted", e); case TRUST_ALWAYS: for (String propName : propNames) { String current = config.getString(propName); String newValue = thumbprint; if (current != null) newValue += "," + current; config.setProperty(propName, newValue); } break; case TRUST_THIS_SESSION_ONLY: for (String propName : propNames) getSessionCertEntry(propName).add(thumbprint); break; } } // ok, we've seen this certificate before } } private X509Certificate[] tryBuildChain(X509Certificate[] chain) throws IOException, URISyntaxException, CertificateException { // Only try to build chains for servers that send only their // own cert, but no issuer. This also matches self signed (will // be ignored later) and Root-CA signed certs. In this case we // throw the Root-CA away after the lookup if (chain.length != 1) return chain; // ignore self signed certs if (chain[0].getIssuerDN().equals(chain[0].getSubjectDN())) return chain; // prepare for the newly created chain List<X509Certificate> newChain = new ArrayList<X509Certificate>(chain.length + 4); for (X509Certificate cert : chain) { newChain.add(cert); } // search from the topmost certificate upwards CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); X509Certificate current = chain[chain.length - 1]; boolean foundParent; int chainLookupCount = 0; do { foundParent = false; // extract the url(s) where the parent certificate can be // found byte[] aiaBytes = current.getExtensionValue(Extension.authorityInfoAccess.getId()); if (aiaBytes == null) break; AuthorityInformationAccess aia = AuthorityInformationAccess .getInstance(X509ExtensionUtil.fromExtensionValue(aiaBytes)); // the AIA may contain different URLs and types, try all // of them for (AccessDescription ad : aia.getAccessDescriptions()) { // we are only interested in the issuer certificate, // not in OCSP urls the like if (!ad.getAccessMethod().equals(AccessDescription.id_ad_caIssuers)) continue; GeneralName gn = ad.getAccessLocation(); if (!(gn.getTagNo() == GeneralName.uniformResourceIdentifier && gn.getName() instanceof DERIA5String)) continue; URI uri = new URI(((DERIA5String) gn.getName()).getString()); // only http(s) urls; LDAP is taken care of in the // default implementation if (!(uri.getScheme().equalsIgnoreCase("http") || uri.getScheme().equals("https"))) continue; X509Certificate cert = null; // try to get cert from cache first to avoid consecutive // (slow) http lookups AiaCacheEntry cache = aiaCache.get(uri); if (cache != null && cache.cacheDate.after(new Date())) { cert = cache.cert; } else { // download if no cache entry or if it is expired if (logger.isDebugEnabled()) logger.debug("Downloading parent certificate for <" + current.getSubjectDN() + "> from <" + uri + ">"); try { InputStream is = HttpUtils.openURLConnection(uri.toString()).getContent(); cert = (X509Certificate) certFactory.generateCertificate(is); } catch (Exception e) { logger.debug("Could not download from <" + uri + ">"); } // cache for 10mins aiaCache.put(uri, new AiaCacheEntry(new Date(new Date().getTime() + 10 * 60 * 1000), cert)); } if (cert != null) { if (!cert.getIssuerDN().equals(cert.getSubjectDN())) { newChain.add(cert); foundParent = true; current = cert; break; // an AD was valid, ignore others } else logger.debug("Parent is self-signed, ignoring"); } } chainLookupCount++; } while (foundParent && chainLookupCount < 10); chain = newChain.toArray(chain); return chain; } }; } protected class BrowserLikeHostnameMatcher implements CertificateMatcher { public void verify(Iterable<String> identitiesToTest, X509Certificate cert) throws CertificateException { // check whether one of the hostname is present in the // certificate boolean oneMatched = false; for (String identity : identitiesToTest) { try { org.apache.http.conn.ssl.SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER.verify(identity, cert); oneMatched = true; break; } catch (SSLException e) { } } if (!oneMatched) throw new CertificateException( "None of <" + identitiesToTest + "> matched the cert with CN=" + cert.getSubjectDN()); } } protected class EMailAddressMatcher implements CertificateMatcher { public void verify(Iterable<String> identitiesToTest, X509Certificate cert) throws CertificateException { // check if the certificate contains the E-Mail address(es) // in the SAN(s) //TODO: extract address from DN (E-field) too? boolean oneMatched = false; Iterable<String> emails = getSubjectAltNames(cert, 6); for (String identity : identitiesToTest) { for (String email : emails) { if (identity.equalsIgnoreCase(email)) { oneMatched = true; break; } } } if (!oneMatched) throw new CertificateException("The peer provided certificate with Subject <" + cert.getSubjectDN() + "> contains no SAN for <" + identitiesToTest + ">"); } } /** * Asks the user whether he trusts the supplied chain of certificates. * * @param chain The chain of the certificates to check with user. * @param message A text that describes why the verification failed. * @return The result of the user interaction. One of * {@link CertificateService#DO_NOT_TRUST}, * {@link CertificateService#TRUST_THIS_SESSION_ONLY}, * {@link CertificateService#TRUST_ALWAYS} */ protected int verify(final X509Certificate[] chain, final String message) { if (config.getBoolean(PNAME_NO_USER_INTERACTION, false)) return DO_NOT_TRUST; if (CertificateVerificationActivator.getCertificateDialogService() == null) { logger.error("Missing CertificateDialogService by default " + "will not trust!"); return DO_NOT_TRUST; } VerifyCertificateDialogService.VerifyCertificateDialog dialog = CertificateVerificationActivator .getCertificateDialogService().createDialog(chain, null, message); dialog.setVisible(true); if (!dialog.isTrusted()) return DO_NOT_TRUST; else if (dialog.isAlwaysTrustSelected()) return TRUST_ALWAYS; else return TRUST_THIS_SESSION_ONLY; } /** * Calculates the hash of the certificate known as the "thumbprint" * and returns it as a string representation. * * @param cert The certificate to hash. * @param algorithm The hash algorithm to use. * @return The SHA-1 hash of the certificate. * @throws CertificateException */ private static String getThumbprint(Certificate cert, String algorithm) throws CertificateException { MessageDigest digest; try { digest = MessageDigest.getInstance(algorithm); } catch (NoSuchAlgorithmException e) { throw new CertificateException(e); } byte[] encodedCert = cert.getEncoded(); StringBuilder sb = new StringBuilder(encodedCert.length * 2); Formatter f = new Formatter(sb); try { for (byte b : digest.digest(encodedCert)) f.format("%02x", b); } finally { f.close(); } return sb.toString(); } /** * Gets the SAN (Subject Alternative Name) of the specified type. * * @param cert the certificate to extract from * @param altNameType The type to be returned * @return SAN of the type * * <PRE> * GeneralName ::= CHOICE { * otherName [0] OtherName, * rfc822Name [1] IA5String, * dNSName [2] IA5String, * x400Address [3] ORAddress, * directoryName [4] Name, * ediPartyName [5] EDIPartyName, * uniformResourceIdentifier [6] IA5String, * iPAddress [7] OCTET STRING, * registeredID [8] OBJECT IDENTIFIER * } * <PRE> */ private static Iterable<String> getSubjectAltNames(X509Certificate cert, int altNameType) { Collection<List<?>> altNames = null; try { altNames = cert.getSubjectAlternativeNames(); } catch (CertificateParsingException e) { return Collections.emptyList(); } List<String> matchedAltNames = new LinkedList<String>(); for (List<?> item : altNames) { if (item.contains(altNameType)) { Integer type = (Integer) item.get(0); if (type.intValue() == altNameType) matchedAltNames.add((String) item.get(1)); } } return matchedAltNames; } }