1 /**
2 * X.509 Public Key
3 * 
4 * Copyright:
5 * (C) 1999-2010 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.pubkey.x509_key;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_PUBLIC_KEY_CRYPTO && BOTAN_HAS_X509_CERTIFICATES):
15 
16 alias x509_key = botan.pubkey.x509_key;
17 
18 public import botan.pubkey.pk_keys;
19 public import botan.asn1.alg_id;
20 public import botan.filters.pipe;
21 import botan.asn1.der_enc;
22 import botan.asn1.ber_dec;
23 import botan.asn1.alg_id;
24 import botan.codec.pem;
25 import botan.pubkey.pk_algs;
26 import botan.utils.types;
27 
28 alias X509Encoding = bool;
29 /**
30 * The two types of X509 encoding supported by Botan.
31 */
32 enum : X509Encoding { RAW_BER, PEM_ }
33 
34 /**
35 * BER encode a key
36 * Params:
37 *  key = the public key to encode
38 * Returns: BER encoding of this key
39 */
40 Vector!ubyte BER_encode(in PublicKey key)
41 {
42     auto der_enc = DEREncoder()
43             .startCons(ASN1Tag.SEQUENCE)
44             .encode(key.algorithmIdentifier())
45 			.encode(key.x509SubjectPublicKey(), ASN1Tag.BIT_STRING);
46 	der_enc.endCons();
47     return der_enc.getContentsUnlocked();
48 }
49 
50 /**
51 * PEM encode a public key into a string.
52 * Params:
53 *  key = the key to encode
54 * Returns: PEM encoded key
55 */
56 string PEM_encode(in PublicKey key)
57 {
58     return PEM.encode(x509_key.BER_encode(key), "PUBLIC KEY");
59 }
60 
61 /**
62 * Create a public key from a data source.
63 * Params:
64 *  source = the source providing the DER or PEM encoded key
65 * Returns: new public key object
66 */
67 PublicKey loadKey(DataSource source)
68 {
69     try {
70         auto alg_id = AlgorithmIdentifier();
71         SecureVector!ubyte key_bits;
72         
73         if (maybeBER(source) && !PEM.matches(source))
74         {
75             BERDecoder(source)
76                     .startCons(ASN1Tag.SEQUENCE)
77                     .decode(alg_id)
78                     .decode(key_bits, ASN1Tag.BIT_STRING)
79                     .verifyEnd()
80                     .endCons();
81         }
82         else
83         {
84             auto ber = DataSourceMemory(PEM.decodeCheckLabel(source, "PUBLIC KEY"));
85             
86             BERDecoder(cast(DataSource)ber)
87                     .startCons(ASN1Tag.SEQUENCE)
88                     .decode(alg_id)
89                     .decode(key_bits, ASN1Tag.BIT_STRING)
90                     .verifyEnd()
91                     .endCons();
92         }
93         
94         if (key_bits.empty)
95             throw new DecodingError("X.509 public key decoding failed");
96         
97         return makePublicKey(alg_id, key_bits);
98     }
99     catch(DecodingError)
100     {
101         throw new DecodingError("X.509 public key decoding failed");
102     }
103 }
104 
105 /**
106 * Create a public key from a file
107 * Params:
108 *  filename = pathname to the file to load
109 * Returns: new public key object
110 */
111 PublicKey loadKey(in string filename)
112 {
113     auto source = DataSourceStream(filename, true);
114     return x509_key.loadKey(cast(DataSource)source);
115 }
116 
117 
118 /**
119 * Create a public key from a memory region.
120 * Params:
121 *  enc = the memory region containing the DER or PEM encoded key
122 * Returns: new public key object
123 */
124 PublicKey loadKey(ALLOC)(auto const ref Vector!(ubyte, ALLOC) enc)
125 {
126     auto source = DataSourceMemory(&enc);
127     return x509_key.loadKey(cast(DataSource)source);
128 }
129 
130 /**
131 * Copy a key.
132 * Params:
133 *  key = the public key to copy
134 * Returns: new public key object
135 */
136 PublicKey copyKey(in PublicKey key)
137 {
138     auto source = DataSourceMemory(PEM_encode(key));
139     return x509_key.loadKey(cast(DataSource)source);
140 }
141 
142 static if (BOTAN_TEST && BOTAN_HAS_X509_CERTIFICATES && BOTAN_HAS_RSA && BOTAN_HAS_DSA):
143 
144 import botan.test;
145 import botan.filters.filters;
146 import botan.rng.auto_rng;
147 import botan.pubkey.algo.rsa;
148 import botan.pubkey.algo.dsa;
149 import botan.pubkey.algo.ecdsa;
150 
151 import botan.cert.x509.x509self;
152 import botan.cert.x509.x509path;
153 import botan.cert.x509.x509_ca;
154 import botan.asn1.asn1_time;
155 import botan.cert.x509.pkcs10;
156 
157 ulong keyId(in PublicKey key)
158 {
159     Pipe pipe = Pipe(new HashFilter("SHA-1", 8));
160     pipe.startMsg();
161     pipe.write(key.algoName());
162     pipe.write(key.algorithmIdentifier().parameters);
163     pipe.write(key.x509SubjectPublicKey());
164     pipe.endMsg();
165     
166     SecureVector!ubyte output = pipe.readAll();
167     
168     if (output.length != 8)
169         throw new InternalError("PublicKey::key_id: Incorrect output size");
170     
171     ulong id = 0;
172     for(uint j = 0; j != 8; ++j)
173         id = (id << 8) | output[j];
174     return id;
175 }
176 
177 
178 /* Return some option sets */
179 X509CertOptions caOpts()
180 {
181     X509CertOptions opts = X509CertOptions("Test CA/US/Botan Project/Testing");
182     
183     opts.uri = "http://botan.randombit.net";
184     opts.dns = "botan.randombit.net";
185     opts.email = "testing@globecsys.com";
186     
187     opts.CAKey(1);
188     
189     return opts;
190 }
191 
192 X509CertOptions reqOpts1()
193 {
194     X509CertOptions opts = X509CertOptions("Test User 1/US/Botan Project/Testing");
195     
196     opts.uri = "http://botan.randombit.net";
197     opts.dns = "botan.randombit.net";
198     opts.email = "testing@globecsys.com";
199     
200     return opts;
201 }
202 
203 X509CertOptions reqOpts2()
204 {
205     X509CertOptions opts = X509CertOptions("Test User 2/US/Botan Project/Testing");
206     
207     opts.uri = "http://botan.randombit.net";
208     opts.dns = "botan.randombit.net";
209     opts.email = "testing@randombit.net";
210     
211     opts.addExConstraint("PKIX.EmailProtection");
212     
213     return opts;
214 }
215 
216 uint checkAgainstCopy(const PrivateKey orig, RandomNumberGenerator rng)
217 {
218 
219     auto copy_priv = pkcs8.copyKey(orig, rng);
220     auto copy_pub = x509_key.copyKey(orig);
221     
222     const string passphrase = "I need work! -Mr. T";
223     auto pem = pkcs8.PEM_encode(orig, rng, passphrase);
224     //logDebug(pem[]);
225     auto enc_source = cast(DataSource)DataSourceMemory(pem);
226     auto copy_priv_enc = pkcs8.loadKey(enc_source, rng, passphrase);
227     ulong orig_id = keyId(orig);
228     ulong pub_id = keyId(copy_pub);
229     ulong priv_id = keyId(copy_priv);
230     ulong priv_enc_id = keyId(copy_priv_enc);
231 
232     if (orig_id != pub_id || orig_id != priv_id || orig_id != priv_enc_id)
233     {
234         logError("Failed copy check for " ~ orig.algoName());
235         return 1;
236     }
237     return 0;
238 }
239 
240 static if (BOTAN_HAS_TESTS && !SKIP_X509_KEY_TEST) unittest
241 {
242     logDebug("Testing x509_key ...");
243 	Unique!AutoSeededRNG rng = new AutoSeededRNG;
244     const string hash_fn = "SHA-256";
245     
246     size_t fails = 0;
247     
248     /* Create the CA's key and self-signed cert */
249     auto ca_key = RSAPrivateKey(*rng, 2048);
250     X509Certificate ca_cert = x509self.createSelfSignedCert(caOpts(), *ca_key, hash_fn, *rng);
251 
252     /* Create user #1's key and cert request */
253     auto dl_group = DLGroup("dsa/botan/2048");
254     auto user1_key = DSAPrivateKey(*rng, dl_group.move);
255     
256     auto opts1 = reqOpts1();
257     PKCS10Request user1_req = x509self.createCertReq(opts1, *user1_key, "SHA-1", *rng);
258     
259     /* Create user #2's key and cert request */
260     static if (BOTAN_HAS_ECDSA) {
261         ECGroup ecc_domain = ECGroup(OID("1.2.840.10045.3.1.7"));
262         auto user2_key = ECDSAPrivateKey(*rng, ecc_domain);
263     } else static if (BOTAN_HAS_RSA) {
264         auto user2_key = RSAPrivateKey(*rng, 1536);
265     } else static assert(false, "Must have ECSA or RSA for X509!");
266     
267     PKCS10Request user2_req = x509self.createCertReq(reqOpts2(), *user2_key, hash_fn, *rng);
268     
269     /* Create the CA object */
270     X509CA ca = X509CA(ca_cert, *ca_key, hash_fn);
271     
272     /* Sign the requests to create the certs */
273     X509Certificate user1_cert = ca.signRequest(user1_req, *rng, X509Time("2008-01-01"), X509Time("2100-01-01"));
274     
275     X509Certificate user2_cert = ca.signRequest(user2_req, *rng, X509Time("2008-01-01"), X509Time("2100-01-01"));
276     X509CRL crl1 = ca.newCRL(*rng);
277     
278     /* Verify the certs */
279     Unique!CertificateStoreInMemory store = new CertificateStoreInMemory();
280     
281     store.addCertificate(ca_cert);
282     
283     PathValidationRestrictions restrictions = PathValidationRestrictions(false);
284     
285     PathValidationResult result_u1 = x509PathValidate(user1_cert, restrictions, *store);
286     if (!result_u1.successfulValidation())
287     {
288         logError("FAILED: User cert #1 did not validate - " ~ result_u1.resultString());
289         ++fails;
290     }
291     
292     PathValidationResult result_u2 = x509PathValidate(user2_cert, restrictions, *store);
293     if (!result_u2.successfulValidation())
294     {
295         logError("FAILED: User cert #2 did not validate - " ~ result_u2.resultString());
296         ++fails;
297     }
298     
299     store.addCrl(crl1);
300     
301     Vector!CRLEntry revoked;
302     auto crl_entry1 = CRLEntry(user1_cert, CESSATION_OF_OPERATION);
303     auto crl_entry2 = CRLEntry(user2_cert);
304     revoked.pushBack(crl_entry1);
305     revoked.pushBack(crl_entry2);
306     
307     X509CRL crl2 = ca.updateCRL(crl1, revoked, *rng);
308     
309     store.addCrl(crl2);
310     
311     result_u1 = x509PathValidate(user1_cert, restrictions, *store);
312     if (result_u1.result() != CertificateStatusCode.CERT_IS_REVOKED)
313     {
314         logError("FAILED: User cert #1 was not revoked - " ~ result_u1.resultString());
315         ++fails;
316     }
317     
318     result_u2 = x509PathValidate(user2_cert, restrictions, *store);
319     if (result_u2.result() != CertificateStatusCode.CERT_IS_REVOKED)
320     {
321         logError("FAILED: User cert #2 was not revoked - " ~ result_u2.resultString());
322         ++fails;
323     }
324 
325     auto crl_entry = CRLEntry(user1_cert, REMOVE_FROM_CRL);
326     revoked.clear();
327     revoked.pushBack(crl_entry);
328     X509CRL crl3 = ca.updateCRL(crl2, revoked, *rng);
329     
330     store.addCrl(crl3);
331     
332     
333     result_u1 = x509PathValidate(user1_cert, restrictions, *store);
334     
335     if (!result_u1.successfulValidation())
336     {
337         logError("FAILED: User cert #1 was not un-revoked - " ~ result_u1.resultString());
338         ++fails;
339     }
340     
341     checkAgainstCopy(*ca_key, *rng);
342     checkAgainstCopy(*user1_key, *rng);
343     checkAgainstCopy(*user2_key, *rng);
344     testReport("X509_key", 5, fails);
345 }