View Javadoc

    1   /*
    2    * Copyright 2008 University Corporation for Advanced Internet Development, Inc.
    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
    17  package edu.internet2.middleware.ant.pki;
    18
    19  import java.io.File;
    20  import java.io.FileOutputStream;
    21  import java.io.FileWriter;
    22  import java.math.BigInteger;
    23  import java.security.KeyPair;
    24  import java.security.KeyPairGenerator;
    25  import java.security.KeyStore;
    26  import java.security.NoSuchAlgorithmException;
    27  import java.security.SecureRandom;
    28  import java.security.cert.X509Certificate;
    29  import java.util.ArrayList;
    30  import java.util.GregorianCalendar;
    31
    32  import org.apache.tools.ant.BuildException;
    33  import org.apache.tools.ant.Project;
    34  import org.apache.tools.ant.Task;
    35  import org.apache.tools.ant.types.EnumeratedAttribute;
    36  import org.bouncycastle.asn1.ASN1Encodable;
    37  import org.bouncycastle.asn1.DERSequence;
    38  import org.bouncycastle.asn1.x509.GeneralName;
    39  import org.bouncycastle.asn1.x509.GeneralNames;
    40  import org.bouncycastle.asn1.x509.X509Extensions;
    41  import org.bouncycastle.asn1.x509.X509Name;
    42  import org.bouncycastle.openssl.PEMWriter;
    43  import org.bouncycastle.x509.X509V3CertificateGenerator;
    44  import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
    45
    46  /**
    47   * An ant task that generates a self-signed certificate.
    48   *
    49   * This ant task requires three attributes:
    50   * <ul>
    51   * <li><strong>hostname</strong> - the server hostname that will be used in the certificate Subject and Issuer DN</li>
    52   * <ul>
    53   *
    54   * The task also has two optional attributes:
    55   * <ul>
    56   * <li><strong>keyType</strong> - type of key generated, possible values are "DSA" or "RSA", the default is RSA</li>
    57   * <li><strong>keySize</strong> - size, in bits, of the generated key, default is 2048</li>
    58   * <li><strong>privateKeyFile</strong> - file to which the generated private key will be written, in PEM format</li>
    59   * <li><strong>certifcateFile</strong> - file to which the self-signed certificate will be written, in PEM format</li>
    60   * <li><strong>keystoreFile</strong> - keystore to which the self-signed certificate and key will be written</li>
    61   * <li><strong>keystorePassword</strong> - password for the keystore, if none is given the string 'changeit' is used</li>
    62   * <li><strong>dnsSubjectAltNames</strong> - space separated list of DNS subject alt names</li>
    63   * <li><strong>uriSubjectAltNames</strong> - space separated list of URI subject alt names</li>
    64   * <ul>
    65   */
    66  public class SelfSignedCertificate extends Task {
    67
    68      /** Type of key to generated. Valid values: DSA or RSA */
    69      private String keyType = "RSA";
    70
    71      /** Size of the generated key. */
    72      private int keysize = 2048;
    73
    74      /** Number of years before the self-signed certificate expires. */
    75      private int certificateLifetime = 20;
    76
    77      /** Hostname that will appear as the certifcate's DN common name component. */
    78      private String hostname;
    79
    80      /** Optional DNS subject alt names. */
    81      private String[] dnsSubjectAltNames;
    82
    83      /** Optional DNS subject alt names. */
    84      private String[] uriSubjectAltNames;
    85
    86      /** File to which the public key will be written. */
    87      private File privateKeyFile;
    88
    89      /** File to which the certificate will be written. */
    90      private File certificateFile;
    91
    92      /** File to which the keystore will be written. */
    93      private File keystoreFile;
    94
    95      /** Password for the generated keystore. */
    96      private String keystorePassword;
    97
    98      /** {@inheritDoc} */
    99      public void execute() throws BuildException {
    100         validate();
    101         KeyPair keypair = generateKeyPair();
    102         X509Certificate certificate = generateCertificate(keypair);
    103
    104         if (privateKeyFile != null) {
    105             try {
    106                 privateKeyFile.createNewFile();
    107                 PEMWriter keyOut = new PEMWriter(new FileWriter(privateKeyFile));
    108                 keyOut.writeObject(keypair.getPrivate());
    109                 keyOut.flush();
    110                 keyOut.close();
    111             } catch (Exception e) {
    112                 throw new BuildException("Unable to create private key file.", e);
    113             }
    114         }
    115
    116         if (certificateFile != null) {
    117             try {
    118                 certificateFile.createNewFile();
    119                 PEMWriter certOut = new PEMWriter(new FileWriter(certificateFile));
    120                 certOut.writeObject(certificate);
    121                 certOut.flush();
    122                 certOut.close();
    123             } catch (Exception e) {
    124                 throw new BuildException("Unable to create private key file.", e);
    125             }
    126         }
    127
    128         if (keystoreFile != null) {
    129             try {
    130                 KeyStore store = KeyStore.getInstance("JKS");
    131                 store.load(null, null);
    132                 store.setKeyEntry(hostname, keypair.getPrivate(), keystorePassword.toCharArray(),
    133                         new X509Certificate[] { certificate });
    134
    135                 FileOutputStream keystoreOut = new FileOutputStream(keystoreFile);
    136                 store.store(keystoreOut, keystorePassword.toCharArray());
    137                 keystoreOut.flush();
    138                 keystoreOut.close();
    139             } catch (Exception e) {
    140                 throw new BuildException(e);
    141             }
    142         }
    143     }
    144
    145     /**
    146      * Sets the type of key that will be generated. Defaults to DSA.
    147      *
    148      * @param type type of key that will be generated
    149      */
    150     public void setKeyType(KeyType type) {
    151         keyType = type.getValue();
    152     }
    153
    154     /**
    155      * Sets the size of the generated key. Defaults to 2048
    156      *
    157      * @param size size of the generated key
    158      */
    159     public void setKeysize(int size) {
    160         keysize = size;
    161     }
    162
    163     /**
    164      * Sets the number of years for which the certificate will be valid.
    165      *
    166      * @param lifetime number of years for which the certificate will be valid
    167      */
    168     public void setCertificateLifetime(int lifetime) {
    169         certificateLifetime = lifetime;
    170     }
    171
    172     /**
    173      * Sets the hostname that will appear in the certificate's DN.
    174      *
    175      * @param name hostname that will appear in the certificate's DN
    176      */
    177     public void setHostName(String name) {
    178         hostname = name;
    179     }
    180
    181     /**
    182      * Sets the file to which the private key will be written.
    183      *
    184      * @param file file to which the private key will be written
    185      */
    186     public void setPrivateKeyFile(File file) {
    187         privateKeyFile = file;
    188     }
    189
    190     /**
    191      * Sets the file to which the certificate will be written.
    192      *
    193      * @param file file to which the certificate will be written
    194      */
    195     public void setCertificateFile(File file) {
    196         certificateFile = file;
    197     }
    198
    199     /**
    200      * Sets the file to which the keystore will be written.
    201      *
    202      * @param file file to which the keystore will be written
    203      */
    204     public void setKeystoreFile(File file) {
    205         keystoreFile = file;
    206     }
    207
    208     /**
    209      * Sets the password for the generated keystore.
    210      *
    211      * @param password password for the generated keystore
    212      */
    213     public void setKeystorePassword(String password) {
    214         keystorePassword = password;
    215     }
    216
    217     /**
    218      * Sets the optional DNS subject alt names.
    219      *
    220      * @param altNames space delimited set of subject alt names.
    221      */
    222     public void setDnsSubjectAltNames(String altNames){
    223         dnsSubjectAltNames = altNames.split(" ");
    224     }
    225
    226     /**
    227      * Sets the optional URI subject alt names.
    228      *
    229      * @param altNames space delimited set of subject alt names.
    230      */
    231     public void setUriSubjectAltNames(String altNames){
    232         uriSubjectAltNames = altNames.split(" ");
    233     }
    234
    235     /** Validates the provided task input. */
    236     protected void validate() throws BuildException {
    237         if (keysize > 2048) {
    238             log("Key size is greater than 2048, this may cause problems with some JVMs", Project.MSG_WARN);
    239         }
    240
    241         if (hostname == null || hostname.length() == 0) {
    242             throw new BuildException("The hostname attribute is required and may not contain an empty value");
    243         }
    244
    245         if (keystoreFile != null && (keystorePassword == null || keystorePassword.length() == 0)) {
    246             throw new BuildException("Keystore password may not be null if a keystore file is given");
    247         }
    248     }
    249
    250     /**
    251      * Generates the key pair for the certificate.
    252      *
    253      * @return key pair for the certificate
    254      *
    255      * @throws BuildException thrown if there is a problem generating the keys.
    256      */
    257     protected KeyPair generateKeyPair() throws BuildException {
    258         try {
    259             KeyPairGenerator generator = KeyPairGenerator.getInstance(keyType);
    260             generator.initialize(keysize);
    261             return generator.generateKeyPair();
    262         } catch (NoSuchAlgorithmException e) {
    263             throw new BuildException("The " + keyType + " key type is not supported by this JVM");
    264         }
    265     }
    266
    267     /**
    268      * Generates the self-signed certificate.
    269      *
    270      * @param keypair keypair associated with the certificate
    271      *
    272      * @return self-signed certificate
    273      *
    274      * @throws BuildException thrown if the certificate can not be generated
    275      */
    276     protected X509Certificate generateCertificate(KeyPair keypair) throws BuildException {
    277         try {
    278             X509V3CertificateGenerator certifcateGenerator = new X509V3CertificateGenerator();
    279             certifcateGenerator.setPublicKey(keypair.getPublic());
    280
    281             StringBuffer dnBuffer = new StringBuffer("CN=").append(hostname);
    282
    283             X509Name dn = new X509Name(false, dnBuffer.toString(), new RdnConverter());
    284             certifcateGenerator.setIssuerDN(dn);
    285             certifcateGenerator.setSubjectDN(dn);
    286
    287             GregorianCalendar date = new GregorianCalendar();
    288             certifcateGenerator.setNotBefore(date.getTime());
    289
    290             date.set(GregorianCalendar.YEAR, date.get(GregorianCalendar.YEAR) + certificateLifetime);
    291             certifcateGenerator.setNotAfter(date.getTime());
    292
    293             certifcateGenerator.setSerialNumber(new BigInteger(160, new SecureRandom()));
    294
    295             certifcateGenerator.setSignatureAlgorithm("SHA1withRSA");
    296
    297             certifcateGenerator.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(
    298                     new DERSequence(buildSubjectAltNames())));
    299
    300             certifcateGenerator.addExtension(X509Extensions.SubjectKeyIdentifier, false,
    301                     new SubjectKeyIdentifierStructure(keypair.getPublic()));
    302
    303             return certifcateGenerator.generate(keypair.getPrivate());
    304         } catch (Exception e) {
    305             log(e.toString(), Project.MSG_ERR);
    306             throw new BuildException("Unable to generate self-signed certificate", e);
    307         }
    308     }
    309
    310     /**
    311      * Builds the subject alt names for the certificate.
    312      *
    313      * @return subject alt names for the certificate
    314      */
    315     protected ASN1Encodable[] buildSubjectAltNames(){
    316         ArrayList<ASN1Encodable> subjectAltNames = new ArrayList<ASN1Encodable>();
    317
    318         subjectAltNames.add(new GeneralName(GeneralName.dNSName, hostname));
    319
    320         if(dnsSubjectAltNames != null){
    321             for(String subjectAltName : dnsSubjectAltNames){
    322                 subjectAltNames.add(new GeneralName(GeneralName.dNSName, subjectAltName));
    323             }
    324         }
    325
    326         if(uriSubjectAltNames != null){
    327             for(String subjectAltName : uriSubjectAltNames){
    328                 subjectAltNames.add(new GeneralName(GeneralName.uniformResourceIdentifier, subjectAltName));
    329             }
    330         }
    331
    332         return subjectAltNames.toArray(new ASN1Encodable[0]);
    333     }
    334
    335     /** Key type enumeration. */
    336     public static class KeyType extends EnumeratedAttribute {
    337
    338         public String[] getValues() {
    339             return new String[] { "DSA", "RSA" };
    340         }
    341     }
    342 }