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