/* * PKCS #8 * (C) 1999-2010,2014,2018 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include #include #include #include #include #include #include #include #include #if defined(BOTAN_HAS_PKCS5_PBES2) #include #endif namespace Botan { namespace PKCS8 { namespace { /* * Get info from an EncryptedPrivateKeyInfo */ secure_vector PKCS8_extract(DataSource& source, AlgorithmIdentifier& pbe_alg_id) { secure_vector key_data; BER_Decoder(source) .start_sequence() .decode(pbe_alg_id) .decode(key_data, ASN1_Type::OCTET_STRING) .verify_end(); return key_data; } /* * PEM decode and/or decrypt a private key */ secure_vector PKCS8_decode( DataSource& source, std::function get_passphrase, AlgorithmIdentifier& pk_alg_id, bool is_encrypted) { AlgorithmIdentifier pbe_alg_id; secure_vector key_data, key; try { if(ASN1::maybe_BER(source) && !PEM_Code::matches(source)) { if(is_encrypted) { key_data = PKCS8_extract(source, pbe_alg_id); } else { // todo read more efficiently while(!source.end_of_data()) { uint8_t b; size_t read = source.read_byte(b); if(read) { key_data.push_back(b); } } } } else { std::string label; key_data = PEM_Code::decode(source, label); // todo remove autodetect for pem as well? if(label == "PRIVATE KEY") is_encrypted = false; else if(label == "ENCRYPTED PRIVATE KEY") { DataSource_Memory key_source(key_data); key_data = PKCS8_extract(key_source, pbe_alg_id); } else throw PKCS8_Exception("Unknown PEM label " + label); } if(key_data.empty()) throw PKCS8_Exception("No key data found"); } catch(Decoding_Error& e) { throw Decoding_Error("PKCS #8 private key decoding", e); } try { if(is_encrypted) { if(OIDS::oid2str_or_throw(pbe_alg_id.get_oid()) != "PBE-PKCS5v20") throw PKCS8_Exception("Unknown PBE type " + pbe_alg_id.get_oid().to_string()); #if defined(BOTAN_HAS_PKCS5_PBES2) key = pbes2_decrypt(key_data, get_passphrase(), pbe_alg_id.get_parameters()); #else BOTAN_UNUSED(get_passphrase); throw Decoding_Error("Private key is encrypted but PBES2 was disabled in build"); #endif } else key = key_data; BER_Decoder(key) .start_sequence() .decode_and_check(0, "Unknown PKCS #8 version number") .decode(pk_alg_id) .decode(key, ASN1_Type::OCTET_STRING) .discard_remaining() .end_cons(); } catch(std::exception& e) { throw Decoding_Error("PKCS #8 private key decoding", e); } return key; } } /* * PEM encode a PKCS #8 private key, unencrypted */ std::string PEM_encode(const Private_Key& key) { return PEM_Code::encode(key.private_key_info(), "PRIVATE KEY"); } #if defined(BOTAN_HAS_PKCS5_PBES2) namespace { std::pair choose_pbe_params(const std::string& pbe_algo, const std::string& key_algo) { if(pbe_algo.empty()) { /* * For algorithms where we are using a non-RFC format anyway, default to * SIV or GCM. For others (RSA, ECDSA, ...) default to something widely * compatible. */ const bool nonstandard_pk = (key_algo == "McEliece" || key_algo == "XMSS"); if(nonstandard_pk) { #if defined(BOTAN_HAS_AEAD_SIV) && defined(BOTAN_HAS_SHA2_64) return std::make_pair("AES-256/SIV", "SHA-512"); #elif defined(BOTAN_HAS_AEAD_GCM) && defined(BOTAN_HAS_SHA2_64) return std::make_pair("AES-256/GCM", "SHA-512"); #endif } // Default is something compatible with everyone else return std::make_pair("AES-256/CBC", "SHA-256"); } SCAN_Name request(pbe_algo); if(request.arg_count() != 2 || (request.algo_name() != "PBE-PKCS5v20" && request.algo_name() != "PBES2")) { throw Invalid_Argument("Unsupported PBE " + pbe_algo); } return std::make_pair(request.arg(0), request.arg(1)); } } #endif /* * BER encode a PKCS #8 private key, encrypted */ std::vector BER_encode(const Private_Key& key, RandomNumberGenerator& rng, const std::string& pass, std::chrono::milliseconds msec, const std::string& pbe_algo) { #if defined(BOTAN_HAS_PKCS5_PBES2) const auto pbe_params = choose_pbe_params(pbe_algo, key.algo_name()); const std::pair> pbe_info = pbes2_encrypt_msec(PKCS8::BER_encode(key), pass, msec, nullptr, pbe_params.first, pbe_params.second, rng); std::vector output; DER_Encoder der(output); der.start_sequence() .encode(pbe_info.first) .encode(pbe_info.second, ASN1_Type::OCTET_STRING) .end_cons(); return output; #else BOTAN_UNUSED(key, rng, pass, msec, pbe_algo); throw Encoding_Error("PKCS8::BER_encode cannot encrypt because PBES2 was disabled in build"); #endif } /* * PEM encode a PKCS #8 private key, encrypted */ std::string PEM_encode(const Private_Key& key, RandomNumberGenerator& rng, const std::string& pass, std::chrono::milliseconds msec, const std::string& pbe_algo) { if(pass.empty()) return PEM_encode(key); return PEM_Code::encode(PKCS8::BER_encode(key, rng, pass, msec, pbe_algo), "ENCRYPTED PRIVATE KEY"); } /* * BER encode a PKCS #8 private key, encrypted */ std::vector BER_encode_encrypted_pbkdf_iter(const Private_Key& key, RandomNumberGenerator& rng, const std::string& pass, size_t pbkdf_iterations, const std::string& cipher, const std::string& pbkdf_hash) { #if defined(BOTAN_HAS_PKCS5_PBES2) const std::pair> pbe_info = pbes2_encrypt_iter(key.private_key_info(), pass, pbkdf_iterations, cipher.empty() ? "AES-256/CBC" : cipher, pbkdf_hash.empty() ? "SHA-256" : pbkdf_hash, rng); std::vector output; DER_Encoder der(output); der.start_sequence() .encode(pbe_info.first) .encode(pbe_info.second, ASN1_Type::OCTET_STRING) .end_cons(); return output; #else BOTAN_UNUSED(key, rng, pass, pbkdf_iterations, cipher, pbkdf_hash); throw Encoding_Error("PKCS8::BER_encode_encrypted_pbkdf_iter cannot encrypt because PBES2 disabled in build"); #endif } /* * PEM encode a PKCS #8 private key, encrypted */ std::string PEM_encode_encrypted_pbkdf_iter(const Private_Key& key, RandomNumberGenerator& rng, const std::string& pass, size_t pbkdf_iterations, const std::string& cipher, const std::string& pbkdf_hash) { return PEM_Code::encode( PKCS8::BER_encode_encrypted_pbkdf_iter(key, rng, pass, pbkdf_iterations, cipher, pbkdf_hash), "ENCRYPTED PRIVATE KEY"); } /* * BER encode a PKCS #8 private key, encrypted */ std::vector BER_encode_encrypted_pbkdf_msec(const Private_Key& key, RandomNumberGenerator& rng, const std::string& pass, std::chrono::milliseconds pbkdf_msec, size_t* pbkdf_iterations, const std::string& cipher, const std::string& pbkdf_hash) { #if defined(BOTAN_HAS_PKCS5_PBES2) const std::pair> pbe_info = pbes2_encrypt_msec(key.private_key_info(), pass, pbkdf_msec, pbkdf_iterations, cipher.empty() ? "AES-256/CBC" : cipher, pbkdf_hash.empty() ? "SHA-256" : pbkdf_hash, rng); std::vector output; DER_Encoder(output) .start_sequence() .encode(pbe_info.first) .encode(pbe_info.second, ASN1_Type::OCTET_STRING) .end_cons(); return output; #else BOTAN_UNUSED(key, rng, pass, pbkdf_msec, pbkdf_iterations, cipher, pbkdf_hash); throw Encoding_Error("BER_encode_encrypted_pbkdf_msec cannot encrypt because PBES2 disabled in build"); #endif } /* * PEM encode a PKCS #8 private key, encrypted */ std::string PEM_encode_encrypted_pbkdf_msec(const Private_Key& key, RandomNumberGenerator& rng, const std::string& pass, std::chrono::milliseconds pbkdf_msec, size_t* pbkdf_iterations, const std::string& cipher, const std::string& pbkdf_hash) { return PEM_Code::encode( PKCS8::BER_encode_encrypted_pbkdf_msec(key, rng, pass, pbkdf_msec, pbkdf_iterations, cipher, pbkdf_hash), "ENCRYPTED PRIVATE KEY"); } namespace { /* * Extract a private key (encrypted/unencrypted) and return it */ std::unique_ptr load_key(DataSource& source, std::function get_pass, bool is_encrypted) { AlgorithmIdentifier alg_id; secure_vector pkcs8_key = PKCS8_decode(source, get_pass, alg_id, is_encrypted); const std::string alg_name = OIDS::oid2str_or_empty(alg_id.get_oid()); if(alg_name.empty()) throw PKCS8_Exception("Unknown algorithm OID: " + alg_id.get_oid().to_string()); return load_private_key(alg_id, pkcs8_key); } } /* * Extract an encrypted private key and return it */ std::unique_ptr load_key(DataSource& source, std::function get_pass) { return load_key(source, get_pass, true); } /* * Extract an encrypted private key and return it */ std::unique_ptr load_key(DataSource& source, const std::string& pass) { // We need to use bind rather than a lambda capturing `pass` here in order to avoid a Clang 8 bug. // See https://github.com/randombit/botan/issues/2255. return load_key(source, std::bind([](const std::string p) { return p; }, pass), true); } /* * Extract an unencrypted private key and return it */ std::unique_ptr load_key(DataSource& source) { auto fail_fn = []() -> std::string { throw PKCS8_Exception("Internal error: Attempt to read password for unencrypted key"); }; return load_key(source, fail_fn, false); } } }