src/com/gitblit/GitBlitServer.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/GitblitSslContextFactory.java | ●●●●● patch | view | raw | blame | history | |
src/com/gitblit/GitblitTrustManager.java | ●●●●● patch | view | raw | blame | history |
src/com/gitblit/GitBlitServer.java
@@ -44,7 +44,6 @@ import org.eclipse.jetty.server.ssl.SslConnector; import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; import org.eclipse.jetty.server.ssl.SslSocketConnector; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jgit.storage.file.FileBasedConfig; @@ -426,53 +425,28 @@ private static Connector createSSLConnector(String certAlias, File keyStore, File clientTrustStore, String storePassword, File caRevocationList, boolean useNIO, int port, boolean requireClientCertificates) { SslContextFactory sslContext = new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH); GitblitSslContextFactory factory = new GitblitSslContextFactory(certAlias, keyStore, clientTrustStore, storePassword, caRevocationList); SslConnector connector; if (useNIO) { logger.info("Setting up NIO SslSelectChannelConnector on port " + port); SslSelectChannelConnector ssl = new SslSelectChannelConnector(sslContext); SslSelectChannelConnector ssl = new SslSelectChannelConnector(factory); ssl.setSoLingerTime(-1); if (requireClientCertificates) { sslContext.setNeedClientAuth(true); factory.setNeedClientAuth(true); } else { sslContext.setWantClientAuth(true); factory.setWantClientAuth(true); } ssl.setThreadPool(new QueuedThreadPool(20)); connector = ssl; } else { logger.info("Setting up NIO SslSocketConnector on port " + port); SslSocketConnector ssl = new SslSocketConnector(sslContext); SslSocketConnector ssl = new SslSocketConnector(factory); connector = ssl; } // disable renegotiation unless this is a patched JVM boolean allowRenegotiation = false; String v = System.getProperty("java.version"); if (v.startsWith("1.7")) { allowRenegotiation = true; } else if (v.startsWith("1.6")) { // 1.6.0_22 was first release with RFC-5746 implemented fix. if (v.indexOf('_') > -1) { String b = v.substring(v.indexOf('_') + 1); if (Integer.parseInt(b) >= 22) { allowRenegotiation = true; } } } if (allowRenegotiation) { logger.info(" allowing SSL renegotiation on Java " + v); sslContext.setAllowRenegotiate(allowRenegotiation); } sslContext.setKeyStorePath(keyStore.getAbsolutePath()); sslContext.setKeyStorePassword(storePassword); sslContext.setTrustStore(clientTrustStore.getAbsolutePath()); sslContext.setTrustStorePassword(storePassword); sslContext.setCrlPath(caRevocationList.getAbsolutePath()); if (!StringUtils.isEmpty(certAlias)) { logger.info(" certificate alias = " + certAlias); sslContext.setCertAlias(certAlias); } connector.setPort(port); connector.setMaxIdleTime(30000); return connector; } src/com/gitblit/GitblitSslContextFactory.java
New file @@ -0,0 +1,94 @@ /* * Copyright 2012 gitblit.com. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.gitblit; import java.io.File; import java.security.KeyStore; import java.security.cert.CRL; import java.util.Collection; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.utils.StringUtils; /** * Special SSL context factory that configures Gitblit GO and replaces the * primary trustmanager with a GitblitTrustManager. * * @author James Moger */ public class GitblitSslContextFactory extends SslContextFactory { private static final Logger logger = LoggerFactory.getLogger(GitblitSslContextFactory.class); private final File caRevocationList; public GitblitSslContextFactory(String certAlias, File keyStore, File clientTrustStore, String storePassword, File caRevocationList) { super(keyStore.getAbsolutePath()); this.caRevocationList = caRevocationList; // disable renegotiation unless this is a patched JVM boolean allowRenegotiation = false; String v = System.getProperty("java.version"); if (v.startsWith("1.7")) { allowRenegotiation = true; } else if (v.startsWith("1.6")) { // 1.6.0_22 was first release with RFC-5746 implemented fix. if (v.indexOf('_') > -1) { String b = v.substring(v.indexOf('_') + 1); if (Integer.parseInt(b) >= 22) { allowRenegotiation = true; } } } if (allowRenegotiation) { logger.info(" allowing SSL renegotiation on Java " + v); setAllowRenegotiate(allowRenegotiation); } if (!StringUtils.isEmpty(certAlias)) { logger.info(" certificate alias = " + certAlias); setCertAlias(certAlias); } setKeyStorePassword(storePassword); setTrustStore(clientTrustStore.getAbsolutePath()); setTrustStorePassword(storePassword); logger.info(" keyStorePath = " + keyStore.getAbsolutePath()); logger.info(" trustStorePath = " + clientTrustStore.getAbsolutePath()); logger.info(" crlPath = " + caRevocationList.getAbsolutePath()); } @Override protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception { TrustManager[] managers = super.getTrustManagers(trustStore, crls); X509TrustManager delegate = (X509TrustManager) managers[0]; GitblitTrustManager root = new GitblitTrustManager(delegate, caRevocationList); // replace first manager with the GitblitTrustManager managers[0] = root; return managers; } } src/com/gitblit/GitblitTrustManager.java
New file @@ -0,0 +1,125 @@ /* * Copyright 2012 gitblit.com. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.gitblit; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; import java.security.cert.X509Certificate; import java.text.MessageFormat; import java.util.concurrent.atomic.AtomicLong; import javax.net.ssl.X509TrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * GitblitTrustManager is a wrapper trust manager that hot-reloads a local file * CRL and enforces client certificate revocations. The GitblitTrustManager * also implements fuzzy revocation enforcement in case of issuer mismatch BUT * serial number match. These rejecions are specially noted in the log. * * @author James Moger */ public class GitblitTrustManager implements X509TrustManager { private static final Logger logger = LoggerFactory.getLogger(GitblitTrustManager.class); private final X509TrustManager delegate; private final File caRevocationList; private final AtomicLong lastModified = new AtomicLong(0); private volatile X509CRL crl; public GitblitTrustManager(X509TrustManager delegate, File crlFile) { this.delegate = delegate; this.caRevocationList = crlFile; } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { X509Certificate cert = chain[0]; if (isRevoked(cert)) { String message = MessageFormat.format("Rejecting revoked certificate {0,number,0} for {1}", cert.getSerialNumber(), cert.getSubjectDN().getName()); logger.warn(message); throw new CertificateException(message); } delegate.checkClientTrusted(chain, authType); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { delegate.checkServerTrusted(chain, authType); } @Override public X509Certificate[] getAcceptedIssuers() { return delegate.getAcceptedIssuers(); } protected boolean isRevoked(X509Certificate cert) { if (!caRevocationList.exists()) { return false; } read(); if (crl.isRevoked(cert)) { // exact cert is revoked return true; } X509CRLEntry entry = crl.getRevokedCertificate(cert.getSerialNumber()); if (entry != null) { logger.warn("Certificate issuer does not match CRL issuer, but serial number has been revoked!"); logger.warn(" cert issuer = " + cert.getIssuerX500Principal()); logger.warn(" crl issuer = " + crl.getIssuerX500Principal()); return true; } return false; } protected synchronized void read() { if (lastModified.get() == caRevocationList.lastModified()) { return; } logger.info("Reloading CRL from " + caRevocationList.getAbsolutePath()); InputStream inStream = null; try { inStream = new FileInputStream(caRevocationList); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509CRL list = (X509CRL)cf.generateCRL(inStream); crl = list; lastModified.set(caRevocationList.lastModified()); } catch (Exception e) { } finally { if (inStream != null) { try { inStream.close(); } catch (Exception e) { } } } } }