package org.tmatesoft.svn.core.internal.wc; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import java.io.File; import java.io.InputStream; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.logging.Level; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.ISVNAuthenticationProvider; import org.tmatesoft.svn.core.internal.util.SVNBase64; import org.tmatesoft.svn.core.internal.util.SVNHashMap; import org.tmatesoft.svn.core.internal.util.SVNSSLUtil; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; /** * @author TMate Software Ltd. * @version 1.3 */ public class DefaultSVNSSLTrustManager implements X509TrustManager { private SVNURL myURL; private DefaultSVNAuthenticationManager myAuthManager; private X509Certificate[] myTrustedCerts; private String myRealm; private File myAuthDirectory; private boolean myIsUseKeyStore; private File[] myServerCertFiles; private X509TrustManager[] myDefaultTrustManagers; public DefaultSVNSSLTrustManager(File authDir, SVNURL url, File[] serverCertFiles, boolean useKeyStore, DefaultSVNAuthenticationManager authManager) { myURL = url; myAuthDirectory = authDir; myRealm = "https://" + url.getHost() + ":" + url.getPort(); myAuthManager = authManager; myIsUseKeyStore = useKeyStore; myServerCertFiles = serverCertFiles; } private X509TrustManager[] getDefaultTrustManagers() { if (myDefaultTrustManagers == null && myIsUseKeyStore) { myDefaultTrustManagers = initDefaultTrustManagers(); } return myDefaultTrustManagers; } private X509TrustManager[] initDefaultTrustManagers() { try { TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509", "SunJSSE"); tmf.init((KeyStore) null); TrustManager[] trustManagers = tmf.getTrustManagers(); if (trustManagers == null || trustManagers.length == 0) { return null; } List x509TrustManagers = new ArrayList(); for (int i = 0; i < trustManagers.length; i++) { TrustManager trustManager = trustManagers[i]; if (trustManager instanceof X509TrustManager) { x509TrustManagers.add(trustManager); } } return (X509TrustManager[]) x509TrustManagers.toArray(new X509TrustManager[x509TrustManagers.size()]); } catch (NoSuchAlgorithmException e) { SVNDebugLog.getDefaultLog().log(SVNLogType.DEFAULT, e, Level.FINEST); } catch (NoSuchProviderException e) { SVNDebugLog.getDefaultLog().log(SVNLogType.DEFAULT, e, Level.FINEST); } catch (KeyStoreException e) { SVNDebugLog.getDefaultLog().log(SVNLogType.DEFAULT, e, Level.FINEST); } return null; } private void init() { if (myTrustedCerts != null) { return; } Collection trustedCerts = new ArrayList(); // load trusted certs from files. for (int i = 0; i < myServerCertFiles.length; i++) { X509Certificate cert = loadCertificate(myServerCertFiles[i]); if (cert != null) { trustedCerts.add(cert); } } X509TrustManager[] trustManagers = getDefaultTrustManagers(); for (int i = 0; trustManagers != null && i < trustManagers.length; i++) { X509TrustManager trustManager = trustManagers[i]; X509Certificate[] acceptedCerts = trustManager.getAcceptedIssuers(); for (int c = 0; acceptedCerts != null && c < acceptedCerts.length; c++) { X509Certificate cert = acceptedCerts[c]; trustedCerts.add(cert); } } myTrustedCerts = (X509Certificate[]) trustedCerts.toArray(new X509Certificate[trustedCerts.size()]); } public X509Certificate[] getAcceptedIssuers() { init(); return myTrustedCerts; } public void checkClientTrusted(X509Certificate[] certs, String arg1) throws CertificateException { } public void checkServerTrusted(X509Certificate[] certs, String algorithm) throws CertificateException { if (certs != null && certs.length > 0 && certs[0] != null) { String data = SVNBase64.byteArrayToBase64(certs[0].getEncoded()); String stored = (String) myAuthManager.getRuntimeAuthStorage().getData("svn.ssl.server", myRealm); if (data.equals(stored)) { return; } stored = getStoredServerCertificate(myRealm); if (data.equals(stored)) { return; } ISVNAuthenticationProvider authProvider = myAuthManager.getAuthenticationProvider(); int failures = SVNSSLUtil.getServerCertificateFailures(certs[0], myURL.getHost()); // compose bit mask. // 8 is default // check dates for 1 and 2 // check host name for 4 if (authProvider != null) { boolean store = myAuthManager.getHostOptionsProvider().getHostOptions(myURL).isAuthStorageEnabled(); final CertificateException exception = checkServerTrustedByDefault(certs, algorithm); if (exception != null && exception.getMessage().indexOf("Certificates does not conform to algorithm constraints") >= 0) { throw new SVNSSLUtil.CertificateDoesNotConformConstraints("svn: Server SSL certificates chain for '" + myRealm + "' does not conform to algorithm constraints", exception); } final boolean trustServer = exception == null; int result; if (trustServer) { result = ISVNAuthenticationProvider.ACCEPTED; } else { result = authProvider.acceptServerAuthentication(myURL, myRealm, certs[0], store); } if (result == ISVNAuthenticationProvider.ACCEPTED && store) { try { storeServerCertificate(myRealm, data, failures); } catch (SVNException e) { // ignore that exception, as we only need to trust now and may save data later. //throw new SVNSSLUtil.CertificateNotTrustedException("svn: Server SSL certificate for '" + myRealm + "' cannot be saved"); SVNDebugLog.getDefaultLog().logError(SVNLogType.NETWORK, e); } } if (result != ISVNAuthenticationProvider.REJECTED) { myAuthManager.getRuntimeAuthStorage().putData("svn.ssl.server", myRealm, data); return; } throw new SVNSSLUtil.CertificateNotTrustedException("svn: Server SSL certificate for '" + myRealm + "' rejected"); } // like as tmp. accepted. } } private CertificateException checkServerTrustedByDefault(X509Certificate[] certs, String algorithm) { final X509TrustManager[] trustManagers = getDefaultTrustManagers(); if (trustManagers == null) { return null; } for (int i = 0; i < trustManagers.length; i++) { final X509TrustManager trustManager = trustManagers[i]; try { trustManager.checkServerTrusted(certs, algorithm); } catch (CertificateException e) { return e; } } return null; } private String getStoredServerCertificate(String realm) { File file = new File(myAuthDirectory, SVNFileUtil.computeChecksum(realm)); if (!file.isFile()) { return null; } SVNWCProperties props = new SVNWCProperties(file, ""); try { String storedRealm = props.getPropertyValue("svn:realmstring"); if (!realm.equals(storedRealm)) { return null; } return props.getPropertyValue("ascii_cert"); } catch (SVNException e) { } return null; } private void storeServerCertificate(String realm, String data, int failures) throws SVNException { myAuthDirectory.mkdirs(); File file = new File(myAuthDirectory, SVNFileUtil.computeChecksum(realm)); SVNHashMap map = new SVNHashMap(); map.put("ascii_cert", data); map.put("svn:realmstring", realm); map.put("failures", Integer.toString(failures)); SVNFileUtil.deleteFile(file); File tmpFile = SVNFileUtil.createUniqueFile(myAuthDirectory, "auth", ".tmp", true); try { SVNWCProperties.setProperties(SVNProperties.wrap(map), file, tmpFile, SVNWCProperties.SVN_HASH_TERMINATOR); } finally { SVNFileUtil.deleteFile(tmpFile); } } public static X509Certificate loadCertificate(File pemFile) { InputStream is = null; try { is = SVNFileUtil.openFileForReading(pemFile, SVNLogType.WC); } catch (SVNException e) { return null; } try { CertificateFactory factory = CertificateFactory.getInstance("X509"); return (X509Certificate)factory.generateCertificate(is); } catch (CertificateException e) { return null; } finally { SVNFileUtil.closeFile(is); } } }