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