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

randombit / botan / 16510618243

25 Jul 2025 12:16AM UTC coverage: 90.699% (+0.006%) from 90.693%
16510618243

Pull #4593

github

web-flow
Merge d70034dd9 into 72deba42e
Pull Request #4593: New interface for PKCS8 key encryption

99992 of 110246 relevant lines covered (90.7%)

12219641.55 hits per line

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

85.4
/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/assert.h>
12
#include <botan/ber_dec.h>
13
#include <botan/der_enc.h>
14
#include <botan/pem.h>
15
#include <botan/pk_algs.h>
16
#include <botan/rng.h>
17
#include <botan/internal/fmt.h>
18
#include <botan/internal/scan_name.h>
19

20
#if defined(BOTAN_HAS_PKCS5_PBES2)
21
   #include <botan/internal/pbes2.h>
22
#endif
23

24
namespace Botan::PKCS8 {
25

26
namespace {
27

28
/*
29
* Get info from an EncryptedPrivateKeyInfo
30
*/
31
secure_vector<uint8_t> PKCS8_extract(DataSource& source, AlgorithmIdentifier& pbe_alg_id) {
479✔
32
   secure_vector<uint8_t> key_data;
479✔
33

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

36
   return key_data;
479✔
37
}
×
38

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

50
   try {
4,765✔
51
      if(ASN1::maybe_BER(source) && !PEM_Code::matches(source)) {
4,765✔
52
         if(is_encrypted) {
2,169✔
53
            key_data = PKCS8_extract(source, pbe_alg_id);
506✔
54
         } else {
55
            // todo read more efficiently
56
            while(auto b = source.read_byte()) {
1,204,501✔
57
               key_data.push_back(*b);
1,202,585✔
58
            }
1,202,585✔
59
         }
60
      } else {
61
         std::string label;
2,595✔
62
         key_data = PEM_Code::decode(source, label);
5,128✔
63

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

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

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

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

98
      BER_Decoder(key)
9,348✔
99
         .start_sequence()
4,580✔
100
         .decode_and_check<size_t>(0, "Unknown PKCS #8 version number")
9,059✔
101
         .decode(pk_alg_id)
4,479✔
102
         .decode(key, ASN1_Type::OctetString)
4,339✔
103
         .discard_remaining()
4,339✔
104
         .end_cons();
4,339✔
105
   } catch(std::exception& e) {
335✔
106
      throw Decoding_Error("PKCS #8 private key decoding", e);
335✔
107
   }
335✔
108
   return key;
8,678✔
109
}
5,191✔
110

111
}  // namespace
112

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

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

124
KeyEncryptionOptions::KeyEncryptionOptions(std::chrono::milliseconds pwhash_duration) :
×
125
      KeyEncryptionOptions(default_cipher(), default_pwhash(), pwhash_duration) {}
×
126

127
KeyEncryptionOptions KeyEncryptionOptions::defaults() {
6✔
128
   return KeyEncryptionOptions(default_cipher(), default_pwhash(), default_pwhash_duration());
12✔
129
}
130

131
size_t KeyEncryptionOptions::pwhash_iterations() const {
88✔
132
   BOTAN_STATE_CHECK(m_pwhash_iterations.has_value());
88✔
133
   return m_pwhash_iterations.value();
88✔
134
}
135

136
std::chrono::milliseconds KeyEncryptionOptions::pwhash_msec() const {
247✔
137
   BOTAN_STATE_CHECK(m_pwhash_duration.has_value());
247✔
138
   return m_pwhash_duration.value();
247✔
139
}
140

141
std::string KeyEncryptionOptions::default_cipher() {
53✔
142
   return "AES-256/CBC";
53✔
143
}
144

145
std::string KeyEncryptionOptions::default_pwhash() {
55✔
146
   // TODO(Botan4) Consider changing this to Scrypt
147
   return "SHA-512";
55✔
148
}
149

150
std::chrono::milliseconds KeyEncryptionOptions::default_pwhash_duration() {
6✔
151
   return std::chrono::milliseconds(300);
6✔
152
}
153

154
KeyEncryptionOptions::KeyEncryptionOptions(std::string_view cipher,
247✔
155
                                           std::string_view pwhash,
156
                                           std::chrono::milliseconds pwhash_duration) :
247✔
157
      m_cipher(cipher.empty() ? default_cipher() : cipher),
497✔
158
      m_pwhash(pwhash.empty() ? default_pwhash() : pwhash),
499✔
159
      m_pwhash_duration(pwhash_duration) {
247✔
160
   BOTAN_ARG_CHECK(OID::from_name(m_cipher).has_value(), "Cipher must have an OID assigned");
494✔
161
   BOTAN_ARG_CHECK(OID::from_name(m_pwhash).has_value(), "Password hash must have an OID assigned");
494✔
162
   BOTAN_ARG_CHECK(m_pwhash_duration.value().count() > 0, "Invalid password hash duration");
247✔
163
}
247✔
164

165
KeyEncryptionOptions::KeyEncryptionOptions(std::string_view cipher, std::string_view pwhash, size_t pwhash_iterations) :
44✔
166
      m_cipher(cipher.empty() ? default_cipher() : cipher),
132✔
167
      m_pwhash(pwhash.empty() ? default_pwhash() : pwhash),
132✔
168
      m_pwhash_iterations(pwhash_iterations) {
44✔
169
   BOTAN_ARG_CHECK(OID::from_name(m_cipher).has_value(), "Cipher must have an OID assigned");
88✔
170
   BOTAN_ARG_CHECK(OID::from_name(m_pwhash).has_value(), "Password hash must have an OID assigned");
88✔
171
   BOTAN_ARG_CHECK(m_pwhash_iterations.value() >= 1, "Invalid password hash iteration count");
44✔
172
}
44✔
173

174
KeyEncryptionOptions KeyEncryptionOptions::_from_pbe_string(std::string_view pbe_algo, std::chrono::milliseconds msec) {
×
175
   SCAN_Name request(pbe_algo);
×
176

177
   if(request.arg_count() != 2 || (request.algo_name() != "PBE-PKCS5v20" && request.algo_name() != "PBES2")) {
×
178
      throw Invalid_Argument(fmt("Unsupported PBE '{}'", pbe_algo));
×
179
   }
180

181
   return KeyEncryptionOptions(request.arg(0), request.arg(1), msec);
×
182
}
×
183

184
EncryptedPrivateKey encrypt_private_key(const Private_Key& key,
291✔
185
                                        std::string_view password,
186
                                        RandomNumberGenerator& rng,
187
                                        const KeyEncryptionOptions& options) {
188
   auto raw_pkcs8 = key.private_key_info();
291✔
189

190
   size_t iterations_out = 0;
291✔
191

192
   const auto [alg_id, pkcs8] = [&]() -> std::pair<AlgorithmIdentifier, std::vector<uint8_t>> {
291✔
193
#if defined(BOTAN_HAS_PKCS5_PBES2)
194
      const auto& cipher = options.cipher();
291✔
195
      const auto& pwhash = options.pwhash();
291✔
196

197
      if(options.using_duration()) {
291✔
198
         return pbes2_encrypt_msec(raw_pkcs8, password, options.pwhash_msec(), &iterations_out, cipher, pwhash, rng);
247✔
199
      } else {
200
         return pbes2_encrypt_iter(raw_pkcs8, password, options.pwhash_iterations(), cipher, pwhash, rng);
44✔
201
      }
202
#else
203
      BOTAN_UNUSED(password, rng);
204
      throw Encoding_Error("Cannot encrypt PKCS8 because PBES2 was disabled in build");
205
#endif
206
   }();
291✔
207

208
   std::vector<uint8_t> output;
291✔
209
   DER_Encoder der(output);
291✔
210
   der.start_sequence().encode(alg_id).encode(pkcs8, ASN1_Type::OctetString).end_cons();
291✔
211

212
   // The iterations_out will not be set for Scrypt
213
   if(options.using_duration()) {
291✔
214
      return EncryptedPrivateKey(output, iterations_out);
247✔
215
   } else {
216
      return EncryptedPrivateKey(output, options.pwhash_iterations());
44✔
217
   }
218
}
873✔
219

220
namespace {
221

222
/*
223
* Extract a private key (encrypted/unencrypted) and return it
224
*/
225
std::unique_ptr<Private_Key> load_key(DataSource& source,
4,765✔
226
                                      const std::function<std::string()>& get_pass,
227
                                      bool is_encrypted) {
228
   AlgorithmIdentifier alg_id;
4,765✔
229
   secure_vector<uint8_t> pkcs8_key = PKCS8_decode(source, get_pass, alg_id, is_encrypted);
4,765✔
230

231
   const std::string alg_name = alg_id.oid().human_name_or_empty();
4,339✔
232
   if(alg_name.empty()) {
4,339✔
233
      throw PKCS8_Exception(fmt("Unknown algorithm OID {}", alg_id.oid()));
4✔
234
   }
235

236
   return load_private_key(alg_id, pkcs8_key);
7,699✔
237
}
9,104✔
238

239
}  // namespace
240

241
/*
242
* Extract an encrypted private key and return it
243
*/
244
std::unique_ptr<Private_Key> load_key(DataSource& source, const std::function<std::string()>& get_pass) {
1✔
245
   return load_key(source, get_pass, true);
1✔
246
}
247

248
std::unique_ptr<Private_Key> load_key(std::span<const uint8_t> source,
×
249
                                      const std::function<std::string()>& get_passphrase) {
250
   Botan::DataSource_Memory ds(source);
×
251
   return load_key(ds, get_passphrase);
×
252
}
×
253

254
std::unique_ptr<Private_Key> load_key(std::span<const uint8_t> source, std::string_view pass) {
×
255
   Botan::DataSource_Memory ds(source);
×
256
   return load_key(ds, pass);
×
257
}
×
258

259
std::unique_ptr<Private_Key> load_key(std::span<const uint8_t> source) {
14✔
260
   Botan::DataSource_Memory ds(source);
14✔
261
   return load_key(ds);
14✔
262
}
14✔
263

264
/*
265
* Extract an encrypted private key and return it
266
*/
267
std::unique_ptr<Private_Key> load_key(DataSource& source, std::string_view pass) {
518✔
268
   return load_key(
518✔
269
      source, [pass]() { return std::string(pass); }, true);
1,515✔
270
}
271

272
/*
273
* Extract an unencrypted private key and return it
274
*/
275
std::unique_ptr<Private_Key> load_key(DataSource& source) {
4,246✔
276
   auto fail_fn = []() -> std::string {
4,246✔
277
      throw PKCS8_Exception("Internal error: Attempt to read password for unencrypted key");
×
278
   };
279

280
   return load_key(source, fail_fn, false);
7,087✔
281
}
282

283
}  // 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