1package org.bouncycastle.x509;
2
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.IOException;
6import java.math.BigInteger;
7import java.security.InvalidKeyException;
8import java.security.NoSuchAlgorithmException;
9import java.security.NoSuchProviderException;
10import java.security.PrivateKey;
11import java.security.PublicKey;
12import java.security.SecureRandom;
13import java.security.Signature;
14import java.security.SignatureException;
15import java.security.cert.CertificateParsingException;
16import java.security.cert.X509Certificate;
17import java.util.Date;
18import java.util.Hashtable;
19import java.util.Iterator;
20import java.util.Vector;
21
22import javax.security.auth.x500.X500Principal;
23
24import org.bouncycastle.asn1.*;
25import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
26import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
27import org.bouncycastle.asn1.x509.TBSCertificateStructure;
28import org.bouncycastle.asn1.x509.Time;
29import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
30import org.bouncycastle.asn1.x509.X509CertificateStructure;
31import org.bouncycastle.asn1.x509.X509Extension;
32import org.bouncycastle.asn1.x509.X509Extensions;
33import org.bouncycastle.asn1.x509.X509Name;
34import org.bouncycastle.jce.X509Principal;
35import org.bouncycastle.jce.provider.X509CertificateObject;
36import org.bouncycastle.x509.extension.X509ExtensionUtil;
37
38/**
39 * class to produce an X.509 Version 3 certificate.
40 */
41public class X509V3CertificateGenerator
42{
43    private V3TBSCertificateGenerator   tbsGen;
44    private DERObjectIdentifier         sigOID;
45    private AlgorithmIdentifier         sigAlgId;
46    private String                      signatureAlgorithm;
47    private Hashtable                   extensions = null;
48    private Vector                      extOrdering = null;
49
50    public X509V3CertificateGenerator()
51    {
52        tbsGen = new V3TBSCertificateGenerator();
53    }
54
55    /**
56     * reset the generator
57     */
58    public void reset()
59    {
60        tbsGen = new V3TBSCertificateGenerator();
61        extensions = null;
62        extOrdering = null;
63    }
64
65    /**
66     * set the serial number for the certificate.
67     */
68    public void setSerialNumber(
69        BigInteger      serialNumber)
70    {
71        if (serialNumber.compareTo(BigInteger.ZERO) <= 0)
72        {
73            throw new IllegalArgumentException("serial number must be a positive integer");
74        }
75
76        tbsGen.setSerialNumber(new DERInteger(serialNumber));
77    }
78
79    /**
80     * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the
81     * certificate.
82     */
83    public void setIssuerDN(
84        X500Principal   issuer)
85    {
86        try
87        {
88            tbsGen.setIssuer(new X509Principal(issuer.getEncoded()));
89        }
90        catch (IOException e)
91        {
92            throw new IllegalArgumentException("can't process principal: " + e);
93        }
94    }
95
96    /**
97     * Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the
98     * certificate.
99     */
100    public void setIssuerDN(
101        X509Name   issuer)
102    {
103        tbsGen.setIssuer(issuer);
104    }
105
106    public void setNotBefore(
107        Date    date)
108    {
109        tbsGen.setStartDate(new Time(date));
110    }
111
112    public void setNotAfter(
113        Date    date)
114    {
115        tbsGen.setEndDate(new Time(date));
116    }
117
118    /**
119     * Set the subject distinguished name. The subject describes the entity associated with the public key.
120     */
121    public void setSubjectDN(
122        X500Principal   subject)
123    {
124        try
125        {
126            tbsGen.setSubject(new X509Principal(subject.getEncoded()));
127        }
128        catch (IOException e)
129        {
130            throw new IllegalArgumentException("can't process principal: " + e);
131        }
132    }
133
134    /**
135     * Set the subject distinguished name. The subject describes the entity associated with the public key.
136     */
137    public void setSubjectDN(
138        X509Name   subject)
139    {
140        tbsGen.setSubject(subject);
141    }
142
143    public void setPublicKey(
144        PublicKey       key)
145    {
146        try
147        {
148            tbsGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo((ASN1Sequence)new ASN1InputStream(
149                                new ByteArrayInputStream(key.getEncoded())).readObject()));
150        }
151        catch (Exception e)
152        {
153            throw new IllegalArgumentException("unable to process key - " + e.toString());
154        }
155    }
156
157    /**
158     * Set the signature algorithm. This can be either a name or an OID, names
159     * are treated as case insensitive.
160     *
161     * @param signatureAlgorithm string representation of the algorithm name.
162     */
163    public void setSignatureAlgorithm(
164        String  signatureAlgorithm)
165    {
166        this.signatureAlgorithm = signatureAlgorithm;
167
168        try
169        {
170            sigOID = X509Util.getAlgorithmOID(signatureAlgorithm);
171        }
172        catch (Exception e)
173        {
174            throw new IllegalArgumentException("Unknown signature type requested: " + signatureAlgorithm);
175        }
176
177        sigAlgId = X509Util.getSigAlgID(sigOID);
178
179        tbsGen.setSignature(sigAlgId);
180    }
181
182    /**
183     * add a given extension field for the standard extensions tag (tag 3)
184     */
185    public void addExtension(
186        String          oid,
187        boolean         critical,
188        DEREncodable    value)
189    {
190        this.addExtension(new DERObjectIdentifier(oid), critical, value);
191    }
192
193    /**
194     * add a given extension field for the standard extensions tag (tag 3)
195     */
196    public void addExtension(
197        DERObjectIdentifier oid,
198        boolean             critical,
199        DEREncodable        value)
200    {
201        if (extensions == null)
202        {
203            extensions = new Hashtable();
204            extOrdering = new Vector();
205        }
206
207        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
208        DEROutputStream         dOut = new DEROutputStream(bOut);
209
210        try
211        {
212            dOut.writeObject(value);
213        }
214        catch (IOException e)
215        {
216            throw new IllegalArgumentException("error encoding value: " + e);
217        }
218
219        this.addExtension(oid, critical, bOut.toByteArray());
220    }
221
222    /**
223     * add a given extension field for the standard extensions tag (tag 3)
224     * The value parameter becomes the contents of the octet string associated
225     * with the extension.
226     */
227    public void addExtension(
228        String          oid,
229        boolean         critical,
230        byte[]          value)
231    {
232        this.addExtension(new DERObjectIdentifier(oid), critical, value);
233    }
234
235    /**
236     * add a given extension field for the standard extensions tag (tag 3)
237     */
238    public void addExtension(
239        DERObjectIdentifier oid,
240        boolean             critical,
241        byte[]              value)
242    {
243        if (extensions == null)
244        {
245            extensions = new Hashtable();
246            extOrdering = new Vector();
247        }
248
249        extensions.put(oid, new X509Extension(critical, new DEROctetString(value)));
250        extOrdering.addElement(oid);
251    }
252
253    /**
254     * add a given extension field for the standard extensions tag (tag 3)
255     * copying the extension value from another certificate.
256     * @throws CertificateParsingException if the extension cannot be extracted.
257     */
258    public void copyAndAddExtension(
259        String          oid,
260        boolean         critical,
261        X509Certificate cert)
262        throws CertificateParsingException
263    {
264        byte[] extValue = cert.getExtensionValue(oid);
265
266        if (extValue == null)
267        {
268            throw new CertificateParsingException("extension " + oid + " not present");
269        }
270
271        try
272        {
273            ASN1Encodable value = X509ExtensionUtil.fromExtensionValue(extValue);
274
275            this.addExtension(oid, critical, value);
276        }
277        catch (IOException e)
278        {
279            throw new CertificateParsingException(e.toString());
280        }
281    }
282
283    /**
284     * add a given extension field for the standard extensions tag (tag 3)
285     * copying the extension value from another certificate.
286     * @throws CertificateParsingException if the extension cannot be extracted.
287     */
288    public void copyAndAddExtension(
289        DERObjectIdentifier oid,
290        boolean             critical,
291        X509Certificate     cert)
292        throws CertificateParsingException
293    {
294        this.copyAndAddExtension(oid.getId(), critical, cert);
295    }
296
297    /**
298     * generate an X509 certificate, based on the current issuer and subject
299     * using the default provider "BC".
300     */
301    public X509Certificate generateX509Certificate(
302        PrivateKey      key)
303        throws SecurityException, SignatureException, InvalidKeyException
304    {
305        try
306        {
307            return generateX509Certificate(key, "BC", null);
308        }
309        catch (NoSuchProviderException e)
310        {
311            throw new SecurityException("BC provider not installed!");
312        }
313    }
314
315    /**
316     * generate an X509 certificate, based on the current issuer and subject
317     * using the default provider "BC", and the passed in source of randomness
318     * (if required).
319     */
320    public X509Certificate generateX509Certificate(
321        PrivateKey      key,
322        SecureRandom    random)
323        throws SecurityException, SignatureException, InvalidKeyException
324    {
325        try
326        {
327            return generateX509Certificate(key, "BC", random);
328        }
329        catch (NoSuchProviderException e)
330        {
331            throw new SecurityException("BC provider not installed!");
332        }
333    }
334
335    /**
336     * generate an X509 certificate, based on the current issuer and subject,
337     * using the passed in provider for the signing.
338     */
339    public X509Certificate generateX509Certificate(
340        PrivateKey      key,
341        String          provider)
342        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
343    {
344        return generateX509Certificate(key, provider, null);
345    }
346
347    /**
348     * generate an X509 certificate, based on the current issuer and subject,
349     * using the passed in provider for the signing and the supplied source
350     * of randomness, if required.
351     */
352    public X509Certificate generateX509Certificate(
353        PrivateKey      key,
354        String          provider,
355        SecureRandom    random)
356        throws NoSuchProviderException, SecurityException, SignatureException, InvalidKeyException
357    {
358        Signature sig = null;
359
360        if (sigOID == null)
361        {
362            throw new IllegalStateException("no signature algorithm specified");
363        }
364
365        try
366        {
367            sig = Signature.getInstance(sigOID.getId(), provider);
368        }
369        catch (NoSuchAlgorithmException ex)
370        {
371            try
372            {
373                sig = Signature.getInstance(signatureAlgorithm, provider);
374            }
375            catch (NoSuchAlgorithmException e)
376            {
377                throw new SecurityException("exception creating signature: " + e.toString());
378            }
379        }
380
381        if (random != null)
382        {
383            sig.initSign(key, random);
384        }
385        else
386        {
387            sig.initSign(key);
388        }
389
390        if (extensions != null)
391        {
392            tbsGen.setExtensions(new X509Extensions(extOrdering, extensions));
393        }
394
395        TBSCertificateStructure tbsCert = tbsGen.generateTBSCertificate();
396
397        try
398        {
399            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
400            DEROutputStream         dOut = new DEROutputStream(bOut);
401
402            dOut.writeObject(tbsCert);
403
404            sig.update(bOut.toByteArray());
405        }
406        catch (Exception e)
407        {
408            throw new SecurityException("exception encoding TBS cert - " + e);
409        }
410
411        ASN1EncodableVector  v = new ASN1EncodableVector();
412
413        v.add(tbsCert);
414        v.add(sigAlgId);
415        v.add(new DERBitString(sig.sign()));
416
417        return new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
418    }
419
420    /**
421     * Return an iterator of the signature names supported by the generator.
422     *
423     * @return an iterator containing recognised names.
424     */
425    public Iterator getSignatureAlgNames()
426    {
427        return X509Util.getAlgNames();
428    }
429}
430