123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173 |
- /*
- * 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.utils;
-
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.io.InputStream;
- import java.lang.reflect.Field;
- import java.math.BigInteger;
- import java.security.InvalidKeyException;
- import java.security.KeyPair;
- import java.security.KeyPairGenerator;
- import java.security.KeyStore;
- import java.security.NoSuchAlgorithmException;
- import java.security.PrivateKey;
- import java.security.SecureRandom;
- import java.security.Security;
- import java.security.SignatureException;
- import java.security.cert.CertPathBuilder;
- import java.security.cert.CertPathBuilderException;
- import java.security.cert.CertStore;
- import java.security.cert.Certificate;
- import java.security.cert.CertificateEncodingException;
- import java.security.cert.CertificateFactory;
- import java.security.cert.CollectionCertStoreParameters;
- import java.security.cert.PKIXBuilderParameters;
- import java.security.cert.PKIXCertPathBuilderResult;
- import java.security.cert.TrustAnchor;
- import java.security.cert.X509CRL;
- import java.security.cert.X509CertSelector;
- import java.security.cert.X509Certificate;
- import java.text.MessageFormat;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Calendar;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.TimeZone;
- import java.util.zip.ZipEntry;
- import java.util.zip.ZipOutputStream;
-
- import javax.crypto.Cipher;
- import javax.naming.ldap.LdapName;
-
- import org.bouncycastle.asn1.ASN1ObjectIdentifier;
- import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
- import org.bouncycastle.asn1.x500.X500Name;
- import org.bouncycastle.asn1.x500.X500NameBuilder;
- import org.bouncycastle.asn1.x500.style.BCStyle;
- import org.bouncycastle.asn1.x509.BasicConstraints;
- import org.bouncycastle.asn1.x509.GeneralName;
- import org.bouncycastle.asn1.x509.GeneralNames;
- import org.bouncycastle.asn1.x509.KeyUsage;
- import org.bouncycastle.asn1.x509.Extension;
- import org.bouncycastle.cert.X509CRLHolder;
- import org.bouncycastle.cert.X509v2CRLBuilder;
- import org.bouncycastle.cert.X509v3CertificateBuilder;
- import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
- import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
- import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
- import org.bouncycastle.jce.PrincipalUtil;
- import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
- import org.bouncycastle.openssl.PEMEncryptor;
- import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
- import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
- import org.bouncycastle.operator.ContentSigner;
- import org.bouncycastle.operator.OperatorCreationException;
- import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-
- import com.gitblit.Constants;
-
- /**
- * Utility class to generate X509 certificates, keystores, and truststores.
- *
- * @author James Moger
- *
- */
- public class X509Utils {
-
- public static final String SERVER_KEY_STORE = "serverKeyStore.jks";
-
- public static final String SERVER_TRUST_STORE = "serverTrustStore.jks";
-
- public static final String CERTS = "certs";
-
- public static final String CA_KEY_STORE = "certs/caKeyStore.p12";
-
- public static final String CA_REVOCATION_LIST = "certs/caRevocationList.crl";
-
- public static final String CA_CONFIG = "certs/authority.conf";
-
- public static final String CA_CN = "Gitblit Certificate Authority";
-
- public static final String CA_ALIAS = CA_CN;
-
- private static final String BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
-
- private static final int KEY_LENGTH = 2048;
-
- private static final String KEY_ALGORITHM = "RSA";
-
- private static final String SIGNING_ALGORITHM = "SHA512withRSA";
-
- public static final boolean unlimitedStrength;
-
- private static final Logger logger = LoggerFactory.getLogger(X509Utils.class);
-
- static {
- Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
-
- // check for JCE Unlimited Strength
- int maxKeyLen = 0;
- try {
- maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
- } catch (NoSuchAlgorithmException e) {
- }
-
- unlimitedStrength = maxKeyLen > 128;
- if (unlimitedStrength) {
- logger.info("Using JCE Unlimited Strength Jurisdiction Policy files");
- } else {
- logger.info("Using JCE Standard Encryption Policy files, encryption key lengths will be limited");
- }
- }
-
- public static enum RevocationReason {
- // https://en.wikipedia.org/wiki/Revocation_list
- unspecified, keyCompromise, caCompromise, affiliationChanged, superseded,
- cessationOfOperation, certificateHold, unused, removeFromCRL, privilegeWithdrawn,
- ACompromise;
-
- public static RevocationReason [] reasons = {
- unspecified, keyCompromise, caCompromise,
- affiliationChanged, superseded, cessationOfOperation,
- privilegeWithdrawn };
-
- @Override
- public String toString() {
- return name() + " (" + ordinal() + ")";
- }
- }
-
- public interface X509Log {
- void log(String message);
- }
-
- public static class X509Metadata {
-
- // map for distinguished name OIDs
- public final Map<String, String> oids;
-
- // CN in distingiushed name
- public final String commonName;
-
- // password for store
- public final String password;
-
- // password hint for README in bundle
- public String passwordHint;
-
- // E or EMAILADDRESS in distinguished name
- public String emailAddress;
-
- // start date of generated certificate
- public Date notBefore;
-
- // expiraiton date of generated certificate
- public Date notAfter;
-
- // hostname of server for which certificate is generated
- public String serverHostname;
-
- // displayname of user for README in bundle
- public String userDisplayname;
-
- // serialnumber of generated or read certificate
- public String serialNumber;
-
- public X509Metadata(String cn, String pwd) {
- if (StringUtils.isEmpty(cn)) {
- throw new RuntimeException("Common name required!");
- }
- if (StringUtils.isEmpty(pwd)) {
- throw new RuntimeException("Password required!");
- }
-
- commonName = cn;
- password = pwd;
- Calendar c = Calendar.getInstance(TimeZone.getDefault());
- c.set(Calendar.SECOND, 0);
- c.set(Calendar.MILLISECOND, 0);
- notBefore = c.getTime();
- c.add(Calendar.YEAR, 1);
- c.add(Calendar.DATE, 1);
- notAfter = c.getTime();
- oids = new HashMap<String, String>();
- }
-
- public X509Metadata clone(String commonName, String password) {
- X509Metadata clone = new X509Metadata(commonName, password);
- clone.emailAddress = emailAddress;
- clone.notBefore = notBefore;
- clone.notAfter = notAfter;
- clone.oids.putAll(oids);
- clone.passwordHint = passwordHint;
- clone.serverHostname = serverHostname;
- clone.userDisplayname = userDisplayname;
- return clone;
- }
-
- public String getOID(String oid, String defaultValue) {
- if (oids.containsKey(oid)) {
- return oids.get(oid);
- }
- return defaultValue;
- }
-
- public void setOID(String oid, String value) {
- if (StringUtils.isEmpty(value)) {
- oids.remove(oid);
- } else {
- oids.put(oid, value);
- }
- }
- }
-
- /**
- * Prepare all the certificates and stores necessary for a Gitblit GO server.
- *
- * @param metadata
- * @param folder
- * @param x509log
- */
- public static void prepareX509Infrastructure(X509Metadata metadata, File folder, X509Log x509log) {
- // make the specified folder, if necessary
- folder.mkdirs();
-
- // Gitblit CA certificate
- File caKeyStore = new File(folder, CA_KEY_STORE);
- if (!caKeyStore.exists()) {
- logger.info(MessageFormat.format("Generating {0} ({1})", CA_CN, caKeyStore.getAbsolutePath()));
- X509Certificate caCert = newCertificateAuthority(metadata, caKeyStore, x509log);
- saveCertificate(caCert, new File(caKeyStore.getParentFile(), "ca.cer"));
- }
-
- // Gitblit CRL
- File caRevocationList = new File(folder, CA_REVOCATION_LIST);
- if (!caRevocationList.exists()) {
- logger.info(MessageFormat.format("Generating {0} CRL ({1})", CA_CN, caRevocationList.getAbsolutePath()));
- newCertificateRevocationList(caRevocationList, caKeyStore, metadata.password);
- x509log.log("new certificate revocation list created");
- }
-
- // rename the old keystore to the new name
- File oldKeyStore = new File(folder, "keystore");
- if (oldKeyStore.exists()) {
- oldKeyStore.renameTo(new File(folder, SERVER_KEY_STORE));
- logger.info(MessageFormat.format("Renaming {0} to {1}", oldKeyStore.getName(), SERVER_KEY_STORE));
- }
-
- // create web SSL certificate signed by CA
- File serverKeyStore = new File(folder, SERVER_KEY_STORE);
- if (!serverKeyStore.exists()) {
- logger.info(MessageFormat.format("Generating SSL certificate for {0} signed by {1} ({2})", metadata.commonName, CA_CN, serverKeyStore.getAbsolutePath()));
- PrivateKey caPrivateKey = getPrivateKey(CA_ALIAS, caKeyStore, metadata.password);
- X509Certificate caCert = getCertificate(CA_ALIAS, caKeyStore, metadata.password);
- newSSLCertificate(metadata, caPrivateKey, caCert, serverKeyStore, x509log);
- }
-
- // server certificate trust store holds trusted public certificates
- File serverTrustStore = new File(folder, X509Utils.SERVER_TRUST_STORE);
- if (!serverTrustStore.exists()) {
- logger.info(MessageFormat.format("Importing {0} into trust store ({1})", CA_ALIAS, serverTrustStore.getAbsolutePath()));
- X509Certificate caCert = getCertificate(CA_ALIAS, caKeyStore, metadata.password);
- addTrustedCertificate(CA_ALIAS, caCert, serverTrustStore, metadata.password);
- }
- }
-
- /**
- * Open a keystore. Store type is determined by file extension of name. If
- * undetermined, JKS is assumed. The keystore does not need to exist.
- *
- * @param storeFile
- * @param storePassword
- * @return a KeyStore
- */
- public static KeyStore openKeyStore(File storeFile, String storePassword) {
- String lc = storeFile.getName().toLowerCase();
- String type = "JKS";
- String provider = null;
- if (lc.endsWith(".p12") || lc.endsWith(".pfx")) {
- type = "PKCS12";
- provider = BC;
- }
-
- try {
- KeyStore store;
- if (provider == null) {
- store = KeyStore.getInstance(type);
- } else {
- store = KeyStore.getInstance(type, provider);
- }
- if (storeFile.exists()) {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(storeFile);
- store.load(fis, storePassword.toCharArray());
- } finally {
- if (fis != null) {
- fis.close();
- }
- }
- } else {
- store.load(null);
- }
- return store;
- } catch (Exception e) {
- throw new RuntimeException("Could not open keystore " + storeFile, e);
- }
- }
-
- /**
- * Saves the keystore to the specified file.
- *
- * @param targetStoreFile
- * @param store
- * @param password
- */
- public static void saveKeyStore(File targetStoreFile, KeyStore store, String password) {
- File folder = targetStoreFile.getAbsoluteFile().getParentFile();
- if (!folder.exists()) {
- folder.mkdirs();
- }
- File tmpFile = new File(folder, Long.toHexString(System.currentTimeMillis()) + ".tmp");
- FileOutputStream fos = null;
- try {
- fos = new FileOutputStream(tmpFile);
- store.store(fos, password.toCharArray());
- fos.flush();
- fos.close();
- if (targetStoreFile.exists()) {
- targetStoreFile.delete();
- }
- tmpFile.renameTo(targetStoreFile);
- } catch (IOException e) {
- String message = e.getMessage().toLowerCase();
- if (message.contains("illegal key size")) {
- throw new RuntimeException("Illegal Key Size! You might consider installing the JCE Unlimited Strength Jurisdiction Policy files for your JVM.");
- } else {
- throw new RuntimeException("Could not save keystore " + targetStoreFile, e);
- }
- } catch (Exception e) {
- throw new RuntimeException("Could not save keystore " + targetStoreFile, e);
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- }
- }
-
- if (tmpFile.exists()) {
- tmpFile.delete();
- }
- }
- }
-
- /**
- * Retrieves the X509 certificate with the specified alias from the certificate
- * store.
- *
- * @param alias
- * @param storeFile
- * @param storePassword
- * @return the certificate
- */
- public static X509Certificate getCertificate(String alias, File storeFile, String storePassword) {
- try {
- KeyStore store = openKeyStore(storeFile, storePassword);
- X509Certificate caCert = (X509Certificate) store.getCertificate(alias);
- return caCert;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Retrieves the private key for the specified alias from the certificate
- * store.
- *
- * @param alias
- * @param storeFile
- * @param storePassword
- * @return the private key
- */
- public static PrivateKey getPrivateKey(String alias, File storeFile, String storePassword) {
- try {
- KeyStore store = openKeyStore(storeFile, storePassword);
- PrivateKey key = (PrivateKey) store.getKey(alias, storePassword.toCharArray());
- return key;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Saves the certificate to the file system. If the destination filename
- * ends with the pem extension, the certificate is written in the PEM format,
- * otherwise the certificate is written in the DER format.
- *
- * @param cert
- * @param targetFile
- */
- public static void saveCertificate(X509Certificate cert, File targetFile) {
- File folder = targetFile.getAbsoluteFile().getParentFile();
- if (!folder.exists()) {
- folder.mkdirs();
- }
- File tmpFile = new File(folder, Long.toHexString(System.currentTimeMillis()) + ".tmp");
- try {
- boolean asPem = targetFile.getName().toLowerCase().endsWith(".pem");
- if (asPem) {
- // PEM encoded X509
- JcaPEMWriter pemWriter = null;
- try {
- pemWriter = new JcaPEMWriter(new FileWriter(tmpFile));
- pemWriter.writeObject(cert);
- pemWriter.flush();
- } finally {
- if (pemWriter != null) {
- pemWriter.close();
- }
- }
- } else {
- // DER encoded X509
- FileOutputStream fos = null;
- try {
- fos = new FileOutputStream(tmpFile);
- fos.write(cert.getEncoded());
- fos.flush();
- } finally {
- if (fos != null) {
- fos.close();
- }
- }
- }
-
- // rename tmp file to target
- if (targetFile.exists()) {
- targetFile.delete();
- }
- tmpFile.renameTo(targetFile);
- } catch (Exception e) {
- if (tmpFile.exists()) {
- tmpFile.delete();
- }
- throw new RuntimeException("Failed to save certificate " + cert.getSubjectX500Principal().getName(), e);
- }
- }
-
- /**
- * Generate a new keypair.
- *
- * @return a keypair
- * @throws Exception
- */
- private static KeyPair newKeyPair() throws Exception {
- KeyPairGenerator kpGen = KeyPairGenerator.getInstance(KEY_ALGORITHM, BC);
- kpGen.initialize(KEY_LENGTH, new SecureRandom());
- return kpGen.generateKeyPair();
- }
-
- /**
- * Builds a distinguished name from the X509Metadata.
- *
- * @return a DN
- */
- private static X500Name buildDistinguishedName(X509Metadata metadata) {
- X500NameBuilder dnBuilder = new X500NameBuilder(BCStyle.INSTANCE);
- setOID(dnBuilder, metadata, "C", null);
- setOID(dnBuilder, metadata, "ST", null);
- setOID(dnBuilder, metadata, "L", null);
- setOID(dnBuilder, metadata, "O", Constants.NAME);
- setOID(dnBuilder, metadata, "OU", Constants.NAME);
- setOID(dnBuilder, metadata, "E", metadata.emailAddress);
- setOID(dnBuilder, metadata, "CN", metadata.commonName);
- X500Name dn = dnBuilder.build();
- return dn;
- }
-
- private static void setOID(X500NameBuilder dnBuilder, X509Metadata metadata,
- String oid, String defaultValue) {
-
- String value = null;
- if (metadata.oids != null && metadata.oids.containsKey(oid)) {
- value = metadata.oids.get(oid);
- }
- if (StringUtils.isEmpty(value)) {
- value = defaultValue;
- }
-
- if (!StringUtils.isEmpty(value)) {
- try {
- Field field = BCStyle.class.getField(oid);
- ASN1ObjectIdentifier objectId = (ASN1ObjectIdentifier) field.get(null);
- dnBuilder.addRDN(objectId, value);
- } catch (Exception e) {
- logger.error(MessageFormat.format("Failed to set OID \"{0}\"!", oid) ,e);
- }
- }
- }
-
- /**
- * Creates a new SSL certificate signed by the CA private key and stored in
- * keyStore.
- *
- * @param sslMetadata
- * @param caPrivateKey
- * @param caCert
- * @param targetStoreFile
- * @param x509log
- */
- public static X509Certificate newSSLCertificate(X509Metadata sslMetadata, PrivateKey caPrivateKey, X509Certificate caCert, File targetStoreFile, X509Log x509log) {
- try {
- KeyPair pair = newKeyPair();
-
- X500Name webDN = buildDistinguishedName(sslMetadata);
- X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(caCert).getName());
-
- X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
- issuerDN,
- BigInteger.valueOf(System.currentTimeMillis()),
- sslMetadata.notBefore,
- sslMetadata.notAfter,
- webDN,
- pair.getPublic());
-
- JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
- certBuilder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pair.getPublic()));
- certBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
- certBuilder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey()));
-
- // support alternateSubjectNames for SSL certificates
- List<GeneralName> altNames = new ArrayList<GeneralName>();
- if (HttpUtils.isIpAddress(sslMetadata.commonName)) {
- altNames.add(new GeneralName(GeneralName.iPAddress, sslMetadata.commonName));
- }
- if (altNames.size() > 0) {
- GeneralNames subjectAltName = new GeneralNames(altNames.toArray(new GeneralName [altNames.size()]));
- certBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltName);
- }
-
- ContentSigner caSigner = new JcaContentSignerBuilder(SIGNING_ALGORITHM)
- .setProvider(BC).build(caPrivateKey);
- X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC)
- .getCertificate(certBuilder.build(caSigner));
-
- cert.checkValidity(new Date());
- cert.verify(caCert.getPublicKey());
-
- // Save to keystore
- KeyStore serverStore = openKeyStore(targetStoreFile, sslMetadata.password);
- serverStore.setKeyEntry(sslMetadata.commonName, pair.getPrivate(), sslMetadata.password.toCharArray(),
- new Certificate[] { cert, caCert });
- saveKeyStore(targetStoreFile, serverStore, sslMetadata.password);
-
- x509log.log(MessageFormat.format("New SSL certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getSubjectDN().getName()));
-
- // update serial number in metadata object
- sslMetadata.serialNumber = cert.getSerialNumber().toString();
-
- return cert;
- } catch (Throwable t) {
- throw new RuntimeException("Failed to generate SSL certificate!", t);
- }
- }
-
- /**
- * Creates a new certificate authority PKCS#12 store. This function will
- * destroy any existing CA store.
- *
- * @param metadata
- * @param storeFile
- * @param keystorePassword
- * @param x509log
- * @return
- */
- public static X509Certificate newCertificateAuthority(X509Metadata metadata, File storeFile, X509Log x509log) {
- try {
- KeyPair caPair = newKeyPair();
-
- ContentSigner caSigner = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPair.getPrivate());
-
- // clone metadata
- X509Metadata caMetadata = metadata.clone(CA_CN, metadata.password);
- X500Name issuerDN = buildDistinguishedName(caMetadata);
-
- // Generate self-signed certificate
- X509v3CertificateBuilder caBuilder = new JcaX509v3CertificateBuilder(
- issuerDN,
- BigInteger.valueOf(System.currentTimeMillis()),
- caMetadata.notBefore,
- caMetadata.notAfter,
- issuerDN,
- caPair.getPublic());
-
- JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
- caBuilder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(caPair.getPublic()));
- caBuilder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caPair.getPublic()));
- caBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(true));
- caBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign));
-
- JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC);
- X509Certificate cert = converter.getCertificate(caBuilder.build(caSigner));
-
- // confirm the validity of the CA certificate
- cert.checkValidity(new Date());
- cert.verify(cert.getPublicKey());
-
- // Delete existing keystore
- if (storeFile.exists()) {
- storeFile.delete();
- }
-
- // Save private key and certificate to new keystore
- KeyStore store = openKeyStore(storeFile, caMetadata.password);
- store.setKeyEntry(CA_ALIAS, caPair.getPrivate(), caMetadata.password.toCharArray(),
- new Certificate[] { cert });
- saveKeyStore(storeFile, store, caMetadata.password);
-
- x509log.log(MessageFormat.format("New CA certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getIssuerDN().getName()));
-
- // update serial number in metadata object
- caMetadata.serialNumber = cert.getSerialNumber().toString();
-
- return cert;
- } catch (Throwable t) {
- throw new RuntimeException("Failed to generate Gitblit CA certificate!", t);
- }
- }
-
- /**
- * Creates a new certificate revocation list (CRL). This function will
- * destroy any existing CRL file.
- *
- * @param caRevocationList
- * @param storeFile
- * @param keystorePassword
- * @return
- */
- public static void newCertificateRevocationList(File caRevocationList, File caKeystoreFile, String caKeystorePassword) {
- try {
- // read the Gitblit CA key and certificate
- KeyStore store = openKeyStore(caKeystoreFile, caKeystorePassword);
- PrivateKey caPrivateKey = (PrivateKey) store.getKey(CA_ALIAS, caKeystorePassword.toCharArray());
- X509Certificate caCert = (X509Certificate) store.getCertificate(CA_ALIAS);
-
- X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(caCert).getName());
- X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuerDN, new Date());
-
- // build and sign CRL with CA private key
- ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPrivateKey);
- X509CRLHolder crl = crlBuilder.build(signer);
-
- File tmpFile = new File(caRevocationList.getParentFile(), Long.toHexString(System.currentTimeMillis()) + ".tmp");
- FileOutputStream fos = null;
- try {
- fos = new FileOutputStream(tmpFile);
- fos.write(crl.getEncoded());
- fos.flush();
- fos.close();
- if (caRevocationList.exists()) {
- caRevocationList.delete();
- }
- tmpFile.renameTo(caRevocationList);
- } finally {
- if (fos != null) {
- fos.close();
- }
- if (tmpFile.exists()) {
- tmpFile.delete();
- }
- }
- } catch (Exception e) {
- throw new RuntimeException("Failed to create new certificate revocation list " + caRevocationList, e);
- }
- }
-
- /**
- * Imports a certificate into the trust store.
- *
- * @param alias
- * @param cert
- * @param storeFile
- * @param storePassword
- */
- public static void addTrustedCertificate(String alias, X509Certificate cert, File storeFile, String storePassword) {
- try {
- KeyStore store = openKeyStore(storeFile, storePassword);
- store.setCertificateEntry(alias, cert);
- saveKeyStore(storeFile, store, storePassword);
- } catch (Exception e) {
- throw new RuntimeException("Failed to import certificate into trust store " + storeFile, e);
- }
- }
-
- /**
- * Creates a new client certificate PKCS#12 and PEM store. Any existing
- * stores are destroyed. After generation, the certificates are bundled
- * into a zip file with a personalized README file.
- *
- * The zip file reference is returned.
- *
- * @param clientMetadata a container for dynamic parameters needed for generation
- * @param caKeystoreFile
- * @param caKeystorePassword
- * @param x509log
- * @return a zip file containing the P12, PEM, and personalized README
- */
- public static File newClientBundle(X509Metadata clientMetadata, File caKeystoreFile,
- String caKeystorePassword, X509Log x509log) {
- return newClientBundle(null,clientMetadata,caKeystoreFile,caKeystorePassword,x509log);
- }
-
- /**
- * Creates a new client certificate PKCS#12 and PEM store. Any existing
- * stores are destroyed. After generation, the certificates are bundled
- * into a zip file with a personalized README file.
- *
- * The zip file reference is returned.
- *
- * @param user
- * @param clientMetadata a container for dynamic parameters needed for generation
- * @param caKeystoreFile
- * @param caKeystorePassword
- * @param x509log
- * @return a zip file containing the P12, PEM, and personalized README
- */
- public static File newClientBundle(com.gitblit.models.UserModel user,X509Metadata clientMetadata, File caKeystoreFile,
- String caKeystorePassword, X509Log x509log) {
- try {
- // read the Gitblit CA key and certificate
- KeyStore store = openKeyStore(caKeystoreFile, caKeystorePassword);
- PrivateKey caPrivateKey = (PrivateKey) store.getKey(CA_ALIAS, caKeystorePassword.toCharArray());
- X509Certificate caCert = (X509Certificate) store.getCertificate(CA_ALIAS);
-
- // generate the P12 and PEM files
- File targetFolder = new File(caKeystoreFile.getParentFile(), clientMetadata.commonName);
- X509Certificate cert = newClientCertificate(clientMetadata, caPrivateKey, caCert, targetFolder);
- x509log.log(MessageFormat.format("New client certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getSubjectDN().getName()));
-
- // process template message
- String readme = null;
- String sInstructionsFileName = "instructions.tmpl";
- if( user == null )
- readme = processTemplate(new File(caKeystoreFile.getParentFile(),sInstructionsFileName), clientMetadata);
- else{
- File fileInstructionsTmp = null;
- if( (fileInstructionsTmp = new File(caKeystoreFile.getParentFile(),sInstructionsFileName+"_"+user.getPreferences().getLocale())).exists() )
- readme = processTemplate(fileInstructionsTmp,clientMetadata);
- else
- readme = processTemplate(new File(caKeystoreFile.getParentFile(),sInstructionsFileName),clientMetadata);
- }
- // Create a zip bundle with the p12, pem, and a personalized readme
- File zipFile = new File(targetFolder, clientMetadata.commonName + ".zip");
- if (zipFile.exists()) {
- zipFile.delete();
- }
- ZipOutputStream zos = null;
- try {
- zos = new ZipOutputStream(new FileOutputStream(zipFile));
- File p12File = new File(targetFolder, clientMetadata.commonName + ".p12");
- if (p12File.exists()) {
- zos.putNextEntry(new ZipEntry(p12File.getName()));
- zos.write(FileUtils.readContent(p12File));
- zos.closeEntry();
- }
- File pemFile = new File(targetFolder, clientMetadata.commonName + ".pem");
- if (pemFile.exists()) {
- zos.putNextEntry(new ZipEntry(pemFile.getName()));
- zos.write(FileUtils.readContent(pemFile));
- zos.closeEntry();
- }
-
- // include user's public certificate
- zos.putNextEntry(new ZipEntry(clientMetadata.commonName + ".cer"));
- zos.write(cert.getEncoded());
- zos.closeEntry();
-
- // include CA public certificate
- zos.putNextEntry(new ZipEntry("ca.cer"));
- zos.write(caCert.getEncoded());
- zos.closeEntry();
-
- if (readme != null) {
- zos.putNextEntry(new ZipEntry("README.TXT"));
- zos.write(readme.getBytes("UTF-8"));
- zos.closeEntry();
- }
- zos.flush();
- } finally {
- if (zos != null) {
- zos.close();
- }
- }
-
- return zipFile;
- } catch (Throwable t) {
- throw new RuntimeException("Failed to generate client bundle!", t);
- }
- }
-
- /**
- * Creates a new client certificate PKCS#12 and PEM store. Any existing
- * stores are destroyed.
- *
- * @param clientMetadata a container for dynamic parameters needed for generation
- * @param caKeystoreFile
- * @param caKeystorePassword
- * @param targetFolder
- * @return
- */
- public static X509Certificate newClientCertificate(X509Metadata clientMetadata,
- PrivateKey caPrivateKey, X509Certificate caCert, File targetFolder) {
- try {
- KeyPair pair = newKeyPair();
-
- X500Name userDN = buildDistinguishedName(clientMetadata);
- X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(caCert).getName());
-
- // create a new certificate signed by the Gitblit CA certificate
- X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
- issuerDN,
- BigInteger.valueOf(System.currentTimeMillis()),
- clientMetadata.notBefore,
- clientMetadata.notAfter,
- userDN,
- pair.getPublic());
-
- JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
- certBuilder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pair.getPublic()));
- certBuilder.addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
- certBuilder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey()));
- certBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyEncipherment | KeyUsage.digitalSignature));
- if (!StringUtils.isEmpty(clientMetadata.emailAddress)) {
- GeneralNames subjectAltName = new GeneralNames(
- new GeneralName(GeneralName.rfc822Name, clientMetadata.emailAddress));
- certBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltName);
- }
-
- ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPrivateKey);
-
- X509Certificate userCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certBuilder.build(signer));
- PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)pair.getPrivate();
- bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
- extUtils.createSubjectKeyIdentifier(pair.getPublic()));
-
- // confirm the validity of the user certificate
- userCert.checkValidity();
- userCert.verify(caCert.getPublicKey());
- userCert.getIssuerDN().equals(caCert.getSubjectDN());
-
- // verify user certificate chain
- verifyChain(userCert, caCert);
-
- targetFolder.mkdirs();
-
- // save certificate, stamped with unique name
- String date = new SimpleDateFormat("yyyyMMdd").format(new Date());
- String id = date;
- File certFile = new File(targetFolder, id + ".cer");
- int count = 0;
- while (certFile.exists()) {
- id = date + "_" + Character.toString((char) (0x61 + count));
- certFile = new File(targetFolder, id + ".cer");
- count++;
- }
-
- // save user private key, user certificate and CA certificate to a PKCS#12 store
- File p12File = new File(targetFolder, clientMetadata.commonName + ".p12");
- if (p12File.exists()) {
- p12File.delete();
- }
- KeyStore userStore = openKeyStore(p12File, clientMetadata.password);
- userStore.setKeyEntry(MessageFormat.format("Gitblit ({0}) {1} {2}", clientMetadata.serverHostname, clientMetadata.userDisplayname, id), pair.getPrivate(), null, new Certificate [] { userCert });
- userStore.setCertificateEntry(MessageFormat.format("Gitblit ({0}) Certificate Authority", clientMetadata.serverHostname), caCert);
- saveKeyStore(p12File, userStore, clientMetadata.password);
-
- // save user private key, user certificate, and CA certificate to a PEM store
- File pemFile = new File(targetFolder, clientMetadata.commonName + ".pem");
- if (pemFile.exists()) {
- pemFile.delete();
- }
- JcePEMEncryptorBuilder builder = new JcePEMEncryptorBuilder("DES-EDE3-CBC");
- builder.setSecureRandom(new SecureRandom());
- PEMEncryptor pemEncryptor = builder.build(clientMetadata.password.toCharArray());
- JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(pemFile));
- pemWriter.writeObject(pair.getPrivate(), pemEncryptor);
- pemWriter.writeObject(userCert);
- pemWriter.writeObject(caCert);
- pemWriter.flush();
- pemWriter.close();
-
- // save certificate after successfully creating the key stores
- saveCertificate(userCert, certFile);
-
- // update serial number in metadata object
- clientMetadata.serialNumber = userCert.getSerialNumber().toString();
-
- return userCert;
- } catch (Throwable t) {
- throw new RuntimeException("Failed to generate client certificate!", t);
- }
- }
-
- /**
- * Verifies a certificate's chain to ensure that it will function properly.
- *
- * @param testCert
- * @param additionalCerts
- * @return
- */
- public static PKIXCertPathBuilderResult verifyChain(X509Certificate testCert, X509Certificate... additionalCerts) {
- try {
- // Check for self-signed certificate
- if (isSelfSigned(testCert)) {
- throw new RuntimeException("The certificate is self-signed. Nothing to verify.");
- }
-
- // Prepare a set of all certificates
- // chain builder must have all certs, including cert to validate
- // http://stackoverflow.com/a/10788392
- Set<X509Certificate> certs = new HashSet<X509Certificate>();
- certs.add(testCert);
- certs.addAll(Arrays.asList(additionalCerts));
-
- // Attempt to build the certification chain and verify it
- // Create the selector that specifies the starting certificate
- X509CertSelector selector = new X509CertSelector();
- selector.setCertificate(testCert);
-
- // Create the trust anchors (set of root CA certificates)
- Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
- for (X509Certificate cert : additionalCerts) {
- if (isSelfSigned(cert)) {
- trustAnchors.add(new TrustAnchor(cert, null));
- }
- }
-
- // Configure the PKIX certificate builder
- PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
- pkixParams.setRevocationEnabled(false);
- pkixParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certs), BC));
-
- // Build and verify the certification chain
- CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", BC);
- PKIXCertPathBuilderResult verifiedCertChain = (PKIXCertPathBuilderResult) builder.build(pkixParams);
-
- // The chain is built and verified
- return verifiedCertChain;
- } catch (CertPathBuilderException e) {
- throw new RuntimeException("Error building certification path: " + testCert.getSubjectX500Principal(), e);
- } catch (Exception e) {
- throw new RuntimeException("Error verifying the certificate: " + testCert.getSubjectX500Principal(), e);
- }
- }
-
- /**
- * Checks whether given X.509 certificate is self-signed.
- *
- * @param cert
- * @return true if the certificate is self-signed
- */
- public static boolean isSelfSigned(X509Certificate cert) {
- try {
- cert.verify(cert.getPublicKey());
- return true;
- } catch (SignatureException e) {
- return false;
- } catch (InvalidKeyException e) {
- return false;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public static String processTemplate(File template, X509Metadata metadata) {
- String content = null;
- if (template.exists()) {
- String message = FileUtils.readContent(template, "\n");
- if (!StringUtils.isEmpty(message)) {
- content = message;
- if (!StringUtils.isEmpty(metadata.serverHostname)) {
- content = content.replace("$serverHostname", metadata.serverHostname);
- }
- if (!StringUtils.isEmpty(metadata.commonName)) {
- content = content.replace("$username", metadata.commonName);
- }
- if (!StringUtils.isEmpty(metadata.userDisplayname)) {
- content = content.replace("$userDisplayname", metadata.userDisplayname);
- }
- if (!StringUtils.isEmpty(metadata.passwordHint)) {
- content = content.replace("$storePasswordHint", metadata.passwordHint);
- }
- }
- }
- return content;
- }
-
- /**
- * Revoke a certificate.
- *
- * @param cert
- * @param reason
- * @param caRevocationList
- * @param caKeystoreFile
- * @param caKeystorePassword
- * @param x509log
- * @return true if the certificate has been revoked
- */
- public static boolean revoke(X509Certificate cert, RevocationReason reason,
- File caRevocationList, File caKeystoreFile, String caKeystorePassword,
- X509Log x509log) {
- try {
- // read the Gitblit CA key and certificate
- KeyStore store = openKeyStore(caKeystoreFile, caKeystorePassword);
- PrivateKey caPrivateKey = (PrivateKey) store.getKey(CA_ALIAS, caKeystorePassword.toCharArray());
- return revoke(cert, reason, caRevocationList, caPrivateKey, x509log);
- } catch (Exception e) {
- logger.error(MessageFormat.format("Failed to revoke certificate {0,number,0} [{1}] in {2}",
- cert.getSerialNumber(), cert.getSubjectDN().getName(), caRevocationList));
- }
- return false;
- }
-
- /**
- * Revoke a certificate.
- *
- * @param cert
- * @param reason
- * @param caRevocationList
- * @param caPrivateKey
- * @param x509log
- * @return true if the certificate has been revoked
- */
- public static boolean revoke(X509Certificate cert, RevocationReason reason,
- File caRevocationList, PrivateKey caPrivateKey, X509Log x509log) {
- try {
- X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(cert).getName());
- X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuerDN, new Date());
- if (caRevocationList.exists()) {
- byte [] data = FileUtils.readContent(caRevocationList);
- X509CRLHolder crl = new X509CRLHolder(data);
- crlBuilder.addCRL(crl);
- }
- crlBuilder.addCRLEntry(cert.getSerialNumber(), new Date(), reason.ordinal());
-
- // build and sign CRL with CA private key
- ContentSigner signer = new JcaContentSignerBuilder("SHA1WithRSA").setProvider(BC).build(caPrivateKey);
- X509CRLHolder crl = crlBuilder.build(signer);
-
- File tmpFile = new File(caRevocationList.getParentFile(), Long.toHexString(System.currentTimeMillis()) + ".tmp");
- FileOutputStream fos = null;
- try {
- fos = new FileOutputStream(tmpFile);
- fos.write(crl.getEncoded());
- fos.flush();
- fos.close();
- if (caRevocationList.exists()) {
- caRevocationList.delete();
- }
- tmpFile.renameTo(caRevocationList);
-
- } finally {
- if (fos != null) {
- fos.close();
- }
- if (tmpFile.exists()) {
- tmpFile.delete();
- }
- }
-
- x509log.log(MessageFormat.format("Revoked certificate {0,number,0} reason: {1} [{2}]",
- cert.getSerialNumber(), reason.toString(), cert.getSubjectDN().getName()));
- return true;
- } catch (IOException | OperatorCreationException | CertificateEncodingException e) {
- logger.error(MessageFormat.format("Failed to revoke certificate {0,number,0} [{1}] in {2}",
- cert.getSerialNumber(), cert.getSubjectDN().getName(), caRevocationList));
- }
- return false;
- }
-
- /**
- * Returns true if the certificate has been revoked.
- *
- * @param cert
- * @param caRevocationList
- * @return true if the certificate is revoked
- */
- public static boolean isRevoked(X509Certificate cert, File caRevocationList) {
- if (!caRevocationList.exists()) {
- return false;
- }
- InputStream inStream = null;
- try {
- inStream = new FileInputStream(caRevocationList);
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- X509CRL crl = (X509CRL)cf.generateCRL(inStream);
- return crl.isRevoked(cert);
- } catch (Exception e) {
- logger.error(MessageFormat.format("Failed to check revocation status for certificate {0,number,0} [{1}] in {2}",
- cert.getSerialNumber(), cert.getSubjectDN().getName(), caRevocationList));
- } finally {
- if (inStream != null) {
- try {
- inStream.close();
- } catch (Exception e) {
- }
- }
- }
- return false;
- }
-
- public static X509Metadata getMetadata(X509Certificate cert) {
- Map<String, String> oids = new HashMap<String, String>();
- try {
- String dn = cert.getSubjectDN().getName();
- LdapName ldapName = new LdapName(dn);
- for (int i = 0; i < ldapName.size(); i++) {
- String [] val = ldapName.get(i).trim().split("=", 2);
- String oid = val[0].toUpperCase().trim();
- String data = val[1].trim();
- oids.put(oid, data);
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- X509Metadata metadata = new X509Metadata(oids.get("CN"), "whocares");
- metadata.oids.putAll(oids);
- metadata.serialNumber = cert.getSerialNumber().toString();
- metadata.notAfter = cert.getNotAfter();
- metadata.notBefore = cert.getNotBefore();
- metadata.emailAddress = metadata.getOID("E", null);
- if (metadata.emailAddress == null) {
- metadata.emailAddress = metadata.getOID("EMAILADDRESS", null);
- }
- return metadata;
- }
- }
|