James Moger
2012-11-23 d8a0f1e6e9d2420be31200cf0554336e84eec843
Revised Gitblit GO certificate generation to use new X509 utility functions
1 files added
8 files modified
188 ■■■■■ changed files
.gitignore 11 ●●●●● patch | view | raw | blame | history
build.xml 18 ●●●●● patch | view | raw | blame | history
distrib/authority.conf 6 ●●●●● patch | view | raw | blame | history
distrib/gitblit.properties 14 ●●●●● patch | view | raw | blame | history
distrib/makekeystore.cmd 2 ●●● patch | view | raw | blame | history
distrib/makekeystore_jdk.cmd 4 ●●●● patch | view | raw | blame | history
docs/04_releases.mkd 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlitServer.java 75 ●●●● patch | view | raw | blame | history
src/com/gitblit/MakeCertificate.java 54 ●●●●● patch | view | raw | blame | history
.gitignore
@@ -25,7 +25,10 @@
/build-demo.xml
/users.conf
*.directory
/.gradle
/projects.conf
/pom.xml
/deploy
/.gradle
/projects.conf
/pom.xml
/deploy
/*.jks
/x509test
/certs
build.xml
@@ -111,6 +111,14 @@
                <include name="users.conf" />
            </fileset>
        </copy>
        <!-- copy required distribution files to project folder -->
        <mkdir dir="${basedir}/certs" />
        <copy todir="${basedir}/certs" overwrite="false">
            <fileset dir="${basedir}/distrib">
                <include name="authority.conf" />
            </fileset>
        </copy>
        <!-- copy gitblit.properties to the WEB-INF folder.
             this file is only used for parsing setting descriptions. -->
@@ -174,10 +182,20 @@
                <include name="**/*" />
                <exclude name="federation.properties" />
                <exclude name="openshift.mkd" />
                <exclude name="authority.conf" />
            </fileset>
            <fileset dir="${basedir}">
                <include name="LICENSE" />
                <include name="NOTICE" />
                <include name="authority*.jar" />
            </fileset>
        </copy>
        <!-- Certificate templates -->
        <mkdir dir="${project.deploy.dir}/certs"/>
        <copy todir="${project.deploy.dir}/certs">
            <fileset dir="${basedir}/distrib">
                <include name="authority.conf" />
            </fileset>
        </copy>
distrib/authority.conf
New file
@@ -0,0 +1,6 @@
[new]
    duration = 365
    organizationalUnit = Gitblit
    organization = Gitblit
    locality = Gitblit
    stateProvince = NY
distrib/gitblit.properties
@@ -1137,6 +1137,20 @@
# RESTART REQUIRED
server.storePassword = gitblit
# If serving over https (recommended) you might consider requiring clients to
# authenticate with ssl certificates.  If enabled, only https clients with the
# a valid client certificate will be able to access Gitblit.
#
# If disabled, client certificate authentication is optional and will be tried
# first before falling-back to form authentication or basic authentication.
#
# Requiring client certificates to access any of Gitblit may be too extreme,
# consider this carefully.
#
# SINCE 1.2.0
# RESTART REQUIRED
server.requireClientCertificates = false
# Port for shutdown monitor to listen on.
#
# SINCE 0.5.0
distrib/makekeystore.cmd
@@ -2,5 +2,5 @@
@REM Set HOSTNAME to the server's hostname
@REM --------------------------------------------------------------------------
@SET HOSTNAME=localhost
@del keystore
@del serverKeyStore.jks
@java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.MakeCertificate --hostname %HOSTNAME% --subject "CN=%HOSTNAME%, OU=Gitblit, O=Gitblit, L=Some Town, ST=Some State, C=US"
distrib/makekeystore_jdk.cmd
@@ -2,5 +2,5 @@
@REM Set HOSTNAME to the server's hostname
@REM --------------------------------------------------------------------------
@SET HOSTNAME=localhost
@del keystore
@keytool -keystore keystore -alias %HOSTNAME% -genkey -keyalg RSA -dname "CN=%HOSTNAME%, OU=Gitblit, O=Gitblit, L=Some Town, ST=Some State, C=US"
@del serverKeyStore.jks
@keytool -keystore serverKeyStore.jks -alias %HOSTNAME% -genkey -keyalg RSA -dname "CN=%HOSTNAME%, OU=Gitblit, O=Gitblit, L=Some Town, ST=Some State, C=US"
docs/04_releases.mkd
@@ -52,6 +52,10 @@
    **New:** *git.requireClientCertificates = false*  
    **New:** *git.enforceCertificateValidity = true*  
    **New:** *git.certificateUsernameOIDs = CN*
- Revised clean install certificate generation to create a Gitblit GO Certificate Authority certificate; an SSL certificate signed by the CA certificate; and to create distinct server key and server trust stores.  <u>The store files have been renamed!</u>
- Added support for Gitblit GO to require usage of client certificates to access the entire server.
This is extreme and should be considered carefully since it affects every https access.  The default is to **want** client certificates.  Setting this value to *true* changes that to **need** client certificates.
    **New:** *server.requireClientCertificates = false*
- Added setting to control length of shortened commit ids  
    **New:** *web.shortCommitIdLength=8*  
- Added simple project pages.  A project is a subfolder off the *git.repositoriesFolder*.
src/com/gitblit/GitBlitServer.java
@@ -29,6 +29,7 @@
import java.security.ProtectionDomain;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;
@@ -41,8 +42,11 @@
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;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,7 +55,11 @@
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
import com.gitblit.authority.NewCertificateConfig;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.utils.X509Utils;
import com.gitblit.utils.X509Utils.X509Metadata;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
@@ -186,15 +194,32 @@
        // conditionally configure the https connector
        if (params.securePort > 0) {
            File keystore = new File("keystore");
            if (!keystore.exists()) {
                logger.info("Generating self-signed SSL certificate for localhost");
                MakeCertificate.generateSelfSignedCertificate("localhost", keystore,
                        params.storePassword);
            File folder = new File(System.getProperty("user.dir"));
            File certificatesConf = new File(folder, X509Utils.CA_CONFIG);
            File serverKeyStore = new File(folder, X509Utils.SERVER_KEY_STORE);
            File serverTrustStore = new File(folder, X509Utils.SERVER_TRUST_STORE);
            File caRevocationList = new File(folder, X509Utils.CA_REVOCATION_LIST);
            // generate CA & web certificates, create certificate stores
            X509Metadata metadata = new X509Metadata("localhost", params.storePassword);
            // set default certificate values from config file
            if (certificatesConf.exists()) {
                FileBasedConfig config = new FileBasedConfig(certificatesConf, FS.detect());
                try {
                    config.load();
                } catch (Exception e) {
                    logger.error("Error parsing " + certificatesConf, e);
                }
                NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config);
                certificateConfig.update(metadata);
            }
            if (keystore.exists()) {
                Connector secureConnector = createSSLConnector(keystore, params.storePassword,
                        params.useNIO, params.securePort);
            metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
            X509Utils.prepareX509Infrastructure(metadata, folder);
            if (serverKeyStore.exists()) {
                Connector secureConnector = createSSLConnector(serverKeyStore, serverTrustStore, params.storePassword,
                        caRevocationList, params.useNIO, params.securePort, params.requireClientCertificates);
                String bindInterface = settings.getString(Keys.server.httpsBindInterface, null);
                if (!StringUtils.isEmpty(bindInterface)) {
                    logger.warn(MessageFormat.format(
@@ -364,24 +389,34 @@
     * SSL renegotiation will be enabled if the JVM is 1.6.0_22 or later.
     * oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html
     * 
     * @param keystore
     * @param password
     * @param keyStore
     * @param clientTrustStore
     * @param storePassword
     * @param caRevocationList
     * @param useNIO
     * @param port
     * @param requireClientCertificates
     * @return an https connector
     */
    private static Connector createSSLConnector(File keystore, String password, boolean useNIO,
            int port) {
    private static Connector createSSLConnector(File keyStore, File clientTrustStore,
            String storePassword, File caRevocationList, boolean useNIO, int port,
            boolean requireClientCertificates) {
        SslContextFactory sslContext = new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH);
        SslConnector connector;
        if (useNIO) {
            logger.info("Setting up NIO SslSelectChannelConnector on port " + port);
            SslSelectChannelConnector ssl = new SslSelectChannelConnector();
            SslSelectChannelConnector ssl = new SslSelectChannelConnector(sslContext);
            ssl.setSoLingerTime(-1);
            if (requireClientCertificates) {
                sslContext.setNeedClientAuth(true);
            } else {
                sslContext.setWantClientAuth(true);
            }
            ssl.setThreadPool(new QueuedThreadPool(20));
            connector = ssl;
        } else {
            logger.info("Setting up NIO SslSocketConnector on port " + port);
            SslSocketConnector ssl = new SslSocketConnector();
            SslSocketConnector ssl = new SslSocketConnector(sslContext);
            connector = ssl;
        }
        // disable renegotiation unless this is a patched JVM
@@ -400,10 +435,13 @@
        }
        if (allowRenegotiation) {
            logger.info("   allowing SSL renegotiation on Java " + v);
            connector.setAllowRenegotiate(allowRenegotiation);
            sslContext.setAllowRenegotiate(allowRenegotiation);
        }
        connector.setKeystore(keystore.getAbsolutePath());
        connector.setPassword(password);
        sslContext.setKeyStorePath(keyStore.getAbsolutePath());
        sslContext.setKeyStorePassword(storePassword);
        sslContext.setTrustStore(clientTrustStore.getAbsolutePath());
        sslContext.setTrustStorePassword(storePassword);
        sslContext.setCrlPath(caRevocationList.getAbsolutePath());
        connector.setPort(port);
        connector.setMaxIdleTime(30000);
        return connector;
@@ -540,6 +578,9 @@
        @Parameter(names = "--shutdownPort", description = "Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor)")
        public Integer shutdownPort = FILESETTINGS.getInteger(Keys.server.shutdownPort, 8081);
        @Parameter(names = "--requireClientCertificates", description = "Require client X509 certificates for https connections.")
        public Boolean requireClientCertificates = FILESETTINGS.getBoolean(Keys.server.requireClientCertificates, false);
        /*
         * Setting overrides
         */
src/com/gitblit/MakeCertificate.java
@@ -29,8 +29,6 @@
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
@@ -62,57 +60,9 @@
            System.err.println(t.getMessage());
            jc.usage();
        }
        File keystore = new File("keystore");
        File keystore = new File("serverKeyStore.jks");
        generateSelfSignedCertificate(params.hostname, keystore, params.storePassword,
                params.subject);
    }
    public static void generateSelfSignedCertificate(String hostname, File keystore,
            String keystorePassword) {
        try {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
            kpGen.initialize(1024, new SecureRandom());
            KeyPair pair = kpGen.generateKeyPair();
            // Generate self-signed certificate
            X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
            builder.addRDN(BCStyle.OU, Constants.NAME);
            builder.addRDN(BCStyle.O, Constants.NAME);
            builder.addRDN(BCStyle.CN, hostname);
            Date notBefore = new Date(System.currentTimeMillis() - TimeUtils.ONEDAY);
            Date notAfter = new Date(System.currentTimeMillis() + 10 * TimeUtils.ONEYEAR);
            BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
            X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(),
                    serial, notBefore, notAfter, builder.build(), pair.getPublic());
            ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
                    .setProvider(BC).build(pair.getPrivate());
            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC)
                    .getCertificate(certGen.build(sigGen));
            cert.checkValidity(new Date());
            cert.verify(cert.getPublicKey());
            // Save to keystore
            KeyStore store = KeyStore.getInstance("JKS");
            if (keystore.exists()) {
                FileInputStream fis = new FileInputStream(keystore);
                store.load(fis, keystorePassword.toCharArray());
                fis.close();
            } else {
                store.load(null);
            }
            store.setKeyEntry(hostname, pair.getPrivate(), keystorePassword.toCharArray(),
                    new java.security.cert.Certificate[] { cert });
            FileOutputStream fos = new FileOutputStream(keystore);
            store.store(fos, keystorePassword.toCharArray());
            fos.close();
        } catch (Throwable t) {
            t.printStackTrace();
            throw new RuntimeException("Failed to generate self-signed certificate!", t);
        }
    }
    public static void generateSelfSignedCertificate(String hostname, File keystore,
@@ -120,7 +70,7 @@
        try {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", BC);
            kpGen.initialize(1024, new SecureRandom());
            KeyPair pair = kpGen.generateKeyPair();