/src/botan/src/lib/pubkey/pkcs8.cpp
Line | Count | Source (jump to first uncovered line) |
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/internal/pbes2.h> |
20 | | #endif |
21 | | |
22 | | namespace Botan { |
23 | | |
24 | | namespace PKCS8 { |
25 | | |
26 | | namespace { |
27 | | |
28 | | /* |
29 | | * Get info from an EncryptedPrivateKeyInfo |
30 | | */ |
31 | | secure_vector<uint8_t> PKCS8_extract(DataSource& source, |
32 | | AlgorithmIdentifier& pbe_alg_id) |
33 | 2 | { |
34 | 2 | secure_vector<uint8_t> key_data; |
35 | | |
36 | 2 | BER_Decoder(source) |
37 | 2 | .start_cons(SEQUENCE) |
38 | 2 | .decode(pbe_alg_id) |
39 | 2 | .decode(key_data, OCTET_STRING) |
40 | 2 | .verify_end(); |
41 | | |
42 | 2 | return key_data; |
43 | 2 | } |
44 | | |
45 | | /* |
46 | | * PEM decode and/or decrypt a private key |
47 | | */ |
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 | 3.39k | { |
54 | 3.39k | AlgorithmIdentifier pbe_alg_id; |
55 | 3.39k | secure_vector<uint8_t> key_data, key; |
56 | | |
57 | 3.39k | try { |
58 | 3.39k | if(ASN1::maybe_BER(source) && !PEM_Code::matches(source)) |
59 | 3.10k | { |
60 | 3.10k | if(is_encrypted) |
61 | 0 | { |
62 | 0 | key_data = PKCS8_extract(source, pbe_alg_id); |
63 | 0 | } |
64 | 3.10k | else |
65 | 3.10k | { |
66 | | // todo read more efficiently |
67 | 1.23M | while(!source.end_of_data()) |
68 | 1.23M | { |
69 | 1.23M | uint8_t b; |
70 | 1.23M | size_t read = source.read_byte(b); |
71 | 1.23M | if(read) |
72 | 1.23M | { |
73 | 1.23M | key_data.push_back(b); |
74 | 1.23M | } |
75 | 1.23M | } |
76 | 3.10k | } |
77 | 3.10k | } |
78 | 289 | else |
79 | 289 | { |
80 | 289 | std::string label; |
81 | 289 | key_data = PEM_Code::decode(source, label); |
82 | | |
83 | | // todo remove autodetect for pem as well? |
84 | 289 | if(label == "PRIVATE KEY") |
85 | 6 | is_encrypted = false; |
86 | 283 | else if(label == "ENCRYPTED PRIVATE KEY") |
87 | 2 | { |
88 | 2 | DataSource_Memory key_source(key_data); |
89 | 2 | key_data = PKCS8_extract(key_source, pbe_alg_id); |
90 | 2 | } |
91 | 281 | else |
92 | 281 | throw PKCS8_Exception("Unknown PEM label " + label); |
93 | 3.11k | } |
94 | | |
95 | 3.11k | if(key_data.empty()) |
96 | 1 | throw PKCS8_Exception("No key data found"); |
97 | 248 | } |
98 | 248 | catch(Decoding_Error& e) |
99 | 248 | { |
100 | 248 | throw Decoding_Error("PKCS #8 private key decoding", e); |
101 | 248 | } |
102 | | |
103 | 3.11k | try |
104 | 3.11k | { |
105 | 3.11k | if(is_encrypted) |
106 | 0 | { |
107 | 0 | if(OIDS::oid2str_or_throw(pbe_alg_id.get_oid()) != "PBE-PKCS5v20") |
108 | 0 | throw PKCS8_Exception("Unknown PBE type " + pbe_alg_id.get_oid().to_string()); |
109 | 0 | #if defined(BOTAN_HAS_PKCS5_PBES2) |
110 | 0 | 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 | 0 | } |
116 | 3.11k | else |
117 | 3.11k | key = key_data; |
118 | | |
119 | 3.11k | BER_Decoder(key) |
120 | 3.11k | .start_cons(SEQUENCE) |
121 | 3.11k | .decode_and_check<size_t>(0, "Unknown PKCS #8 version number") |
122 | 3.11k | .decode(pk_alg_id) |
123 | 3.11k | .decode(key, OCTET_STRING) |
124 | 3.11k | .discard_remaining() |
125 | 3.11k | .end_cons(); |
126 | 3.11k | } |
127 | 3.11k | catch(std::exception& e) |
128 | 990 | { |
129 | 990 | throw Decoding_Error("PKCS #8 private key decoding", e); |
130 | 990 | } |
131 | 2.12k | return key; |
132 | 2.12k | } |
133 | | |
134 | | } |
135 | | |
136 | | /* |
137 | | * BER encode a PKCS #8 private key, unencrypted |
138 | | */ |
139 | | secure_vector<uint8_t> BER_encode(const Private_Key& key) |
140 | 0 | { |
141 | | // keeping around for compat |
142 | 0 | return key.private_key_info(); |
143 | 0 | } |
144 | | |
145 | | /* |
146 | | * PEM encode a PKCS #8 private key, unencrypted |
147 | | */ |
148 | | std::string PEM_encode(const Private_Key& key) |
149 | 0 | { |
150 | 0 | return PEM_Code::encode(PKCS8::BER_encode(key), "PRIVATE KEY"); |
151 | 0 | } |
152 | | |
153 | | #if defined(BOTAN_HAS_PKCS5_PBES2) |
154 | | |
155 | | namespace { |
156 | | |
157 | | std::pair<std::string, std::string> |
158 | | choose_pbe_params(const std::string& pbe_algo, const std::string& key_algo) |
159 | 0 | { |
160 | 0 | if(pbe_algo.empty()) |
161 | 0 | { |
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 | 0 | const bool nonstandard_pk = (key_algo == "McEliece" || key_algo == "XMSS"); |
168 | |
|
169 | 0 | if(nonstandard_pk) |
170 | 0 | { |
171 | 0 | #if defined(BOTAN_HAS_AEAD_SIV) && defined(BOTAN_HAS_SHA2_64) |
172 | 0 | 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 | 0 | } |
177 | | |
178 | | // Default is something compatible with everyone else |
179 | 0 | return std::make_pair("AES-256/CBC", "SHA-256"); |
180 | 0 | } |
181 | | |
182 | 0 | SCAN_Name request(pbe_algo); |
183 | |
|
184 | 0 | if(request.arg_count() != 2 || |
185 | 0 | (request.algo_name() != "PBE-PKCS5v20" && request.algo_name() != "PBES2")) |
186 | 0 | { |
187 | 0 | throw Invalid_Argument("Unsupported PBE " + pbe_algo); |
188 | 0 | } |
189 | | |
190 | 0 | return std::make_pair(request.arg(0), request.arg(1)); |
191 | 0 | } |
192 | | |
193 | | } |
194 | | |
195 | | #endif |
196 | | |
197 | | /* |
198 | | * BER encode a PKCS #8 private key, encrypted |
199 | | */ |
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 | 0 | { |
206 | 0 | #if defined(BOTAN_HAS_PKCS5_PBES2) |
207 | 0 | const auto pbe_params = choose_pbe_params(pbe_algo, key.algo_name()); |
208 | |
|
209 | 0 | const std::pair<AlgorithmIdentifier, std::vector<uint8_t>> pbe_info = |
210 | 0 | pbes2_encrypt_msec(PKCS8::BER_encode(key), pass, msec, nullptr, |
211 | 0 | pbe_params.first, pbe_params.second, rng); |
212 | |
|
213 | 0 | std::vector<uint8_t> output; |
214 | 0 | DER_Encoder der(output); |
215 | 0 | der.start_cons(SEQUENCE) |
216 | 0 | .encode(pbe_info.first) |
217 | 0 | .encode(pbe_info.second, OCTET_STRING) |
218 | 0 | .end_cons(); |
219 | |
|
220 | 0 | 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 | 0 | } |
226 | | |
227 | | /* |
228 | | * PEM encode a PKCS #8 private key, encrypted |
229 | | */ |
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 | 0 | { |
236 | 0 | if(pass.empty()) |
237 | 0 | return PEM_encode(key); |
238 | | |
239 | 0 | return PEM_Code::encode(PKCS8::BER_encode(key, rng, pass, msec, pbe_algo), |
240 | 0 | "ENCRYPTED PRIVATE KEY"); |
241 | 0 | } |
242 | | |
243 | | /* |
244 | | * BER encode a PKCS #8 private key, encrypted |
245 | | */ |
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 | 0 | { |
253 | 0 | #if defined(BOTAN_HAS_PKCS5_PBES2) |
254 | 0 | const std::pair<AlgorithmIdentifier, std::vector<uint8_t>> pbe_info = |
255 | 0 | pbes2_encrypt_iter(key.private_key_info(), |
256 | 0 | pass, pbkdf_iterations, |
257 | 0 | cipher.empty() ? "AES-256/CBC" : cipher, |
258 | 0 | pbkdf_hash.empty() ? "SHA-256" : pbkdf_hash, |
259 | 0 | rng); |
260 | |
|
261 | 0 | std::vector<uint8_t> output; |
262 | 0 | DER_Encoder der(output); |
263 | 0 | der.start_cons(SEQUENCE) |
264 | 0 | .encode(pbe_info.first) |
265 | 0 | .encode(pbe_info.second, OCTET_STRING) |
266 | 0 | .end_cons(); |
267 | |
|
268 | 0 | 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 | 0 | } |
275 | | |
276 | | /* |
277 | | * PEM encode a PKCS #8 private key, encrypted |
278 | | */ |
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 | 0 | { |
286 | 0 | return PEM_Code::encode( |
287 | 0 | PKCS8::BER_encode_encrypted_pbkdf_iter(key, rng, pass, pbkdf_iterations, cipher, pbkdf_hash), |
288 | 0 | "ENCRYPTED PRIVATE KEY"); |
289 | 0 | } |
290 | | |
291 | | /* |
292 | | * BER encode a PKCS #8 private key, encrypted |
293 | | */ |
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 | 0 | { |
302 | 0 | #if defined(BOTAN_HAS_PKCS5_PBES2) |
303 | 0 | const std::pair<AlgorithmIdentifier, std::vector<uint8_t>> pbe_info = |
304 | 0 | pbes2_encrypt_msec(key.private_key_info(), pass, |
305 | 0 | pbkdf_msec, pbkdf_iterations, |
306 | 0 | cipher.empty() ? "AES-256/CBC" : cipher, |
307 | 0 | pbkdf_hash.empty() ? "SHA-256" : pbkdf_hash, |
308 | 0 | rng); |
309 | |
|
310 | 0 | std::vector<uint8_t> output; |
311 | 0 | DER_Encoder(output) |
312 | 0 | .start_cons(SEQUENCE) |
313 | 0 | .encode(pbe_info.first) |
314 | 0 | .encode(pbe_info.second, OCTET_STRING) |
315 | 0 | .end_cons(); |
316 | |
|
317 | 0 | 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 | 0 | } |
323 | | |
324 | | /* |
325 | | * PEM encode a PKCS #8 private key, encrypted |
326 | | */ |
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 | 0 | { |
335 | 0 | return PEM_Code::encode( |
336 | 0 | PKCS8::BER_encode_encrypted_pbkdf_msec(key, rng, pass, pbkdf_msec, pbkdf_iterations, cipher, pbkdf_hash), |
337 | 0 | "ENCRYPTED PRIVATE KEY"); |
338 | 0 | } |
339 | | |
340 | | namespace { |
341 | | |
342 | | /* |
343 | | * Extract a private key (encrypted/unencrypted) and return it |
344 | | */ |
345 | | std::unique_ptr<Private_Key> |
346 | | load_key(DataSource& source, |
347 | | std::function<std::string ()> get_pass, |
348 | | bool is_encrypted) |
349 | 3.39k | { |
350 | 3.39k | AlgorithmIdentifier alg_id; |
351 | 3.39k | secure_vector<uint8_t> pkcs8_key = PKCS8_decode(source, get_pass, alg_id, is_encrypted); |
352 | | |
353 | 3.39k | const std::string alg_name = OIDS::oid2str_or_empty(alg_id.get_oid()); |
354 | 3.39k | if(alg_name.empty()) |
355 | 155 | throw PKCS8_Exception("Unknown algorithm OID: " + |
356 | 155 | alg_id.get_oid().to_string()); |
357 | | |
358 | 3.24k | return load_private_key(alg_id, pkcs8_key); |
359 | 3.24k | } |
360 | | |
361 | | } |
362 | | |
363 | | /* |
364 | | * Extract an encrypted private key and return it |
365 | | */ |
366 | | std::unique_ptr<Private_Key> load_key(DataSource& source, |
367 | | std::function<std::string ()> get_pass) |
368 | 0 | { |
369 | 0 | return load_key(source, get_pass, true); |
370 | 0 | } |
371 | | |
372 | | /* |
373 | | * Extract an encrypted private key and return it |
374 | | */ |
375 | | std::unique_ptr<Private_Key> load_key(DataSource& source, |
376 | | const std::string& pass) |
377 | 0 | { |
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 | 0 | return load_key(source, std::bind([](const std::string p) { return p; }, pass), true); |
381 | 0 | } |
382 | | |
383 | | /* |
384 | | * Extract an unencrypted private key and return it |
385 | | */ |
386 | | std::unique_ptr<Private_Key> load_key(DataSource& source) |
387 | 3.39k | { |
388 | 0 | auto fail_fn = []() -> std::string { |
389 | 0 | throw PKCS8_Exception("Internal error: Attempt to read password for unencrypted key"); |
390 | 0 | }; |
391 | | |
392 | 3.39k | return load_key(source, fail_fn, false); |
393 | 3.39k | } |
394 | | |
395 | | /* |
396 | | * Make a copy of this private key |
397 | | */ |
398 | | std::unique_ptr<Private_Key> copy_key(const Private_Key& key) |
399 | 0 | { |
400 | 0 | DataSource_Memory source(PEM_encode(key)); |
401 | 0 | return PKCS8::load_key(source); |
402 | 0 | } |
403 | | |
404 | | /* |
405 | | * Extract an encrypted private key and return it |
406 | | */ |
407 | | Private_Key* load_key(DataSource& source, |
408 | | RandomNumberGenerator& rng, |
409 | | std::function<std::string ()> get_pass) |
410 | 0 | { |
411 | 0 | BOTAN_UNUSED(rng); |
412 | 0 | return PKCS8::load_key(source, get_pass).release(); |
413 | 0 | } |
414 | | |
415 | | /* |
416 | | * Extract an encrypted private key and return it |
417 | | */ |
418 | | Private_Key* load_key(DataSource& source, |
419 | | RandomNumberGenerator& rng, |
420 | | const std::string& pass) |
421 | 0 | { |
422 | 0 | BOTAN_UNUSED(rng); |
423 | 0 | return PKCS8::load_key(source, pass).release(); |
424 | 0 | } |
425 | | |
426 | | /* |
427 | | * Extract an unencrypted private key and return it |
428 | | */ |
429 | | Private_Key* load_key(DataSource& source, |
430 | | RandomNumberGenerator& rng) |
431 | 0 | { |
432 | 0 | BOTAN_UNUSED(rng); |
433 | 0 | return PKCS8::load_key(source).release(); |
434 | 0 | } |
435 | | |
436 | | #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) |
437 | | |
438 | | /* |
439 | | * Extract an encrypted private key and return it |
440 | | */ |
441 | | Private_Key* load_key(const std::string& fsname, |
442 | | RandomNumberGenerator& rng, |
443 | | std::function<std::string ()> get_pass) |
444 | 0 | { |
445 | 0 | BOTAN_UNUSED(rng); |
446 | 0 | DataSource_Stream in(fsname); |
447 | 0 | return PKCS8::load_key(in, get_pass).release(); |
448 | 0 | } |
449 | | |
450 | | /* |
451 | | * Extract an encrypted private key and return it |
452 | | */ |
453 | | Private_Key* load_key(const std::string& fsname, |
454 | | RandomNumberGenerator& rng, |
455 | | const std::string& pass) |
456 | 0 | { |
457 | 0 | BOTAN_UNUSED(rng); |
458 | 0 | 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 | 0 | return PKCS8::load_key(in, std::bind([](const std::string p) { return p; }, pass)).release(); |
462 | 0 | } |
463 | | |
464 | | /* |
465 | | * Extract an unencrypted private key and return it |
466 | | */ |
467 | | Private_Key* load_key(const std::string& fsname, |
468 | | RandomNumberGenerator& rng) |
469 | 0 | { |
470 | 0 | BOTAN_UNUSED(rng); |
471 | 0 | DataSource_Stream in(fsname); |
472 | 0 | return PKCS8::load_key(in).release(); |
473 | 0 | } |
474 | | #endif |
475 | | |
476 | | /* |
477 | | * Make a copy of this private key |
478 | | */ |
479 | | Private_Key* copy_key(const Private_Key& key, |
480 | | RandomNumberGenerator& rng) |
481 | 0 | { |
482 | 0 | BOTAN_UNUSED(rng); |
483 | 0 | return PKCS8::copy_key(key).release(); |
484 | 0 | } |
485 | | |
486 | | |
487 | | |
488 | | } |
489 | | |
490 | | } |