aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java
diff options
context:
space:
mode:
authorIngo Bauersachs <ingo@jitsi.org>2014-11-08 10:16:43 +0100
committerIngo Bauersachs <ingo@jitsi.org>2014-11-08 12:20:17 +0100
commit152e034e5d87c104e2dbbb9fea399555597a5bc0 (patch)
tree39af45ddaea5b0b0f3aa875f9d2a2de6a8b1abdd /src/net/java/sip/communicator/impl/certificate/CertificateServiceImpl.java
parentd26988c08297bc58f3a75e839f29880f3d84f4e8 (diff)
downloadjitsi-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.java2225
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;
+ }
+}