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 }