diff options
author | Ingo Bauersachs <ingo@jitsi.org> | 2014-11-08 10:16:43 +0100 |
---|---|---|
committer | Ingo Bauersachs <ingo@jitsi.org> | 2014-11-08 12:20:17 +0100 |
commit | 152e034e5d87c104e2dbbb9fea399555597a5bc0 (patch) | |
tree | 39af45ddaea5b0b0f3aa875f9d2a2de6a8b1abdd /src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java | |
parent | d26988c08297bc58f3a75e839f29880f3d84f4e8 (diff) | |
download | jitsi-152e034e5d87c104e2dbbb9fea399555597a5bc0.zip jitsi-152e034e5d87c104e2dbbb9fea399555597a5bc0.tar.gz jitsi-152e034e5d87c104e2dbbb9fea399555597a5bc0.tar.bz2 |
Log the reason why a certificate is not trusted
Diffstat (limited to 'src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java')
-rw-r--r-- | src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java | 2225 |
1 files changed, 1113 insertions, 1112 deletions
diff --git a/src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java b/src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java index b24a108..c0b1ea7 100644 --- a/src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java +++ b/src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java @@ -1,1112 +1,1113 @@ -/*
- * 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.X509Extension;
-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");
- }
-
- // ------------------------------------------------------------------------
- // 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());
- tmFactory.init((KeyStore) null);
- 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:
- throw new CertificateException(
- "The peer provided certificate with Subject <"
- + chain[0].getSubjectDN()
- + "> is not trusted");
- 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(
- X509Extension.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;
- }
-}
+/* + * 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.X509Extension; +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"); + } + + // ------------------------------------------------------------------------ + // 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()); + tmFactory.init((KeyStore) null); + 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( + X509Extension.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; + } +} |