/* * 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) { } } } } }