]> source.dussan.org Git - gitblit.git/commitdiff
Implemented hot-reloadable CRL
authorJames Moger <james.moger@gitblit.com>
Thu, 6 Dec 2012 00:05:59 +0000 (19:05 -0500)
committerJames Moger <james.moger@gitblit.com>
Thu, 6 Dec 2012 00:05:59 +0000 (19:05 -0500)
src/com/gitblit/GitBlitServer.java
src/com/gitblit/GitblitSslContextFactory.java [new file with mode: 0644]
src/com/gitblit/GitblitTrustManager.java [new file with mode: 0644]

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