1 /**
2 * X.509 Self-Signed Certificate
3 * 
4 * Copyright:
5 * (C) 1999-2007 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.cert.x509.x509self;
12 import botan.constants;
13 
14 alias x509self = botan.cert.x509.x509self;
15 
16 public import botan.cert.x509.x509_ca;
17 public import botan.cert.x509.x509cert;
18 public import botan.asn1.asn1_time;
19 import botan.cert.x509.pkcs10;
20 import botan.cert.x509.x509_ext;
21 import botan.cert.x509.key_constraint;
22 import botan.asn1.oids;
23 import botan.asn1.der_enc;
24 import botan.asn1.asn1_attribute;
25 import botan.asn1.asn1_alt_name;
26 import botan.filters.pipe;
27 import botan.utils.types;
28 import botan.utils.parsing;
29 import botan.pubkey.pkcs8;
30 import std.datetime;
31 
32 static if (__traits(compiles, { X509Time time; }())):
33 
34 alias X509CertOptions = RefCounted!X509CertOptionsImpl;
35 
36 /**
37 * Options for X.509 certificates.
38 */
39 final class X509CertOptionsImpl
40 {
41 public:
42     /**
43     * the subject common name
44     */
45     string common_name;
46 
47     /**
48     * the subject counry
49     */
50     string country;
51 
52     /**
53     * the subject organization
54     */
55     string organization;
56 
57     /**
58     * the subject organizational unit
59     */
60     string org_unit;
61 
62     /**
63     * the subject locality
64     */
65     string locality;
66 
67     /**
68     * the subject state
69     */
70     string state;
71 
72     /**
73     * the subject serial number
74     */
75     string serial_number;
76 
77     /**
78     * the subject email adress
79     */
80     string email;
81 
82     /**
83     * the subject URI
84     */
85     string uri;
86 
87     /**
88     * the subject IPv4 address
89     */
90     string ip;
91 
92     /**
93     * the subject DNS
94     */
95     string dns;
96 
97     /**
98     * the subject XMPP
99     */
100     string xmpp;
101 
102     /**
103     * the subject challenge password
104     */
105     string challenge;
106 
107     /**
108     * the subject notBefore
109     */
110     X509Time start;
111     /**
112     * the subject notAfter
113     */
114     X509Time end;
115 
116     /**
117     * Indicates whether the certificate request
118     */
119     bool is_CA;
120 
121     /**
122     * Indicates the BasicConstraints path limit
123     */
124     size_t path_limit;
125 
126     /**
127     * The key constraints for the subject public key
128     */
129     KeyConstraints constraints;
130 
131     /**
132     * The key extended constraints for the subject public key
133     */
134     Vector!OID ex_constraints;
135 
136     /**
137     * Check the options set in this object for validity.
138     */
139     void sanityCheck() const
140     {
141         if (common_name == "" || country == "")
142             throw new EncodingError("X.509 certificate: name and country MUST be set");
143         if (country.length != 2)
144             throw new EncodingError("Invalid ISO country code: " ~ country);
145         if (start >= end)
146             throw new EncodingError("X509_Cert_Options: invalid time constraints");
147     }
148     
149 
150 
151     /**
152     * Mark the certificate as a CA certificate and set the path limit.
153     *
154     * Params:
155     *  limit = the path limit to be set in the BasicConstraints extension.
156     */
157     void CAKey(size_t limit = 1)
158     {
159         is_CA = true;
160         path_limit = limit;
161     }
162 
163 
164     /**
165     * Set when the certificate should become valid
166     * Params:
167     *  time_string = the notBefore value of the certificate
168     */
169     void notBefore(in string time_string)
170     {
171         start = X509Time(time_string);
172     }
173 
174     /**
175     * Set the notAfter of the certificate.
176     *
177     * Params:
178     *  time_string = the notAfter value of the certificate
179     */
180     void notAfter(in string time_string)
181     {
182         end = X509Time(time_string);
183     }
184 
185     /**
186     * Add the key constraints of the KeyUsage extension.
187     *
188     * Params:
189     *  usage = the constraints to set
190     */
191     void addConstraints(KeyConstraints usage)
192     {
193         constraints = usage;
194     }
195 
196     /**
197     * Add constraints to the ExtendedKeyUsage extension.
198     *
199     * Params:
200     *  oid = the oid to add
201     */
202     void addExConstraint()(auto ref OID oid)
203     {
204         ex_constraints.pushBack(oid.dup);
205     }
206 
207     /**
208     * Add constraints to the ExtendedKeyUsage extension.
209     *
210     * Params:
211     *  oid_str = the name to look up the oid to add
212     */
213     void addExConstraint(in string oid_str)
214     {
215         ex_constraints.pushBack(OIDS.lookup(oid_str));
216     }
217 
218     /**
219     * Construct a new options object
220     * Params:
221     *  initial_opts = define the common name of this object. An example for this
222     * parameter would be "common_name/country/organization/organizational_unit".
223     *  expiration_time = the expiration time (default 1 year)
224     */
225     this(in string initial_opts = "", Duration expiration_time = 365.days)
226     {
227         is_CA = false;
228         path_limit = 0;
229         constraints = KeyConstraints.NO_CONSTRAINTS;
230         
231         auto now = Clock.currTime(UTC());
232         start = X509Time(now);
233         end = X509Time(now + expiration_time);
234         
235         if (initial_opts == "")
236             return;
237         
238         Vector!string parsed = botan.utils.parsing.splitter(initial_opts, '/');
239         
240         if (parsed.length > 4)
241             throw new InvalidArgument("X.509 cert options: Too many names: " ~ initial_opts);
242         
243         if (parsed.length >= 1) common_name      = parsed[0];
244         if (parsed.length >= 2) country          = parsed[1];
245         if (parsed.length >= 3) organization     = parsed[2];
246         if (parsed.length == 4) org_unit         = parsed[3];
247     }
248 }
249 
250 /**
251 * Create a self-signed X.509 certificate.
252 * Params:
253 *  opts = the options defining the certificate to create
254 *  key = the private key used for signing, i.e. the key
255 * associated with this self-signed certificate
256 *  hash_fn = the hash function to use
257 *  rng = the rng to use
258 * Returns: newly created self-signed certificate
259 */
260 X509Certificate createSelfSignedCert()(auto const ref X509CertOptions opts,
261                                        in PrivateKey key,
262                                        in string hash_fn,
263                                        RandomNumberGenerator rng)
264 {
265     auto sig_algo = AlgorithmIdentifier();
266     X509DN subject_dn = X509DN();
267     AlternativeName subject_alt = AlternativeName();
268     
269     opts.sanityCheck();
270     
271     Vector!ubyte pub_key = x509_key.BER_encode(key);
272     PKSigner signer = chooseSigFormat(key, hash_fn, sig_algo);
273     loadInfo(opts, subject_dn, subject_alt);
274     
275     KeyConstraints constraints;
276     if (opts.is_CA)
277         constraints = KeyConstraints.KEY_CERT_SIGN | KeyConstraints.CRL_SIGN;
278     else
279         constraints = findConstraints(key, opts.constraints);
280     
281     X509Extensions extensions = X509Extensions(true);
282     
283     extensions.add(new BasicConstraints(opts.is_CA, opts.path_limit), true);
284 
285     extensions.add(new KeyUsage(constraints), true);
286     extensions.add(new SubjectKeyID(pub_key));
287     extensions.add(new SubjectAlternativeName(subject_alt));
288     extensions.add(new ExtendedKeyUsage(opts.ex_constraints));
289     return X509CA.makeCert(signer, rng, sig_algo, pub_key,
290                            opts.start, opts.end,
291                            subject_dn, subject_dn,
292                            extensions);
293 }
294 
295 /**
296 * Create a PKCS#10 certificate request.
297 * Params:
298 *  opts = the options defining the request to create
299 *  key = the key used to sign this request
300 *  hash_fn = the hash function to use
301 *  rng = the rng to use
302 * Returns: newly created PKCS#10 request
303 */
304 PKCS10Request createCertReq()(auto const ref X509CertOptions opts,
305                               in PrivateKey key,
306                               in string hash_fn,
307                               RandomNumberGenerator rng)
308 {
309     auto sig_algo = AlgorithmIdentifier();
310     X509DN subject_dn = X509DN();
311     AlternativeName subject_alt = AlternativeName();
312     
313     opts.sanityCheck();
314     
315     Vector!ubyte pub_key = x509_key.BER_encode(key);
316     PKSigner signer = chooseSigFormat(key, hash_fn, sig_algo);
317     loadInfo(opts, subject_dn, subject_alt);
318     __gshared immutable size_t PKCS10_VERSION = 0;
319     auto extensions = X509Extensions(true);
320     
321     extensions.add(new BasicConstraints(opts.is_CA, opts.path_limit));
322 
323     extensions.add(new KeyUsage(opts.is_CA ? KeyConstraints.KEY_CERT_SIGN | KeyConstraints.CRL_SIGN : findConstraints(key, opts.constraints)));
324     
325     extensions.add(new ExtendedKeyUsage(opts.ex_constraints));
326     extensions.add(new SubjectAlternativeName(subject_alt));
327     
328     DEREncoder tbs_req;
329     
330     tbs_req.startCons(ASN1Tag.SEQUENCE)
331             .encode(PKCS10_VERSION)
332             .encode(subject_dn)
333             .rawBytes(pub_key)
334             .startExplicit(0);
335     
336     if (opts.challenge != "")
337     {
338         ASN1String challenge = ASN1String(opts.challenge, ASN1Tag.DIRECTORY_STRING);
339         
340         tbs_req.encode(Attribute("PKCS9.ChallengePassword", DEREncoder().encode(challenge).getContentsUnlocked()));
341     }
342     
343     tbs_req.encode(Attribute("PKCS9.ExtensionRequest",
344                       DEREncoder()
345                       .startCons(ASN1Tag.SEQUENCE)
346                       .encode(extensions)
347                       .endCons()
348                       .getContentsUnlocked()
349                       )
350                    ).endExplicit().endCons();
351     const Vector!ubyte req = X509Object.makeSigned(signer, rng, sig_algo, tbs_req.getContents());
352     return PKCS10Request(&req);
353 }
354 
355 /*
356 * Load information from the X509_Cert_Options
357 */
358 private void loadInfo(in X509CertOptions opts, 
359                       ref X509DN subject_dn,
360                       ref AlternativeName subject_alt)
361 {
362     subject_dn.addAttribute("X520.CommonName", opts.common_name);
363     subject_dn.addAttribute("X520.Country", opts.country);
364     subject_dn.addAttribute("X520.State", opts.state);
365     subject_dn.addAttribute("X520.Locality", opts.locality);
366     subject_dn.addAttribute("X520.Organization", opts.organization);
367     subject_dn.addAttribute("X520.OrganizationalUnit", opts.org_unit);
368     subject_dn.addAttribute("X520.SerialNumber", opts.serial_number);
369     subject_alt = AlternativeName(opts.email, opts.uri, opts.dns, opts.ip);
370     subject_alt.addOthername(OIDS.lookup("PKIX.XMPPAddr"), opts.xmpp, ASN1Tag.UTF8_STRING);
371 }