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 }