1 /*
   2  * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.oracle.ipack.signer;
  27 
  28 import java.io.File;
  29 import java.io.FileInputStream;
  30 import java.io.IOException;
  31 import java.io.InputStream;
  32 import java.security.KeyStore;
  33 import java.security.KeyStoreException;
  34 import java.security.NoSuchAlgorithmException;
  35 import java.security.PrivateKey;
  36 import java.security.UnrecoverableKeyException;
  37 import java.security.cert.Certificate;
  38 import java.security.cert.CertificateException;
  39 import java.security.cert.X509Certificate;
  40 import java.util.Arrays;
  41 import javax.naming.InvalidNameException;
  42 import javax.naming.ldap.LdapName;
  43 import javax.naming.ldap.Rdn;
  44 import org.bouncycastle.cert.jcajce.JcaCertStore;
  45 import org.bouncycastle.cms.CMSException;
  46 import org.bouncycastle.cms.CMSProcessableByteArray;
  47 import org.bouncycastle.cms.CMSSignedData;
  48 import org.bouncycastle.cms.CMSSignedDataGenerator;
  49 import org.bouncycastle.cms.CMSTypedData;
  50 import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
  51 import org.bouncycastle.operator.OperatorCreationException;
  52 import org.bouncycastle.util.Store;
  53 
  54 public final class Signer {
  55     private final CMSSignedDataGenerator signatureGenerator;
  56     private final String subjectName;
  57 
  58     private Signer(final CMSSignedDataGenerator signatureGenerator,
  59                    final String subjectName) {
  60         this.signatureGenerator = signatureGenerator;
  61         this.subjectName = subjectName;
  62     }
  63 
  64     public static Signer create(
  65             final File keystoreFile,
  66             final String keystorePassword,
  67             final String signingAlias,
  68             final String keyPassword) throws IOException,
  69                                              KeyStoreException,
  70                                              NoSuchAlgorithmException,
  71                                              CertificateException,
  72                                              UnrecoverableKeyException,
  73                                              OperatorCreationException,
  74                                              CMSException,
  75                                              InvalidNameException {
  76         final KeyStore jksKeyStore = KeyStore.getInstance("JKS");
  77         final InputStream is = new FileInputStream(keystoreFile);
  78         try {
  79             jksKeyStore.load(is, keystorePassword.toCharArray());
  80         } finally {
  81             is.close();
  82         }
  83 
  84         final PrivateKey privateKey =
  85                 (PrivateKey) jksKeyStore.getKey(signingAlias,
  86                                                 keyPassword.toCharArray());
  87         final Certificate[] certChain =
  88                 jksKeyStore.getCertificateChain(signingAlias);
  89         if (certChain == null) {
  90             throw new CertificateException(
  91                     "Certificate chain not found under \"" + signingAlias
  92                                                            + "\"");
  93         }
  94 
  95         final X509Certificate signingCert = (X509Certificate) certChain[0];
  96         final String subjectName = getSubjectName(signingCert);
  97 
  98         final Store certs = new JcaCertStore(Arrays.asList(certChain));
  99         final CMSSignedDataGenerator signatureGenerator =
 100                 new CMSSignedDataGenerator();
 101 
 102         signatureGenerator.addSignerInfoGenerator(
 103                 new JcaSimpleSignerInfoGeneratorBuilder()
 104                             .setProvider("BC")
 105                             .build("SHA1withRSA", privateKey, signingCert));
 106         signatureGenerator.addCertificates(certs);
 107 
 108         return new Signer(signatureGenerator, subjectName);
 109     }
 110 
 111     public byte[] sign(final byte[] data) throws CMSException, IOException {
 112         final CMSTypedData typedData = new CMSProcessableByteArray(data);
 113         final CMSSignedData signedData = signatureGenerator.generate(typedData);
 114 
 115         return signedData.getEncoded();
 116     }
 117 
 118     public String getSubjectName() {
 119         return subjectName;
 120     }
 121 
 122     private static String getSubjectName(final X509Certificate cert)
 123             throws InvalidNameException {
 124         final String fullSubjectDn = cert.getSubjectX500Principal().getName();
 125         final LdapName fullSubjectLn = new LdapName(fullSubjectDn);
 126         for (final Rdn rdn: fullSubjectLn.getRdns()) {
 127             if ("CN".equalsIgnoreCase(rdn.getType())) {
 128                 return rdn.getValue().toString();
 129             }
 130         }
 131 
 132         throw new InvalidNameException("Common name not found");
 133     }
 134 }