1 /*
2 * PKCS #8
3 * (C) 1999-2010,2014,2018 Jack Lloyd
4 *
5 * Botan is released under the Simplified BSD License (see license.txt)
6 */
7
8 #include <botan/pkcs8.h>
9 #include <botan/rng.h>
10 #include <botan/der_enc.h>
11 #include <botan/ber_dec.h>
12 #include <botan/asn1_obj.h>
13 #include <botan/oids.h>
14 #include <botan/pem.h>
15 #include <botan/scan_name.h>
16 #include <botan/pk_algs.h>
17
18 #if defined(BOTAN_HAS_PKCS5_PBES2)
19 #include <botan/pbes2.h>
20 #endif
21
22 namespace Botan {
23
24 namespace PKCS8 {
25
26 namespace {
27
28 /*
29 * Get info from an EncryptedPrivateKeyInfo
30 */
PKCS8_extract(DataSource & source,AlgorithmIdentifier & pbe_alg_id)31 secure_vector<uint8_t> PKCS8_extract(DataSource& source,
32 AlgorithmIdentifier& pbe_alg_id)
33 {
34 secure_vector<uint8_t> key_data;
35
36 BER_Decoder(source)
37 .start_cons(SEQUENCE)
38 .decode(pbe_alg_id)
39 .decode(key_data, OCTET_STRING)
40 .verify_end();
41
42 return key_data;
43 }
44
45 /*
46 * PEM decode and/or decrypt a private key
47 */
PKCS8_decode(DataSource & source,std::function<std::string ()> get_passphrase,AlgorithmIdentifier & pk_alg_id,bool is_encrypted)48 secure_vector<uint8_t> PKCS8_decode(
49 DataSource& source,
50 std::function<std::string ()> get_passphrase,
51 AlgorithmIdentifier& pk_alg_id,
52 bool is_encrypted)
53 {
54 AlgorithmIdentifier pbe_alg_id;
55 secure_vector<uint8_t> key_data, key;
56
57 try {
58 if(ASN1::maybe_BER(source) && !PEM_Code::matches(source))
59 {
60 if(is_encrypted)
61 {
62 key_data = PKCS8_extract(source, pbe_alg_id);
63 }
64 else
65 {
66 // todo read more efficiently
67 while(!source.end_of_data())
68 {
69 uint8_t b;
70 size_t read = source.read_byte(b);
71 if(read)
72 {
73 key_data.push_back(b);
74 }
75 }
76 }
77 }
78 else
79 {
80 std::string label;
81 key_data = PEM_Code::decode(source, label);
82
83 // todo remove autodetect for pem as well?
84 if(label == "PRIVATE KEY")
85 is_encrypted = false;
86 else if(label == "ENCRYPTED PRIVATE KEY")
87 {
88 DataSource_Memory key_source(key_data);
89 key_data = PKCS8_extract(key_source, pbe_alg_id);
90 }
91 else
92 throw PKCS8_Exception("Unknown PEM label " + label);
93 }
94
95 if(key_data.empty())
96 throw PKCS8_Exception("No key data found");
97 }
98 catch(Decoding_Error& e)
99 {
100 throw Decoding_Error("PKCS #8 private key decoding", e);
101 }
102
103 try
104 {
105 if(is_encrypted)
106 {
107 if(OIDS::oid2str_or_throw(pbe_alg_id.get_oid()) != "PBE-PKCS5v20")
108 throw PKCS8_Exception("Unknown PBE type " + pbe_alg_id.get_oid().to_string());
109 #if defined(BOTAN_HAS_PKCS5_PBES2)
110 key = pbes2_decrypt(key_data, get_passphrase(), pbe_alg_id.get_parameters());
111 #else
112 BOTAN_UNUSED(get_passphrase);
113 throw Decoding_Error("Private key is encrypted but PBES2 was disabled in build");
114 #endif
115 }
116 else
117 key = key_data;
118
119 BER_Decoder(key)
120 .start_cons(SEQUENCE)
121 .decode_and_check<size_t>(0, "Unknown PKCS #8 version number")
122 .decode(pk_alg_id)
123 .decode(key, OCTET_STRING)
124 .discard_remaining()
125 .end_cons();
126 }
127 catch(std::exception& e)
128 {
129 throw Decoding_Error("PKCS #8 private key decoding", e);
130 }
131 return key;
132 }
133
134 }
135
136 /*
137 * BER encode a PKCS #8 private key, unencrypted
138 */
BER_encode(const Private_Key & key)139 secure_vector<uint8_t> BER_encode(const Private_Key& key)
140 {
141 // keeping around for compat
142 return key.private_key_info();
143 }
144
145 /*
146 * PEM encode a PKCS #8 private key, unencrypted
147 */
PEM_encode(const Private_Key & key)148 std::string PEM_encode(const Private_Key& key)
149 {
150 return PEM_Code::encode(PKCS8::BER_encode(key), "PRIVATE KEY");
151 }
152
153 #if defined(BOTAN_HAS_PKCS5_PBES2)
154
155 namespace {
156
157 std::pair<std::string, std::string>
choose_pbe_params(const std::string & pbe_algo,const std::string & key_algo)158 choose_pbe_params(const std::string& pbe_algo, const std::string& key_algo)
159 {
160 if(pbe_algo.empty())
161 {
162 /*
163 * For algorithms where we are using a non-RFC format anyway, default to
164 * SIV or GCM. For others (RSA, ECDSA, ...) default to something widely
165 * compatible.
166 */
167 const bool nonstandard_pk = (key_algo == "McEliece" || key_algo == "XMSS");
168
169 if(nonstandard_pk)
170 {
171 #if defined(BOTAN_HAS_AEAD_SIV) && defined(BOTAN_HAS_SHA2_64)
172 return std::make_pair("AES-256/SIV", "SHA-512");
173 #elif defined(BOTAN_HAS_AEAD_GCM) && defined(BOTAN_HAS_SHA2_64)
174 return std::make_pair("AES-256/GCM", "SHA-512");
175 #endif
176 }
177
178 // Default is something compatible with everyone else
179 return std::make_pair("AES-256/CBC", "SHA-256");
180 }
181
182 SCAN_Name request(pbe_algo);
183
184 if(request.arg_count() != 2 ||
185 (request.algo_name() != "PBE-PKCS5v20" && request.algo_name() != "PBES2"))
186 {
187 throw Invalid_Argument("Unsupported PBE " + pbe_algo);
188 }
189
190 return std::make_pair(request.arg(0), request.arg(1));
191 }
192
193 }
194
195 #endif
196
197 /*
198 * BER encode a PKCS #8 private key, encrypted
199 */
BER_encode(const Private_Key & key,RandomNumberGenerator & rng,const std::string & pass,std::chrono::milliseconds msec,const std::string & pbe_algo)200 std::vector<uint8_t> BER_encode(const Private_Key& key,
201 RandomNumberGenerator& rng,
202 const std::string& pass,
203 std::chrono::milliseconds msec,
204 const std::string& pbe_algo)
205 {
206 #if defined(BOTAN_HAS_PKCS5_PBES2)
207 const auto pbe_params = choose_pbe_params(pbe_algo, key.algo_name());
208
209 const std::pair<AlgorithmIdentifier, std::vector<uint8_t>> pbe_info =
210 pbes2_encrypt_msec(PKCS8::BER_encode(key), pass, msec, nullptr,
211 pbe_params.first, pbe_params.second, rng);
212
213 std::vector<uint8_t> output;
214 DER_Encoder der(output);
215 der.start_cons(SEQUENCE)
216 .encode(pbe_info.first)
217 .encode(pbe_info.second, OCTET_STRING)
218 .end_cons();
219
220 return output;
221 #else
222 BOTAN_UNUSED(key, rng, pass, msec, pbe_algo);
223 throw Encoding_Error("PKCS8::BER_encode cannot encrypt because PBES2 was disabled in build");
224 #endif
225 }
226
227 /*
228 * PEM encode a PKCS #8 private key, encrypted
229 */
PEM_encode(const Private_Key & key,RandomNumberGenerator & rng,const std::string & pass,std::chrono::milliseconds msec,const std::string & pbe_algo)230 std::string PEM_encode(const Private_Key& key,
231 RandomNumberGenerator& rng,
232 const std::string& pass,
233 std::chrono::milliseconds msec,
234 const std::string& pbe_algo)
235 {
236 if(pass.empty())
237 return PEM_encode(key);
238
239 return PEM_Code::encode(PKCS8::BER_encode(key, rng, pass, msec, pbe_algo),
240 "ENCRYPTED PRIVATE KEY");
241 }
242
243 /*
244 * BER encode a PKCS #8 private key, encrypted
245 */
BER_encode_encrypted_pbkdf_iter(const Private_Key & key,RandomNumberGenerator & rng,const std::string & pass,size_t pbkdf_iterations,const std::string & cipher,const std::string & pbkdf_hash)246 std::vector<uint8_t> BER_encode_encrypted_pbkdf_iter(const Private_Key& key,
247 RandomNumberGenerator& rng,
248 const std::string& pass,
249 size_t pbkdf_iterations,
250 const std::string& cipher,
251 const std::string& pbkdf_hash)
252 {
253 #if defined(BOTAN_HAS_PKCS5_PBES2)
254 const std::pair<AlgorithmIdentifier, std::vector<uint8_t>> pbe_info =
255 pbes2_encrypt_iter(key.private_key_info(),
256 pass, pbkdf_iterations,
257 cipher.empty() ? "AES-256/CBC" : cipher,
258 pbkdf_hash.empty() ? "SHA-256" : pbkdf_hash,
259 rng);
260
261 std::vector<uint8_t> output;
262 DER_Encoder der(output);
263 der.start_cons(SEQUENCE)
264 .encode(pbe_info.first)
265 .encode(pbe_info.second, OCTET_STRING)
266 .end_cons();
267
268 return output;
269
270 #else
271 BOTAN_UNUSED(key, rng, pass, pbkdf_iterations, cipher, pbkdf_hash);
272 throw Encoding_Error("PKCS8::BER_encode_encrypted_pbkdf_iter cannot encrypt because PBES2 disabled in build");
273 #endif
274 }
275
276 /*
277 * PEM encode a PKCS #8 private key, encrypted
278 */
PEM_encode_encrypted_pbkdf_iter(const Private_Key & key,RandomNumberGenerator & rng,const std::string & pass,size_t pbkdf_iterations,const std::string & cipher,const std::string & pbkdf_hash)279 std::string PEM_encode_encrypted_pbkdf_iter(const Private_Key& key,
280 RandomNumberGenerator& rng,
281 const std::string& pass,
282 size_t pbkdf_iterations,
283 const std::string& cipher,
284 const std::string& pbkdf_hash)
285 {
286 return PEM_Code::encode(
287 PKCS8::BER_encode_encrypted_pbkdf_iter(key, rng, pass, pbkdf_iterations, cipher, pbkdf_hash),
288 "ENCRYPTED PRIVATE KEY");
289 }
290
291 /*
292 * BER encode a PKCS #8 private key, encrypted
293 */
BER_encode_encrypted_pbkdf_msec(const Private_Key & key,RandomNumberGenerator & rng,const std::string & pass,std::chrono::milliseconds pbkdf_msec,size_t * pbkdf_iterations,const std::string & cipher,const std::string & pbkdf_hash)294 std::vector<uint8_t> BER_encode_encrypted_pbkdf_msec(const Private_Key& key,
295 RandomNumberGenerator& rng,
296 const std::string& pass,
297 std::chrono::milliseconds pbkdf_msec,
298 size_t* pbkdf_iterations,
299 const std::string& cipher,
300 const std::string& pbkdf_hash)
301 {
302 #if defined(BOTAN_HAS_PKCS5_PBES2)
303 const std::pair<AlgorithmIdentifier, std::vector<uint8_t>> pbe_info =
304 pbes2_encrypt_msec(key.private_key_info(), pass,
305 pbkdf_msec, pbkdf_iterations,
306 cipher.empty() ? "AES-256/CBC" : cipher,
307 pbkdf_hash.empty() ? "SHA-256" : pbkdf_hash,
308 rng);
309
310 std::vector<uint8_t> output;
311 DER_Encoder(output)
312 .start_cons(SEQUENCE)
313 .encode(pbe_info.first)
314 .encode(pbe_info.second, OCTET_STRING)
315 .end_cons();
316
317 return output;
318 #else
319 BOTAN_UNUSED(key, rng, pass, pbkdf_msec, pbkdf_iterations, cipher, pbkdf_hash);
320 throw Encoding_Error("BER_encode_encrypted_pbkdf_msec cannot encrypt because PBES2 disabled in build");
321 #endif
322 }
323
324 /*
325 * PEM encode a PKCS #8 private key, encrypted
326 */
PEM_encode_encrypted_pbkdf_msec(const Private_Key & key,RandomNumberGenerator & rng,const std::string & pass,std::chrono::milliseconds pbkdf_msec,size_t * pbkdf_iterations,const std::string & cipher,const std::string & pbkdf_hash)327 std::string PEM_encode_encrypted_pbkdf_msec(const Private_Key& key,
328 RandomNumberGenerator& rng,
329 const std::string& pass,
330 std::chrono::milliseconds pbkdf_msec,
331 size_t* pbkdf_iterations,
332 const std::string& cipher,
333 const std::string& pbkdf_hash)
334 {
335 return PEM_Code::encode(
336 PKCS8::BER_encode_encrypted_pbkdf_msec(key, rng, pass, pbkdf_msec, pbkdf_iterations, cipher, pbkdf_hash),
337 "ENCRYPTED PRIVATE KEY");
338 }
339
340 namespace {
341
342 /*
343 * Extract a private key (encrypted/unencrypted) and return it
344 */
345 std::unique_ptr<Private_Key>
load_key(DataSource & source,std::function<std::string ()> get_pass,bool is_encrypted)346 load_key(DataSource& source,
347 std::function<std::string ()> get_pass,
348 bool is_encrypted)
349 {
350 AlgorithmIdentifier alg_id;
351 secure_vector<uint8_t> pkcs8_key = PKCS8_decode(source, get_pass, alg_id, is_encrypted);
352
353 const std::string alg_name = OIDS::oid2str_or_empty(alg_id.get_oid());
354 if(alg_name.empty())
355 throw PKCS8_Exception("Unknown algorithm OID: " +
356 alg_id.get_oid().to_string());
357
358 return load_private_key(alg_id, pkcs8_key);
359 }
360
361 }
362
363 /*
364 * Extract an encrypted private key and return it
365 */
load_key(DataSource & source,std::function<std::string ()> get_pass)366 std::unique_ptr<Private_Key> load_key(DataSource& source,
367 std::function<std::string ()> get_pass)
368 {
369 return load_key(source, get_pass, true);
370 }
371
372 /*
373 * Extract an encrypted private key and return it
374 */
load_key(DataSource & source,const std::string & pass)375 std::unique_ptr<Private_Key> load_key(DataSource& source,
376 const std::string& pass)
377 {
378 // We need to use bind rather than a lambda capturing `pass` here in order to avoid a Clang 8 bug.
379 // See https://github.com/randombit/botan/issues/2255.
380 return load_key(source, std::bind([](const std::string p) { return p; }, pass), true);
381 }
382
383 /*
384 * Extract an unencrypted private key and return it
385 */
load_key(DataSource & source)386 std::unique_ptr<Private_Key> load_key(DataSource& source)
387 {
388 auto fail_fn = []() -> std::string {
389 throw PKCS8_Exception("Internal error: Attempt to read password for unencrypted key");
390 };
391
392 return load_key(source, fail_fn, false);
393 }
394
395 /*
396 * Make a copy of this private key
397 */
copy_key(const Private_Key & key)398 std::unique_ptr<Private_Key> copy_key(const Private_Key& key)
399 {
400 DataSource_Memory source(PEM_encode(key));
401 return PKCS8::load_key(source);
402 }
403
404 /*
405 * Extract an encrypted private key and return it
406 */
load_key(DataSource & source,RandomNumberGenerator & rng,std::function<std::string ()> get_pass)407 Private_Key* load_key(DataSource& source,
408 RandomNumberGenerator& rng,
409 std::function<std::string ()> get_pass)
410 {
411 BOTAN_UNUSED(rng);
412 return PKCS8::load_key(source, get_pass).release();
413 }
414
415 /*
416 * Extract an encrypted private key and return it
417 */
load_key(DataSource & source,RandomNumberGenerator & rng,const std::string & pass)418 Private_Key* load_key(DataSource& source,
419 RandomNumberGenerator& rng,
420 const std::string& pass)
421 {
422 BOTAN_UNUSED(rng);
423 return PKCS8::load_key(source, pass).release();
424 }
425
426 /*
427 * Extract an unencrypted private key and return it
428 */
load_key(DataSource & source,RandomNumberGenerator & rng)429 Private_Key* load_key(DataSource& source,
430 RandomNumberGenerator& rng)
431 {
432 BOTAN_UNUSED(rng);
433 return PKCS8::load_key(source).release();
434 }
435
436 #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
437
438 /*
439 * Extract an encrypted private key and return it
440 */
load_key(const std::string & fsname,RandomNumberGenerator & rng,std::function<std::string ()> get_pass)441 Private_Key* load_key(const std::string& fsname,
442 RandomNumberGenerator& rng,
443 std::function<std::string ()> get_pass)
444 {
445 BOTAN_UNUSED(rng);
446 DataSource_Stream in(fsname);
447 return PKCS8::load_key(in, get_pass).release();
448 }
449
450 /*
451 * Extract an encrypted private key and return it
452 */
load_key(const std::string & fsname,RandomNumberGenerator & rng,const std::string & pass)453 Private_Key* load_key(const std::string& fsname,
454 RandomNumberGenerator& rng,
455 const std::string& pass)
456 {
457 BOTAN_UNUSED(rng);
458 DataSource_Stream in(fsname);
459 // We need to use bind rather than a lambda capturing `pass` here in order to avoid a Clang 8 bug.
460 // See https://github.com/randombit/botan/issues/2255.
461 return PKCS8::load_key(in, std::bind([](const std::string p) { return p; }, pass)).release();
462 }
463
464 /*
465 * Extract an unencrypted private key and return it
466 */
load_key(const std::string & fsname,RandomNumberGenerator & rng)467 Private_Key* load_key(const std::string& fsname,
468 RandomNumberGenerator& rng)
469 {
470 BOTAN_UNUSED(rng);
471 DataSource_Stream in(fsname);
472 return PKCS8::load_key(in).release();
473 }
474 #endif
475
476 /*
477 * Make a copy of this private key
478 */
copy_key(const Private_Key & key,RandomNumberGenerator & rng)479 Private_Key* copy_key(const Private_Key& key,
480 RandomNumberGenerator& rng)
481 {
482 BOTAN_UNUSED(rng);
483 return PKCS8::copy_key(key).release();
484 }
485
486
487
488 }
489
490 }
491