James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
b42358 1 /*
JM 2  * Copyright 2012 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.utils;
17
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.FileOutputStream;
21 import java.io.FileWriter;
22 import java.io.IOException;
f185d9 23 import java.io.InputStream;
b42358 24 import java.lang.reflect.Field;
JM 25 import java.math.BigInteger;
26 import java.security.InvalidKeyException;
27 import java.security.KeyPair;
28 import java.security.KeyPairGenerator;
29 import java.security.KeyStore;
30 import java.security.NoSuchAlgorithmException;
31 import java.security.PrivateKey;
32 import java.security.SecureRandom;
33 import java.security.Security;
34 import java.security.SignatureException;
35 import java.security.cert.CertPathBuilder;
36 import java.security.cert.CertPathBuilderException;
37 import java.security.cert.CertStore;
38 import java.security.cert.Certificate;
aff53a 39 import java.security.cert.CertificateEncodingException;
f185d9 40 import java.security.cert.CertificateFactory;
b42358 41 import java.security.cert.CollectionCertStoreParameters;
JM 42 import java.security.cert.PKIXBuilderParameters;
43 import java.security.cert.PKIXCertPathBuilderResult;
44 import java.security.cert.TrustAnchor;
45 import java.security.cert.X509CRL;
46 import java.security.cert.X509CertSelector;
47 import java.security.cert.X509Certificate;
48 import java.text.MessageFormat;
49 import java.text.SimpleDateFormat;
d3c189 50 import java.util.ArrayList;
b42358 51 import java.util.Arrays;
JM 52 import java.util.Calendar;
53 import java.util.Date;
54 import java.util.HashMap;
55 import java.util.HashSet;
d3c189 56 import java.util.List;
b42358 57 import java.util.Map;
JM 58 import java.util.Set;
59 import java.util.TimeZone;
60 import java.util.zip.ZipEntry;
61 import java.util.zip.ZipOutputStream;
62
63 import javax.crypto.Cipher;
08afff 64 import javax.naming.ldap.LdapName;
b42358 65
JM 66 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
67 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
68 import org.bouncycastle.asn1.x500.X500Name;
69 import org.bouncycastle.asn1.x500.X500NameBuilder;
70 import org.bouncycastle.asn1.x500.style.BCStyle;
71 import org.bouncycastle.asn1.x509.BasicConstraints;
72 import org.bouncycastle.asn1.x509.GeneralName;
73 import org.bouncycastle.asn1.x509.GeneralNames;
74 import org.bouncycastle.asn1.x509.KeyUsage;
75 import org.bouncycastle.asn1.x509.X509Extension;
76 import org.bouncycastle.cert.X509CRLHolder;
77 import org.bouncycastle.cert.X509v2CRLBuilder;
78 import org.bouncycastle.cert.X509v3CertificateBuilder;
79 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
80 import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
81 import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
82 import org.bouncycastle.jce.PrincipalUtil;
83 import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
006651 84 import org.bouncycastle.openssl.PEMEncryptor;
b42358 85 import org.bouncycastle.openssl.PEMWriter;
006651 86 import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
JM 87 import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
b42358 88 import org.bouncycastle.operator.ContentSigner;
aff53a 89 import org.bouncycastle.operator.OperatorCreationException;
b42358 90 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
JM 91 import org.slf4j.Logger;
92 import org.slf4j.LoggerFactory;
93
94 import com.gitblit.Constants;
95
96 /**
97  * Utility class to generate X509 certificates, keystores, and truststores.
699e71 98  *
b42358 99  * @author James Moger
699e71 100  *
b42358 101  */
JM 102 public class X509Utils {
699e71 103
b42358 104     public static final String SERVER_KEY_STORE = "serverKeyStore.jks";
699e71 105
b42358 106     public static final String SERVER_TRUST_STORE = "serverTrustStore.jks";
JM 107
108     public static final String CERTS = "certs";
699e71 109
b42358 110     public static final String CA_KEY_STORE = "certs/caKeyStore.p12";
JM 111
112     public static final String CA_REVOCATION_LIST = "certs/caRevocationList.crl";
699e71 113
b42358 114     public static final String CA_CONFIG = "certs/authority.conf";
JM 115
116     public static final String CA_CN = "Gitblit Certificate Authority";
699e71 117
c8b26c 118     public static final String CA_ALIAS = CA_CN;
b42358 119
JM 120     private static final String BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
699e71 121
1687ae 122     private static final int KEY_LENGTH = 2048;
699e71 123
1687ae 124     private static final String KEY_ALGORITHM = "RSA";
699e71 125
1687ae 126     private static final String SIGNING_ALGORITHM = "SHA512withRSA";
699e71 127
b42358 128     public static final boolean unlimitedStrength;
699e71 129
b42358 130     private static final Logger logger = LoggerFactory.getLogger(X509Utils.class);
699e71 131
b42358 132     static {
JM 133         Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
699e71 134
b42358 135         // check for JCE Unlimited Strength
JM 136         int maxKeyLen = 0;
137         try {
138             maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
139         } catch (NoSuchAlgorithmException e) {
140         }
141
142         unlimitedStrength = maxKeyLen > 128;
143         if (unlimitedStrength) {
144             logger.info("Using JCE Unlimited Strength Jurisdiction Policy files");
145         } else {
146             logger.info("Using JCE Standard Encryption Policy files, encryption key lengths will be limited");
147         }
148     }
699e71 149
b42358 150     public static enum RevocationReason {
JM 151         // https://en.wikipedia.org/wiki/Revocation_list
152          unspecified, keyCompromise, caCompromise, affiliationChanged, superseded,
153          cessationOfOperation, certificateHold, unused, removeFromCRL, privilegeWithdrawn,
154          ACompromise;
699e71 155
b42358 156          public static RevocationReason [] reasons = {
699e71 157                  unspecified, keyCompromise, caCompromise,
JM 158                  affiliationChanged, superseded, cessationOfOperation,
b42358 159                  privilegeWithdrawn };
699e71 160
b42358 161          @Override
JM 162          public String toString() {
163              return name() +  " (" + ordinal() + ")";
164          }
c8b26c 165     }
699e71 166
c8b26c 167     public interface X509Log {
JM 168         void log(String message);
b42358 169     }
699e71 170
b42358 171     public static class X509Metadata {
JM 172
173         // map for distinguished name OIDs
174         public final Map<String, String> oids;
175
176         // CN in distingiushed name
177         public final String commonName;
699e71 178
b42358 179         // password for store
JM 180         public final String password;
699e71 181
b42358 182         // password hint for README in bundle
JM 183         public String passwordHint;
184
185         // E or EMAILADDRESS in distinguished name
186         public String emailAddress;
187
188         // start date of generated certificate
189         public Date notBefore;
699e71 190
b42358 191         // expiraiton date of generated certificate
JM 192         public Date notAfter;
699e71 193
b42358 194         // hostname of server for which certificate is generated
JM 195         public String serverHostname;
699e71 196
b42358 197         // displayname of user for README in bundle
JM 198         public String userDisplayname;
199
5f3966 200         // serialnumber of generated or read certificate
JM 201         public String serialNumber;
202
b42358 203         public X509Metadata(String cn, String pwd) {
JM 204             if (StringUtils.isEmpty(cn)) {
205                 throw new RuntimeException("Common name required!");
206             }
207             if (StringUtils.isEmpty(pwd)) {
208                 throw new RuntimeException("Password required!");
209             }
210
211             commonName = cn;
212             password = pwd;
213             Calendar c = Calendar.getInstance(TimeZone.getDefault());
214             c.set(Calendar.SECOND, 0);
215             c.set(Calendar.MILLISECOND, 0);
216             notBefore = c.getTime();
217             c.add(Calendar.YEAR, 1);
218             c.add(Calendar.DATE, 1);
219             notAfter = c.getTime();
220             oids = new HashMap<String, String>();
221         }
699e71 222
b42358 223         public X509Metadata clone(String commonName, String password) {
JM 224             X509Metadata clone = new X509Metadata(commonName, password);
225             clone.emailAddress = emailAddress;
226             clone.notBefore = notBefore;
227             clone.notAfter = notAfter;
228             clone.oids.putAll(oids);
229             clone.passwordHint = passwordHint;
230             clone.serverHostname = serverHostname;
231             clone.userDisplayname = userDisplayname;
232             return clone;
233         }
699e71 234
c8b26c 235         public String getOID(String oid, String defaultValue) {
JM 236             if (oids.containsKey(oid)) {
237                 return oids.get(oid);
238             }
239             return defaultValue;
240         }
699e71 241
c8b26c 242         public void setOID(String oid, String value) {
JM 243             if (StringUtils.isEmpty(value)) {
244                 oids.remove(oid);
245             } else {
246                 oids.put(oid, value);
247             }
248         }
b42358 249     }
699e71 250
b42358 251     /**
JM 252      * Prepare all the certificates and stores necessary for a Gitblit GO server.
699e71 253      *
b42358 254      * @param metadata
JM 255      * @param folder
c8b26c 256      * @param x509log
b42358 257      */
c8b26c 258     public static void prepareX509Infrastructure(X509Metadata metadata, File folder, X509Log x509log) {
b42358 259         // make the specified folder, if necessary
JM 260         folder.mkdirs();
699e71 261
b42358 262         // Gitblit CA certificate
699e71 263         File caKeyStore = new File(folder, CA_KEY_STORE);
b42358 264         if (!caKeyStore.exists()) {
JM 265             logger.info(MessageFormat.format("Generating {0} ({1})", CA_CN, caKeyStore.getAbsolutePath()));
c8b26c 266             X509Certificate caCert = newCertificateAuthority(metadata, caKeyStore, x509log);
b42358 267             saveCertificate(caCert, new File(caKeyStore.getParentFile(), "ca.cer"));
JM 268         }
269
004021 270         // Gitblit CRL
699e71 271         File caRevocationList = new File(folder, CA_REVOCATION_LIST);
004021 272         if (!caRevocationList.exists()) {
JM 273             logger.info(MessageFormat.format("Generating {0} CRL ({1})", CA_CN, caRevocationList.getAbsolutePath()));
c8b26c 274             newCertificateRevocationList(caRevocationList, caKeyStore, metadata.password);
JM 275             x509log.log("new certificate revocation list created");
004021 276         }
JM 277
b42358 278         // rename the old keystore to the new name
JM 279         File oldKeyStore = new File(folder, "keystore");
280         if (oldKeyStore.exists()) {
281             oldKeyStore.renameTo(new File(folder, SERVER_KEY_STORE));
699e71 282             logger.info(MessageFormat.format("Renaming {0} to {1}", oldKeyStore.getName(), SERVER_KEY_STORE));
b42358 283         }
JM 284
285         // create web SSL certificate signed by CA
286         File serverKeyStore = new File(folder, SERVER_KEY_STORE);
287         if (!serverKeyStore.exists()) {
288             logger.info(MessageFormat.format("Generating SSL certificate for {0} signed by {1} ({2})", metadata.commonName, CA_CN, serverKeyStore.getAbsolutePath()));
c8b26c 289             PrivateKey caPrivateKey = getPrivateKey(CA_ALIAS, caKeyStore, metadata.password);
JM 290             X509Certificate caCert = getCertificate(CA_ALIAS, caKeyStore, metadata.password);
699e71 291             newSSLCertificate(metadata, caPrivateKey, caCert, serverKeyStore, x509log);
b42358 292         }
JM 293
294         // server certificate trust store holds trusted public certificates
295         File serverTrustStore = new File(folder, X509Utils.SERVER_TRUST_STORE);
296         if (!serverTrustStore.exists()) {
c8b26c 297             logger.info(MessageFormat.format("Importing {0} into trust store ({1})", CA_ALIAS, serverTrustStore.getAbsolutePath()));
JM 298             X509Certificate caCert = getCertificate(CA_ALIAS, caKeyStore, metadata.password);
299             addTrustedCertificate(CA_ALIAS, caCert, serverTrustStore, metadata.password);
b42358 300         }
JM 301     }
699e71 302
b42358 303     /**
JM 304      * Open a keystore.  Store type is determined by file extension of name. If
305      * undetermined, JKS is assumed.  The keystore does not need to exist.
699e71 306      *
b42358 307      * @param storeFile
JM 308      * @param storePassword
309      * @return a KeyStore
310      */
311     public static KeyStore openKeyStore(File storeFile, String storePassword) {
312         String lc = storeFile.getName().toLowerCase();
313         String type = "JKS";
314         String provider = null;
315         if (lc.endsWith(".p12") || lc.endsWith(".pfx")) {
316             type = "PKCS12";
317             provider = BC;
318         }
319
320         try {
321             KeyStore store;
322             if (provider == null) {
323                 store = KeyStore.getInstance(type);
324             } else {
325                 store = KeyStore.getInstance(type, provider);
326             }
327             if (storeFile.exists()) {
328                 FileInputStream fis = null;
329                 try {
330                     fis = new FileInputStream(storeFile);
331                     store.load(fis, storePassword.toCharArray());
332                 } finally {
333                     if (fis != null) {
334                         fis.close();
335                     }
336                 }
337             } else {
338                 store.load(null);
339             }
340             return store;
341         } catch (Exception e) {
342             throw new RuntimeException("Could not open keystore " + storeFile, e);
343         }
344     }
699e71 345
b42358 346     /**
JM 347      * Saves the keystore to the specified file.
699e71 348      *
b42358 349      * @param targetStoreFile
JM 350      * @param store
351      * @param password
352      */
353     public static void saveKeyStore(File targetStoreFile, KeyStore store, String password) {
354         File folder = targetStoreFile.getAbsoluteFile().getParentFile();
355         if (!folder.exists()) {
356             folder.mkdirs();
357         }
358         File tmpFile = new File(folder, Long.toHexString(System.currentTimeMillis()) + ".tmp");
359         FileOutputStream fos = null;
360         try {
361             fos = new FileOutputStream(tmpFile);
362             store.store(fos, password.toCharArray());
363             fos.flush();
364             fos.close();
365             if (targetStoreFile.exists()) {
366                 targetStoreFile.delete();
367             }
368             tmpFile.renameTo(targetStoreFile);
369         } catch (IOException e) {
370             String message = e.getMessage().toLowerCase();
371             if (message.contains("illegal key size")) {
372                 throw new RuntimeException("Illegal Key Size! You might consider installing the JCE Unlimited Strength Jurisdiction Policy files for your JVM.");
373             } else {
374                 throw new RuntimeException("Could not save keystore " + targetStoreFile, e);
375             }
376         } catch (Exception e) {
377             throw new RuntimeException("Could not save keystore " + targetStoreFile, e);
378         } finally {
379             if (fos != null) {
380                 try {
381                     fos.close();
382                 } catch (IOException e) {
383                 }
384             }
699e71 385
b42358 386             if (tmpFile.exists()) {
JM 387                 tmpFile.delete();
388             }
389         }
699e71 390     }
b42358 391
JM 392     /**
393      * Retrieves the X509 certificate with the specified alias from the certificate
394      * store.
699e71 395      *
b42358 396      * @param alias
JM 397      * @param storeFile
398      * @param storePassword
399      * @return the certificate
400      */
401     public static X509Certificate getCertificate(String alias, File storeFile, String storePassword) {
402         try {
403             KeyStore store = openKeyStore(storeFile, storePassword);
404             X509Certificate caCert = (X509Certificate) store.getCertificate(alias);
405             return caCert;
406         } catch (Exception e) {
407             throw new RuntimeException(e);
408         }
409     }
699e71 410
b42358 411     /**
JM 412      * Retrieves the private key for the specified alias from the certificate
413      * store.
699e71 414      *
b42358 415      * @param alias
JM 416      * @param storeFile
417      * @param storePassword
418      * @return the private key
419      */
420     public static PrivateKey getPrivateKey(String alias, File storeFile, String storePassword) {
421         try {
422             KeyStore store = openKeyStore(storeFile, storePassword);
423             PrivateKey key = (PrivateKey) store.getKey(alias, storePassword.toCharArray());
424             return key;
425         } catch (Exception e) {
426             throw new RuntimeException(e);
427         }
428     }
429
430     /**
431      * Saves the certificate to the file system.  If the destination filename
432      * ends with the pem extension, the certificate is written in the PEM format,
433      * otherwise the certificate is written in the DER format.
699e71 434      *
b42358 435      * @param cert
JM 436      * @param targetFile
437      */
438     public static void saveCertificate(X509Certificate cert, File targetFile) {
439         File folder = targetFile.getAbsoluteFile().getParentFile();
440         if (!folder.exists()) {
441             folder.mkdirs();
442         }
443         File tmpFile = new File(folder, Long.toHexString(System.currentTimeMillis()) + ".tmp");
444         try {
445             boolean asPem = targetFile.getName().toLowerCase().endsWith(".pem");
446             if (asPem) {
447                 // PEM encoded X509
448                 PEMWriter pemWriter = null;
449                 try {
450                     pemWriter = new PEMWriter(new FileWriter(tmpFile));
451                     pemWriter.writeObject(cert);
699e71 452                     pemWriter.flush();
b42358 453                 } finally {
JM 454                     if (pemWriter != null) {
455                         pemWriter.close();
456                     }
457                 }
458             } else {
459                 // DER encoded X509
460                 FileOutputStream fos = null;
461                 try {
462                     fos = new FileOutputStream(tmpFile);
463                     fos.write(cert.getEncoded());
464                     fos.flush();
465                 } finally {
466                     if (fos != null) {
467                         fos.close();
468                     }
469                 }
470             }
699e71 471
b42358 472             // rename tmp file to target
699e71 473             if (targetFile.exists()) {
b42358 474                 targetFile.delete();
JM 475             }
476             tmpFile.renameTo(targetFile);
477         } catch (Exception e) {
478             if (tmpFile.exists()) {
479                 tmpFile.delete();
480             }
481             throw new RuntimeException("Failed to save certificate " + cert.getSubjectX500Principal().getName(), e);
482         }
483     }
699e71 484
b42358 485     /**
JM 486      * Generate a new keypair.
699e71 487      *
b42358 488      * @return a keypair
JM 489      * @throws Exception
490      */
491     private static KeyPair newKeyPair() throws Exception {
1687ae 492         KeyPairGenerator kpGen = KeyPairGenerator.getInstance(KEY_ALGORITHM, BC);
JM 493         kpGen.initialize(KEY_LENGTH, new SecureRandom());
b42358 494         return kpGen.generateKeyPair();
JM 495     }
699e71 496
b42358 497     /**
JM 498      * Builds a distinguished name from the X509Metadata.
699e71 499      *
b42358 500      * @return a DN
JM 501      */
502     private static X500Name buildDistinguishedName(X509Metadata metadata) {
503         X500NameBuilder dnBuilder = new X500NameBuilder(BCStyle.INSTANCE);
504         setOID(dnBuilder, metadata, "C", null);
505         setOID(dnBuilder, metadata, "ST", null);
506         setOID(dnBuilder, metadata, "L", null);
507         setOID(dnBuilder, metadata, "O", Constants.NAME);
508         setOID(dnBuilder, metadata, "OU", Constants.NAME);
509         setOID(dnBuilder, metadata, "E", metadata.emailAddress);
699e71 510         setOID(dnBuilder, metadata, "CN", metadata.commonName);
b42358 511         X500Name dn = dnBuilder.build();
JM 512         return dn;
513     }
699e71 514
b42358 515     private static void setOID(X500NameBuilder dnBuilder, X509Metadata metadata,
JM 516             String oid, String defaultValue) {
699e71 517
b42358 518         String value = null;
JM 519         if (metadata.oids != null && metadata.oids.containsKey(oid)) {
520             value = metadata.oids.get(oid);
521         }
522         if (StringUtils.isEmpty(value)) {
523             value = defaultValue;
524         }
699e71 525
b42358 526         if (!StringUtils.isEmpty(value)) {
JM 527             try {
528                 Field field = BCStyle.class.getField(oid);
529                 ASN1ObjectIdentifier objectId = (ASN1ObjectIdentifier) field.get(null);
530                 dnBuilder.addRDN(objectId, value);
531             } catch (Exception e) {
532                 logger.error(MessageFormat.format("Failed to set OID \"{0}\"!", oid) ,e);
533             }
534         }
535     }
536
537     /**
538      * Creates a new SSL certificate signed by the CA private key and stored in
539      * keyStore.
699e71 540      *
b42358 541      * @param sslMetadata
JM 542      * @param caPrivateKey
543      * @param caCert
544      * @param targetStoreFile
c8b26c 545      * @param x509log
b42358 546      */
c8b26c 547     public static X509Certificate newSSLCertificate(X509Metadata sslMetadata, PrivateKey caPrivateKey, X509Certificate caCert, File targetStoreFile, X509Log x509log) {
b42358 548         try {
JM 549             KeyPair pair = newKeyPair();
550
551             X500Name webDN = buildDistinguishedName(sslMetadata);
552             X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(caCert).getName());
699e71 553
b42358 554             X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
JM 555                     issuerDN,
699e71 556                     BigInteger.valueOf(System.currentTimeMillis()),
b42358 557                     sslMetadata.notBefore,
JM 558                     sslMetadata.notAfter,
559                     webDN,
560                     pair.getPublic());
699e71 561
b42358 562             JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
JM 563             certBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pair.getPublic()));
564             certBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
565             certBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey()));
566
d3c189 567             // support alternateSubjectNames for SSL certificates
JM 568             List<GeneralName> altNames = new ArrayList<GeneralName>();
569             if (HttpUtils.isIpAddress(sslMetadata.commonName)) {
699e71 570                 altNames.add(new GeneralName(GeneralName.iPAddress, sslMetadata.commonName));
d3c189 571             }
JM 572             if (altNames.size() > 0) {
573                 GeneralNames subjectAltName = new GeneralNames(altNames.toArray(new GeneralName [altNames.size()]));
574                 certBuilder.addExtension(X509Extension.subjectAlternativeName, false, subjectAltName);
575             }
576
1687ae 577             ContentSigner caSigner = new JcaContentSignerBuilder(SIGNING_ALGORITHM)
b42358 578                     .setProvider(BC).build(caPrivateKey);
JM 579             X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC)
580                     .getCertificate(certBuilder.build(caSigner));
699e71 581
b42358 582             cert.checkValidity(new Date());
JM 583             cert.verify(caCert.getPublicKey());
584
585             // Save to keystore
586             KeyStore serverStore = openKeyStore(targetStoreFile, sslMetadata.password);
587             serverStore.setKeyEntry(sslMetadata.commonName, pair.getPrivate(), sslMetadata.password.toCharArray(),
588                     new Certificate[] { cert, caCert });
589             saveKeyStore(targetStoreFile, serverStore, sslMetadata.password);
699e71 590
e571c4 591             x509log.log(MessageFormat.format("New SSL certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getSubjectDN().getName()));
699e71 592
5f3966 593             // update serial number in metadata object
JM 594             sslMetadata.serialNumber = cert.getSerialNumber().toString();
595
b42358 596             return cert;
JM 597         } catch (Throwable t) {
598             throw new RuntimeException("Failed to generate SSL certificate!", t);
599         }
600     }
601
602     /**
603      * Creates a new certificate authority PKCS#12 store.  This function will
604      * destroy any existing CA store.
699e71 605      *
b42358 606      * @param metadata
JM 607      * @param storeFile
608      * @param keystorePassword
c8b26c 609      * @param x509log
b42358 610      * @return
JM 611      */
c8b26c 612     public static X509Certificate newCertificateAuthority(X509Metadata metadata, File storeFile, X509Log x509log) {
b42358 613         try {
JM 614             KeyPair caPair = newKeyPair();
699e71 615
1687ae 616             ContentSigner caSigner = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPair.getPrivate());
699e71 617
b42358 618             // clone metadata
JM 619             X509Metadata caMetadata = metadata.clone(CA_CN, metadata.password);
620             X500Name issuerDN = buildDistinguishedName(caMetadata);
699e71 621
b42358 622             // Generate self-signed certificate
JM 623             X509v3CertificateBuilder caBuilder = new JcaX509v3CertificateBuilder(
624                     issuerDN,
625                     BigInteger.valueOf(System.currentTimeMillis()),
626                     caMetadata.notBefore,
627                     caMetadata.notAfter,
628                     issuerDN,
629                     caPair.getPublic());
699e71 630
b42358 631             JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
JM 632             caBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(caPair.getPublic()));
633             caBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caPair.getPublic()));
634             caBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(true));
635             caBuilder.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign));
699e71 636
b42358 637             JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC);
JM 638             X509Certificate cert = converter.getCertificate(caBuilder.build(caSigner));
699e71 639
b42358 640             // confirm the validity of the CA certificate
JM 641             cert.checkValidity(new Date());
642             cert.verify(cert.getPublicKey());
643
644             // Delete existing keystore
645             if (storeFile.exists()) {
646                 storeFile.delete();
647             }
699e71 648
b42358 649             // Save private key and certificate to new keystore
JM 650             KeyStore store = openKeyStore(storeFile, caMetadata.password);
c8b26c 651             store.setKeyEntry(CA_ALIAS, caPair.getPrivate(), caMetadata.password.toCharArray(),
b42358 652                     new Certificate[] { cert });
JM 653             saveKeyStore(storeFile, store, caMetadata.password);
699e71 654
c8b26c 655             x509log.log(MessageFormat.format("New CA certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getIssuerDN().getName()));
5f3966 656
JM 657             // update serial number in metadata object
658             caMetadata.serialNumber = cert.getSerialNumber().toString();
c8b26c 659
b42358 660             return cert;
JM 661         } catch (Throwable t) {
662             throw new RuntimeException("Failed to generate Gitblit CA certificate!", t);
663         }
664     }
699e71 665
b42358 666     /**
004021 667      * Creates a new certificate revocation list (CRL).  This function will
JM 668      * destroy any existing CRL file.
699e71 669      *
004021 670      * @param caRevocationList
JM 671      * @param storeFile
672      * @param keystorePassword
673      * @return
674      */
675     public static void newCertificateRevocationList(File caRevocationList, File caKeystoreFile, String caKeystorePassword) {
676         try {
677             // read the Gitblit CA key and certificate
678             KeyStore store = openKeyStore(caKeystoreFile, caKeystorePassword);
c8b26c 679             PrivateKey caPrivateKey = (PrivateKey) store.getKey(CA_ALIAS, caKeystorePassword.toCharArray());
JM 680             X509Certificate caCert = (X509Certificate) store.getCertificate(CA_ALIAS);
004021 681
JM 682             X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(caCert).getName());
683             X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuerDN, new Date());
699e71 684
004021 685             // build and sign CRL with CA private key
1687ae 686             ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPrivateKey);
004021 687             X509CRLHolder crl = crlBuilder.build(signer);
JM 688
689             File tmpFile = new File(caRevocationList.getParentFile(), Long.toHexString(System.currentTimeMillis()) + ".tmp");
690             FileOutputStream fos = null;
691             try {
692                 fos = new FileOutputStream(tmpFile);
693                 fos.write(crl.getEncoded());
694                 fos.flush();
695                 fos.close();
696                 if (caRevocationList.exists()) {
697                     caRevocationList.delete();
698                 }
699                 tmpFile.renameTo(caRevocationList);
700             } finally {
701                 if (fos != null) {
702                     fos.close();
703                 }
704                 if (tmpFile.exists()) {
705                     tmpFile.delete();
706                 }
707             }
708         } catch (Exception e) {
709             throw new RuntimeException("Failed to create new certificate revocation list " + caRevocationList, e);
710         }
711     }
699e71 712
004021 713     /**
b42358 714      * Imports a certificate into the trust store.
699e71 715      *
b42358 716      * @param alias
JM 717      * @param cert
718      * @param storeFile
719      * @param storePassword
720      */
721     public static void addTrustedCertificate(String alias, X509Certificate cert, File storeFile, String storePassword) {
722         try {
723             KeyStore store = openKeyStore(storeFile, storePassword);
724             store.setCertificateEntry(alias, cert);
699e71 725             saveKeyStore(storeFile, store, storePassword);
b42358 726         } catch (Exception e) {
JM 727             throw new RuntimeException("Failed to import certificate into trust store " + storeFile, e);
728         }
729     }
699e71 730
b42358 731     /**
JM 732      * Creates a new client certificate PKCS#12 and PEM store.  Any existing
733      * stores are destroyed.  After generation, the certificates are bundled
734      * into a zip file with a personalized README file.
699e71 735      *
JM 736      * The zip file reference is returned.
737      *
b42358 738      * @param clientMetadata a container for dynamic parameters needed for generation
JM 739      * @param caKeystoreFile
740      * @param caKeystorePassword
c8b26c 741      * @param x509log
b42358 742      * @return a zip file containing the P12, PEM, and personalized README
JM 743      */
699e71 744     public static File newClientBundle(X509Metadata clientMetadata, File caKeystoreFile,
c8b26c 745             String caKeystorePassword, X509Log x509log) {
b42358 746         try {
JM 747             // read the Gitblit CA key and certificate
748             KeyStore store = openKeyStore(caKeystoreFile, caKeystorePassword);
c8b26c 749             PrivateKey caPrivateKey = (PrivateKey) store.getKey(CA_ALIAS, caKeystorePassword.toCharArray());
JM 750             X509Certificate caCert = (X509Certificate) store.getCertificate(CA_ALIAS);
699e71 751
b42358 752             // generate the P12 and PEM files
JM 753             File targetFolder = new File(caKeystoreFile.getParentFile(), clientMetadata.commonName);
c8b26c 754             X509Certificate cert = newClientCertificate(clientMetadata, caPrivateKey, caCert, targetFolder);
JM 755             x509log.log(MessageFormat.format("New client certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getSubjectDN().getName()));
756
b42358 757             // process template message
JM 758             String readme = processTemplate(new File(caKeystoreFile.getParentFile(), "instructions.tmpl"), clientMetadata);
699e71 759
b42358 760             // Create a zip bundle with the p12, pem, and a personalized readme
JM 761             File zipFile = new File(targetFolder, clientMetadata.commonName + ".zip");
762             if (zipFile.exists()) {
763                 zipFile.delete();
764             }
765             ZipOutputStream zos = null;
766             try {
767                 zos = new ZipOutputStream(new FileOutputStream(zipFile));
768                 File p12File = new File(targetFolder, clientMetadata.commonName + ".p12");
769                 if (p12File.exists()) {
770                     zos.putNextEntry(new ZipEntry(p12File.getName()));
771                     zos.write(FileUtils.readContent(p12File));
772                     zos.closeEntry();
699e71 773                 }
b42358 774                 File pemFile = new File(targetFolder, clientMetadata.commonName + ".pem");
JM 775                 if (pemFile.exists()) {
776                     zos.putNextEntry(new ZipEntry(pemFile.getName()));
777                     zos.write(FileUtils.readContent(pemFile));
778                     zos.closeEntry();
779                 }
699e71 780
99d005 781                 // include user's public certificate
JM 782                 zos.putNextEntry(new ZipEntry(clientMetadata.commonName + ".cer"));
783                 zos.write(cert.getEncoded());
784                 zos.closeEntry();
699e71 785
99d005 786                 // include CA public certificate
JM 787                 zos.putNextEntry(new ZipEntry("ca.cer"));
788                 zos.write(caCert.getEncoded());
789                 zos.closeEntry();
699e71 790
b42358 791                 if (readme != null) {
JM 792                     zos.putNextEntry(new ZipEntry("README.TXT"));
793                     zos.write(readme.getBytes("UTF-8"));
794                     zos.closeEntry();
795                 }
796                 zos.flush();
797             } finally {
798                 if (zos != null) {
799                     zos.close();
800                 }
801             }
699e71 802
b42358 803             return zipFile;
JM 804         } catch (Throwable t) {
805             throw new RuntimeException("Failed to generate client bundle!", t);
806         }
807     }
699e71 808
b42358 809     /**
JM 810      * Creates a new client certificate PKCS#12 and PEM store.  Any existing
811      * stores are destroyed.
699e71 812      *
b42358 813      * @param clientMetadata a container for dynamic parameters needed for generation
JM 814      * @param caKeystoreFile
815      * @param caKeystorePassword
816      * @param targetFolder
817      * @return
818      */
819     public static X509Certificate newClientCertificate(X509Metadata clientMetadata,
820             PrivateKey caPrivateKey, X509Certificate caCert, File targetFolder) {
821         try {
822             KeyPair pair = newKeyPair();
699e71 823
JM 824             X500Name userDN = buildDistinguishedName(clientMetadata);
b42358 825             X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(caCert).getName());
699e71 826
b42358 827             // create a new certificate signed by the Gitblit CA certificate
JM 828             X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
829                     issuerDN,
830                     BigInteger.valueOf(System.currentTimeMillis()),
831                     clientMetadata.notBefore,
832                     clientMetadata.notAfter,
833                     userDN,
834                     pair.getPublic());
699e71 835
b42358 836             JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
JM 837             certBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pair.getPublic()));
838             certBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
839             certBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey()));
840             certBuilder.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.keyEncipherment | KeyUsage.digitalSignature));
841             if (!StringUtils.isEmpty(clientMetadata.emailAddress)) {
842                 GeneralNames subjectAltName = new GeneralNames(
843                     new GeneralName(GeneralName.rfc822Name, clientMetadata.emailAddress));
844                 certBuilder.addExtension(X509Extension.subjectAlternativeName, false, subjectAltName);
845             }
846
1687ae 847             ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPrivateKey);
b42358 848
JM 849             X509Certificate userCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certBuilder.build(signer));
850             PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)pair.getPrivate();
851             bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
852                     extUtils.createSubjectKeyIdentifier(pair.getPublic()));
699e71 853
b42358 854             // confirm the validity of the user certificate
JM 855             userCert.checkValidity();
856             userCert.verify(caCert.getPublicKey());
857             userCert.getIssuerDN().equals(caCert.getSubjectDN());
858
859             // verify user certificate chain
860             verifyChain(userCert, caCert);
861
862             targetFolder.mkdirs();
699e71 863
b42358 864             // save certificate, stamped with unique name
JM 865             String date = new SimpleDateFormat("yyyyMMdd").format(new Date());
866             String id = date;
867             File certFile = new File(targetFolder, id + ".cer");
868             int count = 0;
869             while (certFile.exists()) {
870                 id = date + "_" + Character.toString((char) (0x61 + count));
871                 certFile = new File(targetFolder, id + ".cer");
872                 count++;
873             }
699e71 874
b42358 875             // save user private key, user certificate and CA certificate to a PKCS#12 store
JM 876             File p12File = new File(targetFolder, clientMetadata.commonName + ".p12");
877             if (p12File.exists()) {
878                 p12File.delete();
879             }
880             KeyStore userStore = openKeyStore(p12File, clientMetadata.password);
881             userStore.setKeyEntry(MessageFormat.format("Gitblit ({0}) {1} {2}", clientMetadata.serverHostname, clientMetadata.userDisplayname, id), pair.getPrivate(), null, new Certificate [] { userCert });
699e71 882             userStore.setCertificateEntry(MessageFormat.format("Gitblit ({0}) Certificate Authority", clientMetadata.serverHostname), caCert);
b42358 883             saveKeyStore(p12File, userStore, clientMetadata.password);
699e71 884
b42358 885             // save user private key, user certificate, and CA certificate to a PEM store
JM 886             File pemFile = new File(targetFolder, clientMetadata.commonName + ".pem");
887             if (pemFile.exists()) {
888                 pemFile.delete();
889             }
006651 890             JcePEMEncryptorBuilder builder = new JcePEMEncryptorBuilder("DES-EDE3-CBC");
JM 891             builder.setSecureRandom(new SecureRandom());
892             PEMEncryptor pemEncryptor = builder.build(clientMetadata.password.toCharArray());
893             JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(pemFile));
894             pemWriter.writeObject(pair.getPrivate(), pemEncryptor);
b42358 895             pemWriter.writeObject(userCert);
JM 896             pemWriter.writeObject(caCert);
897             pemWriter.flush();
898             pemWriter.close();
699e71 899
b42358 900             // save certificate after successfully creating the key stores
JM 901             saveCertificate(userCert, certFile);
699e71 902
5f3966 903             // update serial number in metadata object
JM 904             clientMetadata.serialNumber = userCert.getSerialNumber().toString();
699e71 905
b42358 906             return userCert;
JM 907         } catch (Throwable t) {
908             throw new RuntimeException("Failed to generate client certificate!", t);
909         }
910     }
699e71 911
b42358 912     /**
JM 913      * Verifies a certificate's chain to ensure that it will function properly.
699e71 914      *
b42358 915      * @param testCert
JM 916      * @param additionalCerts
917      * @return
918      */
919     public static PKIXCertPathBuilderResult verifyChain(X509Certificate testCert, X509Certificate... additionalCerts) {
920         try {
921             // Check for self-signed certificate
922             if (isSelfSigned(testCert)) {
923                 throw new RuntimeException("The certificate is self-signed.  Nothing to verify.");
924             }
699e71 925
b42358 926             // Prepare a set of all certificates
JM 927             // chain builder must have all certs, including cert to validate
928             // http://stackoverflow.com/a/10788392
929             Set<X509Certificate> certs = new HashSet<X509Certificate>();
930             certs.add(testCert);
931             certs.addAll(Arrays.asList(additionalCerts));
699e71 932
b42358 933             // Attempt to build the certification chain and verify it
JM 934             // Create the selector that specifies the starting certificate
699e71 935             X509CertSelector selector = new X509CertSelector();
b42358 936             selector.setCertificate(testCert);
699e71 937
b42358 938             // Create the trust anchors (set of root CA certificates)
JM 939             Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
940             for (X509Certificate cert : additionalCerts) {
941                 if (isSelfSigned(cert)) {
942                     trustAnchors.add(new TrustAnchor(cert, null));
943                 }
944             }
699e71 945
b42358 946             // Configure the PKIX certificate builder
JM 947             PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
948             pkixParams.setRevocationEnabled(false);
949             pkixParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certs), BC));
699e71 950
b42358 951             // Build and verify the certification chain
JM 952             CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", BC);
953             PKIXCertPathBuilderResult verifiedCertChain = (PKIXCertPathBuilderResult) builder.build(pkixParams);
699e71 954
b42358 955             // The chain is built and verified
JM 956             return verifiedCertChain;
957         } catch (CertPathBuilderException e) {
958             throw new RuntimeException("Error building certification path: " + testCert.getSubjectX500Principal(), e);
959         } catch (Exception e) {
960             throw new RuntimeException("Error verifying the certificate: " + testCert.getSubjectX500Principal(), e);
961         }
962     }
699e71 963
b42358 964     /**
JM 965      * Checks whether given X.509 certificate is self-signed.
699e71 966      *
b42358 967      * @param cert
JM 968      * @return true if the certificate is self-signed
969      */
970     public static boolean isSelfSigned(X509Certificate cert) {
971         try {
972             cert.verify(cert.getPublicKey());
973             return true;
974         } catch (SignatureException e) {
975             return false;
976         } catch (InvalidKeyException e) {
977             return false;
978         } catch (Exception e) {
979             throw new RuntimeException(e);
980         }
981     }
699e71 982
b42358 983     public static String processTemplate(File template, X509Metadata metadata) {
JM 984         String content = null;
985         if (template.exists()) {
986             String message = FileUtils.readContent(template, "\n");
987             if (!StringUtils.isEmpty(message)) {
988                 content = message;
e571c4 989                 if (!StringUtils.isEmpty(metadata.serverHostname)) {
JM 990                     content = content.replace("$serverHostname", metadata.serverHostname);
991                 }
992                 if (!StringUtils.isEmpty(metadata.commonName)) {
993                     content = content.replace("$username", metadata.commonName);
994                 }
995                 if (!StringUtils.isEmpty(metadata.userDisplayname)) {
996                     content = content.replace("$userDisplayname", metadata.userDisplayname);
997                 }
998                 if (!StringUtils.isEmpty(metadata.passwordHint)) {
999                     content = content.replace("$storePasswordHint", metadata.passwordHint);
1000                 }
b42358 1001             }
JM 1002         }
1003         return content;
1004     }
699e71 1005
b42358 1006     /**
JM 1007      * Revoke a certificate.
699e71 1008      *
b42358 1009      * @param cert
JM 1010      * @param reason
1011      * @param caRevocationList
1012      * @param caKeystoreFile
1013      * @param caKeystorePassword
c8b26c 1014      * @param x509log
b42358 1015      * @return true if the certificate has been revoked
JM 1016      */
1017     public static boolean revoke(X509Certificate cert, RevocationReason reason,
c8b26c 1018             File caRevocationList, File caKeystoreFile, String caKeystorePassword,
JM 1019             X509Log x509log) {
b42358 1020         try {
JM 1021             // read the Gitblit CA key and certificate
1022             KeyStore store = openKeyStore(caKeystoreFile, caKeystorePassword);
c8b26c 1023             PrivateKey caPrivateKey = (PrivateKey) store.getKey(CA_ALIAS, caKeystorePassword.toCharArray());
JM 1024             return revoke(cert, reason, caRevocationList, caPrivateKey, x509log);
b42358 1025         } catch (Exception e) {
JM 1026             logger.error(MessageFormat.format("Failed to revoke certificate {0,number,0} [{1}] in {2}",
1027                     cert.getSerialNumber(), cert.getSubjectDN().getName(), caRevocationList));
1028         }
1029         return false;
1030     }
699e71 1031
b42358 1032     /**
JM 1033      * Revoke a certificate.
699e71 1034      *
b42358 1035      * @param cert
JM 1036      * @param reason
1037      * @param caRevocationList
1038      * @param caPrivateKey
c8b26c 1039      * @param x509log
b42358 1040      * @return true if the certificate has been revoked
JM 1041      */
1042     public static boolean revoke(X509Certificate cert, RevocationReason reason,
c8b26c 1043              File caRevocationList, PrivateKey caPrivateKey, X509Log x509log) {
b42358 1044         try {
JM 1045             X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(cert).getName());
1046             X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuerDN, new Date());
1047             if (caRevocationList.exists()) {
699e71 1048                 byte [] data = FileUtils.readContent(caRevocationList);
b42358 1049                 X509CRLHolder crl = new X509CRLHolder(data);
JM 1050                 crlBuilder.addCRL(crl);
1051             }
1052             crlBuilder.addCRLEntry(cert.getSerialNumber(), new Date(), reason.ordinal());
699e71 1053
b42358 1054             // build and sign CRL with CA private key
JM 1055             ContentSigner signer = new JcaContentSignerBuilder("SHA1WithRSA").setProvider(BC).build(caPrivateKey);
1056             X509CRLHolder crl = crlBuilder.build(signer);
699e71 1057
b42358 1058             File tmpFile = new File(caRevocationList.getParentFile(), Long.toHexString(System.currentTimeMillis()) + ".tmp");
JM 1059             FileOutputStream fos = null;
1060             try {
1061                 fos = new FileOutputStream(tmpFile);
1062                 fos.write(crl.getEncoded());
1063                 fos.flush();
1064                 fos.close();
1065                 if (caRevocationList.exists()) {
1066                     caRevocationList.delete();
1067                 }
1068                 tmpFile.renameTo(caRevocationList);
699e71 1069
b42358 1070             } finally {
JM 1071                 if (fos != null) {
1072                     fos.close();
1073                 }
1074                 if (tmpFile.exists()) {
1075                     tmpFile.delete();
1076                 }
1077             }
699e71 1078
c8b26c 1079             x509log.log(MessageFormat.format("Revoked certificate {0,number,0} reason: {1} [{2}]",
JM 1080                     cert.getSerialNumber(), reason.toString(), cert.getSubjectDN().getName()));
b42358 1081             return true;
aff53a 1082         } catch (IOException | OperatorCreationException | CertificateEncodingException e) {
b42358 1083             logger.error(MessageFormat.format("Failed to revoke certificate {0,number,0} [{1}] in {2}",
JM 1084                     cert.getSerialNumber(), cert.getSubjectDN().getName(), caRevocationList));
1085         }
1086         return false;
1087     }
699e71 1088
b42358 1089     /**
JM 1090      * Returns true if the certificate has been revoked.
699e71 1091      *
b42358 1092      * @param cert
JM 1093      * @param caRevocationList
1094      * @return true if the certificate is revoked
1095      */
1096     public static boolean isRevoked(X509Certificate cert, File caRevocationList) {
1097         if (!caRevocationList.exists()) {
1098             return false;
1099         }
f185d9 1100         InputStream inStream = null;
b42358 1101         try {
f185d9 1102             inStream = new FileInputStream(caRevocationList);
JM 1103             CertificateFactory cf = CertificateFactory.getInstance("X.509");
1104             X509CRL crl = (X509CRL)cf.generateCRL(inStream);
b42358 1105             return crl.isRevoked(cert);
JM 1106         } catch (Exception e) {
1107             logger.error(MessageFormat.format("Failed to check revocation status for certificate {0,number,0} [{1}] in {2}",
1108                     cert.getSerialNumber(), cert.getSubjectDN().getName(), caRevocationList));
f185d9 1109         } finally {
JM 1110             if (inStream != null) {
1111                 try {
1112                     inStream.close();
1113                 } catch (Exception e) {
1114                 }
1115             }
b42358 1116         }
JM 1117         return false;
1118     }
699e71 1119
5f3966 1120     public static X509Metadata getMetadata(X509Certificate cert) {
JM 1121         Map<String, String> oids = new HashMap<String, String>();
08afff 1122         try {
JM 1123             String dn = cert.getSubjectDN().getName();
1124             LdapName ldapName = new LdapName(dn);
1125             for (int i = 0; i < ldapName.size(); i++) {
1126                 String [] val = ldapName.get(i).trim().split("=", 2);
1127                 String oid = val[0].toUpperCase().trim();
1128                 String data = val[1].trim();
1129                 oids.put(oid, data);
1130             }
1131         } catch (Exception e) {
1132             throw new RuntimeException(e);
5f3966 1133         }
699e71 1134
5f3966 1135         X509Metadata metadata = new X509Metadata(oids.get("CN"), "whocares");
JM 1136         metadata.oids.putAll(oids);
1137         metadata.serialNumber = cert.getSerialNumber().toString();
1138         metadata.notAfter = cert.getNotAfter();
1139         metadata.notBefore = cert.getNotBefore();
1140         metadata.emailAddress = metadata.getOID("E", null);
1141         if (metadata.emailAddress == null) {
1142             metadata.emailAddress = metadata.getOID("EMAILADDRESS", null);
1143         }
1144         return metadata;
1145     }
b42358 1146 }