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      /** Hostname that will appear as the certifcate's DN common name component. */
75      private String hostname;
76      
77      /** Optional DNS subject alt names. */
78      private String[] dnsSubjectAltNames;
79      
80      /** Optional DNS subject alt names. */
81      private String[] uriSubjectAltNames;
82  
83      /** File to which the public key will be written. */
84      private File privateKeyFile;
85  
86      /** File to which the certificate will be written. */
87      private File certificateFile;
88  
89      /** File to which the keystore will be written. */
90      private File keystoreFile;
91  
92      /** Password for the generated keystore. */
93      private String keystorePassword;
94  
95      /** {@inheritDoc} */
96      public void execute() throws BuildException {
97          validate();
98          KeyPair keypair = generateKeyPair();
99          X509Certificate certificate = generateCertificate(keypair);
100 
101         if (privateKeyFile != null) {
102             try {
103                 privateKeyFile.createNewFile();
104                 PEMWriter keyOut = new PEMWriter(new FileWriter(privateKeyFile));
105                 keyOut.writeObject(keypair.getPrivate());
106                 keyOut.flush();
107                 keyOut.close();
108             } catch (Exception e) {
109                 throw new BuildException("Unable to create private key file.", e);
110             }
111         }
112 
113         if (certificateFile != null) {
114             try {
115                 certificateFile.createNewFile();
116                 PEMWriter certOut = new PEMWriter(new FileWriter(certificateFile));
117                 certOut.writeObject(certificate);
118                 certOut.flush();
119                 certOut.close();
120             } catch (Exception e) {
121                 throw new BuildException("Unable to create private key file.", e);
122             }
123         }
124 
125         if (keystoreFile != null) {
126             try {
127                 KeyStore store = KeyStore.getInstance("JKS");
128                 store.load(null, null);
129                 store.setKeyEntry(hostname, keypair.getPrivate(), keystorePassword.toCharArray(),
130                         new X509Certificate[] { certificate });
131 
132                 FileOutputStream keystoreOut = new FileOutputStream(keystoreFile);
133                 store.store(keystoreOut, keystorePassword.toCharArray());
134                 keystoreOut.flush();
135                 keystoreOut.close();
136             } catch (Exception e) {
137                 throw new BuildException(e);
138             }
139         }
140     }
141 
142     /**
143      * Sets the type of key that will be generated. Defaults to DSA.
144      * 
145      * @param type type of key that will be generated
146      */
147     public void setKeyType(KeyType type) {
148         keyType = type.getValue();
149     }
150 
151     /**
152      * Sets the size of the generated key. Defaults to 2048
153      * 
154      * @param size size of the generated key
155      */
156     public void setKeysize(int size) {
157         keysize = size;
158     }
159 
160     /**
161      * Sets the hostname that will appear in the certificate's DN.
162      * 
163      * @param name hostname that will appear in the certificate's DN
164      */
165     public void setHostName(String name) {
166         hostname = name;
167     }
168 
169     /**
170      * Sets the file to which the private key will be written.
171      * 
172      * @param file file to which the private key will be written
173      */
174     public void setPrivateKeyFile(File file) {
175         privateKeyFile = file;
176     }
177 
178     /**
179      * Sets the file to which the certificate will be written.
180      * 
181      * @param file file to which the certificate will be written
182      */
183     public void setCertificateFile(File file) {
184         certificateFile = file;
185     }
186 
187     /**
188      * Sets the file to which the keystore will be written.
189      * 
190      * @param file file to which the keystore will be written
191      */
192     public void setKeystoreFile(File file) {
193         keystoreFile = file;
194     }
195 
196     /**
197      * Sets the password for the generated keystore.
198      * 
199      * @param password password for the generated keystore
200      */
201     public void setKeystorePassword(String password) {
202         keystorePassword = password;
203     }
204     
205     /**
206      * Sets the optional DNS subject alt names.
207      * 
208      * @param altNames space delimited set of subject alt names.
209      */
210     public void setDnsSubjectAltNames(String altNames){
211         dnsSubjectAltNames = altNames.split(" ");
212     }
213     
214     /**
215      * Sets the optional URI subject alt names.
216      * 
217      * @param altNames space delimited set of subject alt names.
218      */
219     public void setUriSubjectAltNames(String altNames){
220         uriSubjectAltNames = altNames.split(" ");
221     }
222 
223     /** Validates the provided task input. */
224     protected void validate() throws BuildException {
225         if (keysize > 2048) {
226             log("Key size is greater than 2048, this may cause problems with some JVMs", Project.MSG_WARN);
227         }
228 
229         if (hostname == null || hostname.length() == 0) {
230             throw new BuildException("The hostname attribute is required and may not contain an empty value");
231         }
232 
233         if (keystoreFile != null && (keystorePassword == null || keystorePassword.length() == 0)) {
234             throw new BuildException("Keystore password may not be null if a keystore file is given");
235         }
236     }
237 
238     /**
239      * Generates the key pair for the certificate.
240      * 
241      * @return key pair for the certificate
242      * 
243      * @throws BuildException thrown if there is a problem generating the keys.
244      */
245     protected KeyPair generateKeyPair() throws BuildException {
246         try {
247             KeyPairGenerator generator = KeyPairGenerator.getInstance(keyType);
248             generator.initialize(keysize);
249             return generator.generateKeyPair();
250         } catch (NoSuchAlgorithmException e) {
251             throw new BuildException("The " + keyType + " key type is not supported by this JVM");
252         }
253     }
254 
255     /**
256      * Generates the self-signed certificate.
257      * 
258      * @param keypair keypair associated with the certificate
259      * 
260      * @return self-signed certificate
261      * 
262      * @throws BuildException thrown if the certificate can not be generated
263      */
264     protected X509Certificate generateCertificate(KeyPair keypair) throws BuildException {
265         try {
266             X509V3CertificateGenerator certifcateGenerator = new X509V3CertificateGenerator();
267             certifcateGenerator.setPublicKey(keypair.getPublic());
268 
269             StringBuffer dnBuffer = new StringBuffer("CN=").append(hostname);
270 
271             X509Name dn = new X509Name(false, dnBuffer.toString(), new RdnConverter());
272             certifcateGenerator.setIssuerDN(dn);
273             certifcateGenerator.setSubjectDN(dn);
274 
275             GregorianCalendar date = new GregorianCalendar();
276             certifcateGenerator.setNotBefore(date.getTime());
277 
278             date.set(GregorianCalendar.YEAR, date.get(GregorianCalendar.YEAR) + 20);
279             certifcateGenerator.setNotAfter(date.getTime());
280 
281             certifcateGenerator.setSerialNumber(new BigInteger(160, new SecureRandom()));
282 
283             certifcateGenerator.setSignatureAlgorithm("SHA1withRSA");
284 
285             certifcateGenerator.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(
286                     new DERSequence(buildSubjectAltNames())));
287             
288             certifcateGenerator.addExtension(X509Extensions.SubjectKeyIdentifier, false,
289                     new SubjectKeyIdentifierStructure(keypair.getPublic()));
290 
291             return certifcateGenerator.generate(keypair.getPrivate());
292         } catch (Exception e) {
293             log(e.toString(), Project.MSG_ERR);
294             throw new BuildException("Unable to generate self-signed certificate", e);
295         }
296     }
297     
298     /**
299      * Builds the subject alt names for the certificate.
300      * 
301      * @return subject alt names for the certificate
302      */
303     protected ASN1Encodable[] buildSubjectAltNames(){
304         ArrayList<ASN1Encodable> subjectAltNames = new ArrayList<ASN1Encodable>();
305         
306         subjectAltNames.add(new GeneralName(GeneralName.dNSName, hostname));
307         
308         if(dnsSubjectAltNames != null){
309             for(String subjectAltName : dnsSubjectAltNames){
310                 subjectAltNames.add(new GeneralName(GeneralName.dNSName, subjectAltName));
311             }
312         }
313         
314         if(uriSubjectAltNames != null){
315             for(String subjectAltName : uriSubjectAltNames){
316                 subjectAltNames.add(new GeneralName(GeneralName.uniformResourceIdentifier, subjectAltName));
317             }
318         }
319 
320         return subjectAltNames.toArray(new ASN1Encodable[0]);
321     }
322 
323     /** Key type enumeration. */
324     public static class KeyType extends EnumeratedAttribute {
325 
326         public String[] getValues() {
327             return new String[] { "DSA", "RSA" };
328         }
329     }
330 }