• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 12969367069

25 Jan 2025 11:33PM UTC coverage: 91.224% (-0.003%) from 91.227%
12969367069

Pull #4593

github

web-flow
Merge 212e6285e into 79027d241
Pull Request #4593: New interface for PKCS8 key encryption

93636 of 102644 relevant lines covered (91.22%)

11516421.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.94
/src/lib/pubkey/pkcs8.cpp
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
secure_vector<uint8_t> PKCS8_extract(DataSource& source, AlgorithmIdentifier& pbe_alg_id) {
479✔
31
   secure_vector<uint8_t> key_data;
479✔
32

33
   BER_Decoder(source).start_sequence().decode(pbe_alg_id).decode(key_data, ASN1_Type::OctetString).verify_end();
958✔
34

35
   return key_data;
479✔
36
}
×
37

38
/*
39
* PEM decode and/or decrypt a private key
40
*/
41
secure_vector<uint8_t> PKCS8_decode(DataSource& source,
4,754✔
42
                                    const std::function<std::string()>& get_passphrase,
43
                                    AlgorithmIdentifier& pk_alg_id,
44
                                    bool is_encrypted) {
45
   AlgorithmIdentifier pbe_alg_id;
4,754✔
46
   secure_vector<uint8_t> key_data, key;
4,754✔
47

48
   try {
4,754✔
49
      if(ASN1::maybe_BER(source) && !PEM_Code::matches(source)) {
4,754✔
50
         if(is_encrypted) {
2,156✔
51
            key_data = PKCS8_extract(source, pbe_alg_id);
506✔
52
         } else {
53
            // todo read more efficiently
54
            while(!source.end_of_data()) {
1,199,015✔
55
               uint8_t b;
1,197,112✔
56
               size_t read = source.read_byte(b);
1,197,112✔
57
               if(read) {
1,197,112✔
58
                  key_data.push_back(b);
1,197,112✔
59
               }
60
            }
61
         }
62
      } else {
63
         std::string label;
2,597✔
64
         key_data = PEM_Code::decode(source, label);
5,132✔
65

66
         // todo remove autodetect for pem as well?
67
         if(label == "PRIVATE KEY") {
2,535✔
68
            is_encrypted = false;
69
         } else if(label == "ENCRYPTED PRIVATE KEY") {
254✔
70
            DataSource_Memory key_source(key_data);
316✔
71
            key_data = PKCS8_extract(key_source, pbe_alg_id);
452✔
72
         } else {
226✔
73
            throw PKCS8_Exception(fmt("Unknown PEM label '{}'", label));
28✔
74
         }
75
      }
2,597✔
76

77
      if(key_data.empty()) {
4,663✔
78
         throw PKCS8_Exception("No key data found");
×
79
      }
80
   } catch(Decoding_Error& e) {
91✔
81
      throw Decoding_Error("PKCS #8 private key decoding", e);
83✔
82
   }
83✔
83

84
   try {
4,663✔
85
      if(is_encrypted) {
4,663✔
86
         if(pbe_alg_id.oid().to_formatted_string() != "PBE-PKCS5v20") {
479✔
87
            throw PKCS8_Exception(fmt("Unknown PBE type {}", pbe_alg_id.oid()));
×
88
         }
89

90
#if defined(BOTAN_HAS_PKCS5_PBES2)
91
         key = pbes2_decrypt(key_data, get_passphrase(), pbe_alg_id.parameters());
1,437✔
92
#else
93
         BOTAN_UNUSED(get_passphrase);
94
         throw Decoding_Error("Private key is encrypted but PBES2 was disabled in build");
95
#endif
96
      } else {
97
         key = key_data;
4,184✔
98
      }
99

100
      BER_Decoder(key)
9,326✔
101
         .start_sequence()
4,569✔
102
         .decode_and_check<size_t>(0, "Unknown PKCS #8 version number")
9,037✔
103
         .decode(pk_alg_id)
4,468✔
104
         .decode(key, ASN1_Type::OctetString)
4,328✔
105
         .discard_remaining()
4,328✔
106
         .end_cons();
4,328✔
107
   } catch(std::exception& e) {
335✔
108
      throw Decoding_Error("PKCS #8 private key decoding", e);
335✔
109
   }
335✔
110
   return key;
8,656✔
111
}
5,180✔
112

113
}  // namespace
114

115
/*
116
* PEM encode a PKCS #8 private key, unencrypted
117
*/
118
std::string PEM_encode(const Private_Key& key) {
61✔
119
   return PEM_Code::encode(key.private_key_info(), "PRIVATE KEY");
183✔
120
}
121

122
std::string EncryptedPrivateKey::as_pem() const {
248✔
123
   return PEM_Code::encode(m_pkcs8, "ENCRYPTED PRIVATE KEY");
248✔
124
}
125

126
std::string KeyEncryptionOptions::default_cipher() {
53✔
127
   return "AES-256/CBC";
53✔
128
}
129

130
std::string KeyEncryptionOptions::default_pwhash() {
55✔
131
   // TODO(Botan4) Consider changing this to Scrypt
132
   return "SHA-512";
55✔
133
}
134

135
std::chrono::milliseconds KeyEncryptionOptions::default_pwhash_duration() {
6✔
136
   return std::chrono::milliseconds(300);
6✔
137
}
138

139
KeyEncryptionOptions::KeyEncryptionOptions(std::string_view cipher,
247✔
140
                                           std::string_view pwhash,
141
                                           std::chrono::milliseconds pwhash_duration) :
247✔
142
      m_cipher(cipher.empty() ? default_cipher() : cipher),
497✔
143
      m_pwhash(pwhash.empty() ? default_pwhash() : pwhash),
499✔
144
      m_pwhash_duration(pwhash_duration) {
247✔
145
   BOTAN_ARG_CHECK(OID::from_name(m_cipher).has_value(), "Cipher must have an OID assigned");
494✔
146
   BOTAN_ARG_CHECK(OID::from_name(m_pwhash).has_value(), "Password hash must have an OID assigned");
494✔
147
   BOTAN_ARG_CHECK(m_pwhash_duration.value().count() > 0, "Invalid password hash duration");
247✔
148
}
247✔
149

150
KeyEncryptionOptions::KeyEncryptionOptions(std::string_view cipher, std::string_view pwhash, size_t pwhash_iterations) :
44✔
151
      m_cipher(cipher.empty() ? default_cipher() : cipher),
132✔
152
      m_pwhash(pwhash.empty() ? default_pwhash() : pwhash),
132✔
153
      m_pwhash_iterations(pwhash_iterations) {
44✔
154
   BOTAN_ARG_CHECK(OID::from_name(m_cipher).has_value(), "Cipher must have an OID assigned");
88✔
155
   BOTAN_ARG_CHECK(OID::from_name(m_pwhash).has_value(), "Password hash must have an OID assigned");
88✔
156
   BOTAN_ARG_CHECK(m_pwhash_iterations.value() >= 1, "Invalid password hash iteration count");
44✔
157
}
44✔
158

159
KeyEncryptionOptions KeyEncryptionOptions::_from_pbe_string(std::string_view pbe_algo, std::chrono::milliseconds msec) {
×
160
   SCAN_Name request(pbe_algo);
×
161

162
   if(request.arg_count() != 2 || (request.algo_name() != "PBE-PKCS5v20" && request.algo_name() != "PBES2")) {
×
163
      throw Invalid_Argument(fmt("Unsupported PBE '{}'", pbe_algo));
×
164
   }
165

166
   return KeyEncryptionOptions(request.arg(0), request.arg(1), msec);
×
167
}
×
168

169
EncryptedPrivateKey encrypt_private_key(const Private_Key& key,
291✔
170
                                        std::string_view password,
171
                                        RandomNumberGenerator& rng,
172
                                        const KeyEncryptionOptions& options) {
173
   auto raw_pkcs8 = key.private_key_info();
291✔
174

175
   size_t iterations_out = 0;
291✔
176

177
   const auto [alg_id, pkcs8] = [&]() -> std::pair<AlgorithmIdentifier, std::vector<uint8_t>> {
291✔
178
#if defined(BOTAN_HAS_PKCS5_PBES2)
179
      const auto& cipher = options.cipher();
291✔
180
      const auto& pwhash = options.pwhash();
291✔
181

182
      if(options.using_duration()) {
291✔
183
         return pbes2_encrypt_msec(raw_pkcs8, password, options.pwhash_msec(), &iterations_out, cipher, pwhash, rng);
247✔
184
      } else {
185
         return pbes2_encrypt_iter(raw_pkcs8, password, options.pwhash_iterations(), cipher, pwhash, rng);
44✔
186
      }
187
#else
188
      BOTAN_UNUSED(password, rng);
189
      throw Encoding_Error("Cannot encrypt PKCS8 because PBES2 was disabled in build");
190
#endif
191
   }();
291✔
192

193
   std::vector<uint8_t> output;
291✔
194
   DER_Encoder der(output);
291✔
195
   der.start_sequence().encode(alg_id).encode(pkcs8, ASN1_Type::OctetString).end_cons();
291✔
196

197
   // The iterations_out will not be set for Scrypt
198
   if(options.using_duration()) {
291✔
199
      return EncryptedPrivateKey(output, iterations_out);
247✔
200
   } else {
201
      return EncryptedPrivateKey(output, options.pwhash_iterations());
44✔
202
   }
203
}
873✔
204

205
namespace {
206

207
/*
208
* Extract a private key (encrypted/unencrypted) and return it
209
*/
210
std::unique_ptr<Private_Key> load_key(DataSource& source,
4,754✔
211
                                      const std::function<std::string()>& get_pass,
212
                                      bool is_encrypted) {
213
   AlgorithmIdentifier alg_id;
4,754✔
214
   secure_vector<uint8_t> pkcs8_key = PKCS8_decode(source, get_pass, alg_id, is_encrypted);
4,754✔
215

216
   const std::string alg_name = alg_id.oid().human_name_or_empty();
4,328✔
217
   if(alg_name.empty()) {
4,328✔
218
      throw PKCS8_Exception(fmt("Unknown algorithm OID {}", alg_id.oid()));
4✔
219
   }
220

221
   return load_private_key(alg_id, pkcs8_key);
7,676✔
222
}
9,082✔
223

224
}  // namespace
225

226
/*
227
* Extract an encrypted private key and return it
228
*/
229
std::unique_ptr<Private_Key> load_key(DataSource& source, const std::function<std::string()>& get_pass) {
1✔
230
   return load_key(source, get_pass, true);
1✔
231
}
232

233
std::unique_ptr<Private_Key> load_key(std::span<const uint8_t> source,
×
234
                                      const std::function<std::string()>& get_passphrase) {
235
   Botan::DataSource_Memory ds(source);
×
236
   return load_key(ds, get_passphrase);
×
237
}
×
238

239
std::unique_ptr<Private_Key> load_key(std::span<const uint8_t> source, std::string_view pass) {
×
240
   Botan::DataSource_Memory ds(source);
×
241
   return load_key(ds, pass);
×
242
}
×
243

244
std::unique_ptr<Private_Key> load_key(std::span<const uint8_t> source) {
1✔
245
   Botan::DataSource_Memory ds(source);
1✔
246
   return load_key(ds);
1✔
247
}
1✔
248

249
/*
250
* Extract an encrypted private key and return it
251
*/
252
std::unique_ptr<Private_Key> load_key(DataSource& source, std::string_view pass) {
518✔
253
   return load_key(
518✔
254
      source, [pass]() { return std::string(pass); }, true);
1,515✔
255
}
256

257
/*
258
* Extract an unencrypted private key and return it
259
*/
260
std::unique_ptr<Private_Key> load_key(DataSource& source) {
4,235✔
261
   auto fail_fn = []() -> std::string {
4,235✔
262
      throw PKCS8_Exception("Internal error: Attempt to read password for unencrypted key");
×
263
   };
264

265
   return load_key(source, fail_fn, false);
7,064✔
266
}
267

268
}  // namespace Botan::PKCS8
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc