/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/internal/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::PKCS8 { |
23 | | |
24 | | namespace { |
25 | | |
26 | | /* |
27 | | * Get info from an EncryptedPrivateKeyInfo |
28 | | */ |
29 | | secure_vector<uint8_t> PKCS8_extract(DataSource& source, |
30 | | AlgorithmIdentifier& pbe_alg_id) |
31 | 15 | { |
32 | 15 | secure_vector<uint8_t> key_data; |
33 | | |
34 | 15 | BER_Decoder(source) |
35 | 15 | .start_sequence() |
36 | 15 | .decode(pbe_alg_id) |
37 | 15 | .decode(key_data, ASN1_Type::OctetString) |
38 | 15 | .verify_end(); |
39 | | |
40 | 15 | return key_data; |
41 | 15 | } |
42 | | |
43 | | /* |
44 | | * PEM decode and/or decrypt a private key |
45 | | */ |
46 | | secure_vector<uint8_t> PKCS8_decode( |
47 | | DataSource& source, |
48 | | const std::function<std::string ()>& get_passphrase, |
49 | | AlgorithmIdentifier& pk_alg_id, |
50 | | bool is_encrypted) |
51 | 4.73k | { |
52 | 4.73k | AlgorithmIdentifier pbe_alg_id; |
53 | 4.73k | secure_vector<uint8_t> key_data, key; |
54 | | |
55 | 4.73k | try { |
56 | 4.73k | if(ASN1::maybe_BER(source) && !PEM_Code::matches(source)) |
57 | 4.47k | { |
58 | 4.47k | if(is_encrypted) |
59 | 0 | { |
60 | 0 | key_data = PKCS8_extract(source, pbe_alg_id); |
61 | 0 | } |
62 | 4.47k | else |
63 | 4.47k | { |
64 | | // todo read more efficiently |
65 | 1.57M | while(!source.end_of_data()) |
66 | 1.56M | { |
67 | 1.56M | uint8_t b; |
68 | 1.56M | size_t read = source.read_byte(b); |
69 | 1.56M | if(read) |
70 | 1.56M | { |
71 | 1.56M | key_data.push_back(b); |
72 | 1.56M | } |
73 | 1.56M | } |
74 | 4.47k | } |
75 | 4.47k | } |
76 | 260 | else |
77 | 260 | { |
78 | 260 | std::string label; |
79 | 260 | key_data = PEM_Code::decode(source, label); |
80 | | |
81 | | // todo remove autodetect for pem as well? |
82 | 260 | if(label == "PRIVATE KEY") |
83 | 1 | is_encrypted = false; |
84 | 259 | else if(label == "ENCRYPTED PRIVATE KEY") |
85 | 15 | { |
86 | 15 | DataSource_Memory key_source(key_data); |
87 | 15 | key_data = PKCS8_extract(key_source, pbe_alg_id); |
88 | 15 | } |
89 | 244 | else |
90 | 244 | throw PKCS8_Exception("Unknown PEM label " + label); |
91 | 260 | } |
92 | | |
93 | 4.49k | if(key_data.empty()) |
94 | 1 | throw PKCS8_Exception("No key data found"); |
95 | 4.49k | } |
96 | 4.73k | catch(Decoding_Error& e) |
97 | 4.73k | { |
98 | 225 | throw Decoding_Error("PKCS #8 private key decoding", e); |
99 | 225 | } |
100 | | |
101 | 4.47k | try |
102 | 4.47k | { |
103 | 4.47k | if(is_encrypted) |
104 | 0 | { |
105 | 0 | if(OIDS::oid2str_or_throw(pbe_alg_id.get_oid()) != "PBE-PKCS5v20") |
106 | 0 | throw PKCS8_Exception("Unknown PBE type " + pbe_alg_id.get_oid().to_string()); |
107 | 0 | #if defined(BOTAN_HAS_PKCS5_PBES2) |
108 | 0 | key = pbes2_decrypt(key_data, get_passphrase(), pbe_alg_id.get_parameters()); |
109 | | #else |
110 | | BOTAN_UNUSED(get_passphrase); |
111 | | throw Decoding_Error("Private key is encrypted but PBES2 was disabled in build"); |
112 | | #endif |
113 | 0 | } |
114 | 4.47k | else |
115 | 4.47k | key = key_data; |
116 | | |
117 | 4.47k | BER_Decoder(key) |
118 | 4.47k | .start_sequence() |
119 | 4.47k | .decode_and_check<size_t>(0, "Unknown PKCS #8 version number") |
120 | 4.47k | .decode(pk_alg_id) |
121 | 4.47k | .decode(key, ASN1_Type::OctetString) |
122 | 4.47k | .discard_remaining() |
123 | 4.47k | .end_cons(); |
124 | 4.47k | } |
125 | 4.47k | catch(std::exception& e) |
126 | 4.47k | { |
127 | 1.11k | throw Decoding_Error("PKCS #8 private key decoding", e); |
128 | 1.11k | } |
129 | 3.36k | return key; |
130 | 4.47k | } |
131 | | |
132 | | } |
133 | | |
134 | | /* |
135 | | * PEM encode a PKCS #8 private key, unencrypted |
136 | | */ |
137 | | std::string PEM_encode(const Private_Key& key) |
138 | 0 | { |
139 | 0 | return PEM_Code::encode(key.private_key_info(), "PRIVATE KEY"); |
140 | 0 | } |
141 | | |
142 | | #if defined(BOTAN_HAS_PKCS5_PBES2) |
143 | | |
144 | | namespace { |
145 | | |
146 | | std::pair<std::string, std::string> |
147 | | choose_pbe_params(const std::string& pbe_algo, const std::string& key_algo) |
148 | 0 | { |
149 | 0 | if(pbe_algo.empty()) |
150 | 0 | { |
151 | | /* |
152 | | * For algorithms where we are using a non-RFC format anyway, default to |
153 | | * SIV or GCM. For others (RSA, ECDSA, ...) default to something widely |
154 | | * compatible. |
155 | | */ |
156 | 0 | const bool nonstandard_pk = (key_algo == "McEliece" || key_algo == "XMSS"); |
157 | |
|
158 | 0 | if(nonstandard_pk) |
159 | 0 | { |
160 | 0 | #if defined(BOTAN_HAS_AEAD_SIV) && defined(BOTAN_HAS_SHA2_64) |
161 | 0 | return std::make_pair("AES-256/SIV", "SHA-512"); |
162 | | #elif defined(BOTAN_HAS_AEAD_GCM) && defined(BOTAN_HAS_SHA2_64) |
163 | | return std::make_pair("AES-256/GCM", "SHA-512"); |
164 | | #endif |
165 | 0 | } |
166 | | |
167 | | // Default is something compatible with everyone else |
168 | 0 | return std::make_pair("AES-256/CBC", "SHA-256"); |
169 | 0 | } |
170 | | |
171 | 0 | SCAN_Name request(pbe_algo); |
172 | |
|
173 | 0 | if(request.arg_count() != 2 || |
174 | 0 | (request.algo_name() != "PBE-PKCS5v20" && request.algo_name() != "PBES2")) |
175 | 0 | { |
176 | 0 | throw Invalid_Argument("Unsupported PBE " + pbe_algo); |
177 | 0 | } |
178 | | |
179 | 0 | return std::make_pair(request.arg(0), request.arg(1)); |
180 | 0 | } |
181 | | |
182 | | } |
183 | | |
184 | | #endif |
185 | | |
186 | | /* |
187 | | * BER encode a PKCS #8 private key, encrypted |
188 | | */ |
189 | | std::vector<uint8_t> BER_encode(const Private_Key& key, |
190 | | RandomNumberGenerator& rng, |
191 | | const std::string& pass, |
192 | | std::chrono::milliseconds msec, |
193 | | const std::string& pbe_algo) |
194 | 0 | { |
195 | 0 | #if defined(BOTAN_HAS_PKCS5_PBES2) |
196 | 0 | const auto pbe_params = choose_pbe_params(pbe_algo, key.algo_name()); |
197 | |
|
198 | 0 | const std::pair<AlgorithmIdentifier, std::vector<uint8_t>> pbe_info = |
199 | 0 | pbes2_encrypt_msec(PKCS8::BER_encode(key), pass, msec, nullptr, |
200 | 0 | pbe_params.first, pbe_params.second, rng); |
201 | |
|
202 | 0 | std::vector<uint8_t> output; |
203 | 0 | DER_Encoder der(output); |
204 | 0 | der.start_sequence() |
205 | 0 | .encode(pbe_info.first) |
206 | 0 | .encode(pbe_info.second, ASN1_Type::OctetString) |
207 | 0 | .end_cons(); |
208 | |
|
209 | 0 | return output; |
210 | | #else |
211 | | BOTAN_UNUSED(key, rng, pass, msec, pbe_algo); |
212 | | throw Encoding_Error("PKCS8::BER_encode cannot encrypt because PBES2 was disabled in build"); |
213 | | #endif |
214 | 0 | } |
215 | | |
216 | | /* |
217 | | * PEM encode a PKCS #8 private key, encrypted |
218 | | */ |
219 | | std::string PEM_encode(const Private_Key& key, |
220 | | RandomNumberGenerator& rng, |
221 | | const std::string& pass, |
222 | | std::chrono::milliseconds msec, |
223 | | const std::string& pbe_algo) |
224 | 0 | { |
225 | 0 | if(pass.empty()) |
226 | 0 | return PEM_encode(key); |
227 | | |
228 | 0 | return PEM_Code::encode(PKCS8::BER_encode(key, rng, pass, msec, pbe_algo), |
229 | 0 | "ENCRYPTED PRIVATE KEY"); |
230 | 0 | } |
231 | | |
232 | | /* |
233 | | * BER encode a PKCS #8 private key, encrypted |
234 | | */ |
235 | | std::vector<uint8_t> BER_encode_encrypted_pbkdf_iter(const Private_Key& key, |
236 | | RandomNumberGenerator& rng, |
237 | | const std::string& pass, |
238 | | size_t pbkdf_iterations, |
239 | | const std::string& cipher, |
240 | | const std::string& pbkdf_hash) |
241 | 0 | { |
242 | 0 | #if defined(BOTAN_HAS_PKCS5_PBES2) |
243 | 0 | const std::pair<AlgorithmIdentifier, std::vector<uint8_t>> pbe_info = |
244 | 0 | pbes2_encrypt_iter(key.private_key_info(), |
245 | 0 | pass, pbkdf_iterations, |
246 | 0 | cipher.empty() ? "AES-256/CBC" : cipher, |
247 | 0 | pbkdf_hash.empty() ? "SHA-256" : pbkdf_hash, |
248 | 0 | rng); |
249 | |
|
250 | 0 | std::vector<uint8_t> output; |
251 | 0 | DER_Encoder der(output); |
252 | 0 | der.start_sequence() |
253 | 0 | .encode(pbe_info.first) |
254 | 0 | .encode(pbe_info.second, ASN1_Type::OctetString) |
255 | 0 | .end_cons(); |
256 | |
|
257 | 0 | return output; |
258 | |
|
259 | | #else |
260 | | BOTAN_UNUSED(key, rng, pass, pbkdf_iterations, cipher, pbkdf_hash); |
261 | | throw Encoding_Error("PKCS8::BER_encode_encrypted_pbkdf_iter cannot encrypt because PBES2 disabled in build"); |
262 | | #endif |
263 | 0 | } |
264 | | |
265 | | /* |
266 | | * PEM encode a PKCS #8 private key, encrypted |
267 | | */ |
268 | | std::string PEM_encode_encrypted_pbkdf_iter(const Private_Key& key, |
269 | | RandomNumberGenerator& rng, |
270 | | const std::string& pass, |
271 | | size_t pbkdf_iterations, |
272 | | const std::string& cipher, |
273 | | const std::string& pbkdf_hash) |
274 | 0 | { |
275 | 0 | return PEM_Code::encode( |
276 | 0 | PKCS8::BER_encode_encrypted_pbkdf_iter(key, rng, pass, pbkdf_iterations, cipher, pbkdf_hash), |
277 | 0 | "ENCRYPTED PRIVATE KEY"); |
278 | 0 | } |
279 | | |
280 | | /* |
281 | | * BER encode a PKCS #8 private key, encrypted |
282 | | */ |
283 | | std::vector<uint8_t> BER_encode_encrypted_pbkdf_msec(const Private_Key& key, |
284 | | RandomNumberGenerator& rng, |
285 | | const std::string& pass, |
286 | | std::chrono::milliseconds pbkdf_msec, |
287 | | size_t* pbkdf_iterations, |
288 | | const std::string& cipher, |
289 | | const std::string& pbkdf_hash) |
290 | 0 | { |
291 | 0 | #if defined(BOTAN_HAS_PKCS5_PBES2) |
292 | 0 | const std::pair<AlgorithmIdentifier, std::vector<uint8_t>> pbe_info = |
293 | 0 | pbes2_encrypt_msec(key.private_key_info(), pass, |
294 | 0 | pbkdf_msec, pbkdf_iterations, |
295 | 0 | cipher.empty() ? "AES-256/CBC" : cipher, |
296 | 0 | pbkdf_hash.empty() ? "SHA-256" : pbkdf_hash, |
297 | 0 | rng); |
298 | |
|
299 | 0 | std::vector<uint8_t> output; |
300 | 0 | DER_Encoder(output) |
301 | 0 | .start_sequence() |
302 | 0 | .encode(pbe_info.first) |
303 | 0 | .encode(pbe_info.second, ASN1_Type::OctetString) |
304 | 0 | .end_cons(); |
305 | |
|
306 | 0 | return output; |
307 | | #else |
308 | | BOTAN_UNUSED(key, rng, pass, pbkdf_msec, pbkdf_iterations, cipher, pbkdf_hash); |
309 | | throw Encoding_Error("BER_encode_encrypted_pbkdf_msec cannot encrypt because PBES2 disabled in build"); |
310 | | #endif |
311 | 0 | } |
312 | | |
313 | | /* |
314 | | * PEM encode a PKCS #8 private key, encrypted |
315 | | */ |
316 | | std::string PEM_encode_encrypted_pbkdf_msec(const Private_Key& key, |
317 | | RandomNumberGenerator& rng, |
318 | | const std::string& pass, |
319 | | std::chrono::milliseconds pbkdf_msec, |
320 | | size_t* pbkdf_iterations, |
321 | | const std::string& cipher, |
322 | | const std::string& pbkdf_hash) |
323 | 0 | { |
324 | 0 | return PEM_Code::encode( |
325 | 0 | PKCS8::BER_encode_encrypted_pbkdf_msec(key, rng, pass, pbkdf_msec, pbkdf_iterations, cipher, pbkdf_hash), |
326 | 0 | "ENCRYPTED PRIVATE KEY"); |
327 | 0 | } |
328 | | |
329 | | namespace { |
330 | | |
331 | | /* |
332 | | * Extract a private key (encrypted/unencrypted) and return it |
333 | | */ |
334 | | std::unique_ptr<Private_Key> |
335 | | load_key(DataSource& source, |
336 | | const std::function<std::string ()>& get_pass, |
337 | | bool is_encrypted) |
338 | 4.73k | { |
339 | 4.73k | AlgorithmIdentifier alg_id; |
340 | 4.73k | secure_vector<uint8_t> pkcs8_key = PKCS8_decode(source, get_pass, alg_id, is_encrypted); |
341 | | |
342 | 4.73k | const std::string alg_name = OIDS::oid2str_or_empty(alg_id.get_oid()); |
343 | 4.73k | if(alg_name.empty()) |
344 | 139 | throw PKCS8_Exception("Unknown algorithm OID: " + |
345 | 139 | alg_id.get_oid().to_string()); |
346 | | |
347 | 4.59k | return load_private_key(alg_id, pkcs8_key); |
348 | 4.73k | } |
349 | | |
350 | | } |
351 | | |
352 | | /* |
353 | | * Extract an encrypted private key and return it |
354 | | */ |
355 | | std::unique_ptr<Private_Key> load_key(DataSource& source, |
356 | | const std::function<std::string ()>& get_pass) |
357 | 0 | { |
358 | 0 | return load_key(source, get_pass, true); |
359 | 0 | } |
360 | | |
361 | | /* |
362 | | * Extract an encrypted private key and return it |
363 | | */ |
364 | | std::unique_ptr<Private_Key> load_key(DataSource& source, |
365 | | const std::string& pass) |
366 | 0 | { |
367 | | // We need to use bind rather than a lambda capturing `pass` here in order to avoid a Clang 8 bug. |
368 | | // See https://github.com/randombit/botan/issues/2255. |
369 | 0 | return load_key(source, std::bind([](const std::string& p) { return p; }, pass), true); |
370 | 0 | } |
371 | | |
372 | | /* |
373 | | * Extract an unencrypted private key and return it |
374 | | */ |
375 | | std::unique_ptr<Private_Key> load_key(DataSource& source) |
376 | 4.73k | { |
377 | 4.73k | auto fail_fn = []() -> std::string { |
378 | 0 | throw PKCS8_Exception("Internal error: Attempt to read password for unencrypted key"); |
379 | 0 | }; |
380 | | |
381 | 4.73k | return load_key(source, fail_fn, false); |
382 | 4.73k | } |
383 | | |
384 | | } |