diff options
Diffstat (limited to 'src/lib/prov/tpm/tpm.cpp')
-rw-r--r-- | src/lib/prov/tpm/tpm.cpp | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/src/lib/prov/tpm/tpm.cpp b/src/lib/prov/tpm/tpm.cpp new file mode 100644 index 000000000..9a29be395 --- /dev/null +++ b/src/lib/prov/tpm/tpm.cpp @@ -0,0 +1,458 @@ +/* +* TPM 1.2 interface +* (C) 2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/tpm.h> +#include <botan/rsa.h> +#include <botan/hash.h> +#include <botan/hash_id.h> +#include <botan/der_enc.h> +#include <botan/workfactor.h> +#include <botan/internal/pk_utils.h> +#include <sstream> + +#include <tss/platform.h> +#include <tss/tspi.h> +#include <trousers/trousers.h> + +// TODO: dynamically load the TPM libraries? + +namespace Botan { + +namespace { + +void tss_error(TSS_RESULT res, const char* expr, const char* file, int line) + { + std::ostringstream err; + err << "TPM error " << Trspi_Error_String(res) + << " layer " << Trspi_Error_Layer(res) + << " in " << expr << " at " << file << ":" << line; + + throw TPM_Error(err.str()); + } + +TSS_FLAG bit_flag(size_t bits) + { + switch(bits) + { + // 512 supported, but ignored and rejected here + case 1024: + return TSS_KEY_SIZE_1024; + case 2048: + return TSS_KEY_SIZE_2048; + + // Most? v1.2 TPMs only support 1024 and 2048 bit keys ... + case 4096: + return TSS_KEY_SIZE_4096; + case 8192: + return TSS_KEY_SIZE_8192; + case 16384: + return TSS_KEY_SIZE_16384; + default: + throw Invalid_Argument("Unsupported TPM key size " + std::to_string(bits)); + } + } + +bool is_srk_uuid(const UUID& uuid) + { + static const byte srk[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; + const std::vector<uint8_t>& b = uuid.binary_value(); + return (b.size() == 16 && same_mem(b.data(), srk, 16)); + } + + +#define TSPI_CHECK_SUCCESS(expr) do { \ + TSS_RESULT res = expr; \ + if(res != TSS_SUCCESS) \ + tss_error(res, #expr, __FILE__, __LINE__); \ + } while(0) + +std::vector<uint8_t> get_obj_attr(TSS_HCONTEXT ctx, + TSS_HOBJECT obj, + TSS_FLAG flag, + TSS_FLAG sub_flag) + { + BYTE *data = nullptr; + UINT32 data_len = 0; + TSPI_CHECK_SUCCESS(::Tspi_GetAttribData(obj, flag, sub_flag, &data_len, &data)); + + std::vector<uint8_t> r(data, data + data_len); + + TSPI_CHECK_SUCCESS(::Tspi_Context_FreeMemory(ctx, data)); + + return r; + } + +void set_policy_secret(TSS_HPOLICY policy, const char* secret) + { + if(secret) + { + TSPI_CHECK_SUCCESS(::Tspi_Policy_SetSecret(policy, + TSS_SECRET_MODE_PLAIN, + std::strlen(secret), + (BYTE*)secret)); + } + else + { + static const uint8_t nullpass[20] = { 0 }; + + TSPI_CHECK_SUCCESS(::Tspi_Policy_SetSecret(policy, + TSS_SECRET_MODE_SHA1, + sizeof(nullpass), + const_cast<BYTE*>(nullpass))); + } + } + +TSS_UUID to_tss_uuid(const UUID& uuid) + { + static_assert(sizeof(TSS_UUID) == 16, "Expected size of packed UUID"); + + TSS_UUID tss_uuid; + std::memcpy(&tss_uuid, uuid.binary_value().data(), 16); + return tss_uuid; + } + +UUID from_tss_uuid(const TSS_UUID& tss_uuid) + { + static_assert(sizeof(TSS_UUID) == 16, "Expected size of packed UUID"); + + std::vector<uint8_t> mem(16); + std::memcpy(mem.data(), &tss_uuid, 16); + UUID uuid(std::move(mem)); + return uuid; + } + +TPM_Storage_Type storage_type_from_tss_flag(TSS_FLAG flag) + { + if(flag == TSS_PS_TYPE_USER) + return TPM_Storage_Type::User; + else if(flag == TSS_PS_TYPE_SYSTEM) + return TPM_Storage_Type::System; + else + throw TPM_Error("Invalid storage flag " + std::to_string(flag)); + } + +std::string format_url(const UUID& uuid, TPM_Storage_Type storage) + { + std::string storage_str = (storage == TPM_Storage_Type::User) ? "user" : "system"; + return "tpmkey:uuid=" + uuid.to_string() + ";storage=" + storage_str; + } + +std::string format_url(const TSS_UUID& tss_uuid, TSS_FLAG store_type) + { + UUID uuid = from_tss_uuid(tss_uuid); + + return format_url(from_tss_uuid(tss_uuid), + storage_type_from_tss_flag(store_type)); + } + +} + +TPM_Context::TPM_Context(pin_cb cb, const char* srk_password) : m_pin_cb(cb) + { + TSPI_CHECK_SUCCESS(::Tspi_Context_Create(&m_ctx)); + TSPI_CHECK_SUCCESS(::Tspi_Context_Connect(m_ctx, nullptr)); + + TSPI_CHECK_SUCCESS(::Tspi_Context_GetTpmObject(m_ctx, &m_tpm)); + + const TSS_UUID SRK_UUID = TSS_UUID_SRK; + + TSPI_CHECK_SUCCESS(::Tspi_Context_LoadKeyByUUID(m_ctx, TSS_PS_TYPE_SYSTEM, SRK_UUID, &m_srk)); + + TSS_HPOLICY srk_policy; + TSPI_CHECK_SUCCESS(::Tspi_GetPolicyObject(m_srk, TSS_POLICY_USAGE, &srk_policy)); + set_policy_secret(srk_policy, srk_password); + + // TODO: leaking policy object here? + // TODO: do we have to cache it? + // TODO: try to use SRK with null, if it fails call the pin cb? + } + +TPM_Context::~TPM_Context() + { + TSPI_CHECK_SUCCESS(::Tspi_Context_CloseObject(m_ctx, m_srk)); + //TSPI_CHECK_SUCCESS(::Tspi_Context_CloseObject(m_ctx, m_tpm)); + TSPI_CHECK_SUCCESS(::Tspi_Context_Close(m_ctx)); + } + +uint32_t TPM_Context::current_counter() + { + uint32_t r = 0; + TSPI_CHECK_SUCCESS(::Tspi_TPM_ReadCounter(m_tpm, &r)); + return r; + } + +void TPM_Context::gen_random(uint8_t out[], size_t out_len) + { + BYTE* mem; + TSPI_CHECK_SUCCESS(::Tspi_TPM_GetRandom(m_tpm, out_len, &mem)); + std::memcpy(out, mem, out_len); + TSPI_CHECK_SUCCESS(::Tspi_Context_FreeMemory(m_ctx, mem)); + } + +void TPM_Context::stir_random(const uint8_t in[], size_t in_len) + { + TSPI_CHECK_SUCCESS(::Tspi_TPM_StirRandom(m_tpm, in_len, const_cast<BYTE*>(in))); + } + +TPM_PrivateKey::TPM_PrivateKey(TPM_Context& ctx, size_t bits, + const char* key_password) : m_ctx(ctx) + { + // TODO: can also do OAEP decryption via binding keys + // TODO: offer signing, binding (decrypt), or legacy (sign + decrypt) keys? + + TSS_FLAG key_flags = bit_flag(bits) | TSS_KEY_VOLATILE | TSS_KEY_TYPE_SIGNING; + + TSS_HKEY key; + TSPI_CHECK_SUCCESS(::Tspi_Context_CreateObject(m_ctx.handle(), TSS_OBJECT_TYPE_RSAKEY, key_flags, &key)); + + TSPI_CHECK_SUCCESS(::Tspi_SetAttribUint32(key, TSS_TSPATTRIB_KEY_INFO, + TSS_TSPATTRIB_KEYINFO_SIGSCHEME, + TSS_SS_RSASSAPKCS1V15_DER)); + + TSS_HPOLICY policy; + TSPI_CHECK_SUCCESS(::Tspi_Context_CreateObject(m_ctx.handle(), TSS_OBJECT_TYPE_POLICY, TSS_POLICY_USAGE, &policy)); + set_policy_secret(policy, key_password); + TSPI_CHECK_SUCCESS(::Tspi_Policy_AssignToObject(policy, key)); + + TSPI_CHECK_SUCCESS(::Tspi_Key_CreateKey(key, ctx.srk(), 0)); + m_key = key; + } + +// reference a registered TPM key +TPM_PrivateKey::TPM_PrivateKey(TPM_Context& ctx, const std::string& uuid_str, + TPM_Storage_Type storage_type) : + m_ctx(ctx), + m_uuid(uuid_str), + m_storage(storage_type) + { + const TSS_FLAG key_ps_type = + (m_storage == TPM_Storage_Type::User) ? TSS_PS_TYPE_USER : TSS_PS_TYPE_SYSTEM; + + TSPI_CHECK_SUCCESS(::Tspi_Context_LoadKeyByUUID(m_ctx.handle(), + key_ps_type, + to_tss_uuid(m_uuid), + &m_key)); + } + +TPM_PrivateKey::TPM_PrivateKey(TPM_Context& ctx, + const std::vector<uint8_t>& blob) : m_ctx(ctx) + { + TSPI_CHECK_SUCCESS(::Tspi_Context_LoadKeyByBlob(m_ctx.handle(), m_ctx.srk(), blob.size(), + const_cast<uint8_t*>(blob.data()), + &m_key)); + + //TSPI_CHECK_SUCCESS(::Tspi_Key_LoadKey(m_key, m_ctx.srk())); + } + +std::string TPM_PrivateKey::register_key(TPM_Storage_Type storage_type) + { + if(!m_uuid.is_valid()) + { + TPM_RNG rng(ctx()); // use system_rng or arg RNG& instead? + m_uuid = UUID(rng); + m_storage = storage_type; + + const TSS_UUID key_uuid = to_tss_uuid(m_uuid); + const TSS_FLAG key_ps_type = + (storage_type == TPM_Storage_Type::User) ? TSS_PS_TYPE_USER : TSS_PS_TYPE_SYSTEM; + + const TSS_UUID srk_uuid = TSS_UUID_SRK; + + TSPI_CHECK_SUCCESS(::Tspi_Context_RegisterKey(m_ctx.handle(), + m_key, + key_ps_type, + key_uuid, + TSS_PS_TYPE_SYSTEM, + srk_uuid)); + + } + + // Presumably we could re-register in the other store and same UUID + // Doesn't seem like what is desired most of the time here + if(storage_type != m_storage) + { + throw TPM_Error("TPM key " + m_uuid.to_string() + + " already registered with different storage type"); + } + + return format_url(m_uuid, m_storage); + } + +std::vector<std::string> TPM_PrivateKey::registered_keys(TPM_Context& ctx) + { + TSS_KM_KEYINFO2* key_info; + UINT32 key_info_size; + + // TODO: does the PS type matter here at all? + TSPI_CHECK_SUCCESS(::Tspi_Context_GetRegisteredKeysByUUID2(ctx.handle(), + TSS_PS_TYPE_SYSTEM, + nullptr, + &key_info_size, + &key_info)); + + std::vector<std::string> r(key_info_size); + + for(size_t i = 0; i != key_info_size; ++i) + { + r[i] = format_url(key_info[i].keyUUID, key_info[i].persistentStorageType); + } + + // TODO: are we supposed to free this memory and if so how? + //TSPI_CHECK_SUCCESS(::Tspi_Context_FreeMemory(ctx.handle(), key_info)); + + return r; + } + +BigInt TPM_PrivateKey::get_n() const + { + if(m_n == 0) + { + m_n = BigInt::decode(get_obj_attr(m_ctx.handle(), m_key, + TSS_TSPATTRIB_RSAKEY_INFO, + TSS_TSPATTRIB_KEYINFO_RSA_MODULUS)); + } + + return m_n; + } + +BigInt TPM_PrivateKey::get_e() const + { + if(m_e == 0) + { + m_e = BigInt::decode(get_obj_attr(m_ctx.handle(), m_key, + TSS_TSPATTRIB_RSAKEY_INFO, + TSS_TSPATTRIB_KEYINFO_RSA_EXPONENT)); + } + + return m_e; + } + +size_t TPM_PrivateKey::estimated_strength() const + { + return if_work_factor(get_n().bits()); + } + +size_t TPM_PrivateKey::max_input_bits() const + { + return get_n().bits(); + } + +AlgorithmIdentifier TPM_PrivateKey::algorithm_identifier() const + { + return AlgorithmIdentifier(get_oid(), + AlgorithmIdentifier::USE_NULL_PARAM); + } + +std::vector<byte> TPM_PrivateKey::x509_subject_public_key() const + { + return DER_Encoder() + .start_cons(SEQUENCE) + .encode(get_n()) + .encode(get_e()) + .end_cons() + .get_contents_unlocked(); + } + +secure_vector<byte> TPM_PrivateKey::pkcs8_private_key() const + { + throw TPM_Error("PKCS #8 export not supported for TPM keys"); + } + +std::vector<uint8_t> TPM_PrivateKey::export_blob() const + { + return get_obj_attr(m_ctx.handle(), m_key, + TSS_TSPATTRIB_KEY_BLOB, + TSS_TSPATTRIB_KEYBLOB_BLOB); + } + +std::unique_ptr<Public_Key> TPM_PrivateKey::public_key() const + { + return std::unique_ptr<Public_Key>(new RSA_PublicKey(get_n(), get_e())); + } + +bool TPM_PrivateKey::check_key(RandomNumberGenerator&, bool) const + { + return true; // TODO do a kat or pairwise check + } + +namespace { + +class TPM_Signing_Operation : public PK_Ops::Signature + { + public: + static TPM_Signing_Operation* make(const Spec& spec) + { + if(auto* key = dynamic_cast<const TPM_PrivateKey*>(&spec.key())) + { + const std::string padding = spec.padding(); + const std::string hash = "SHA-256"; // TODO + return new TPM_Signing_Operation(*key, hash); + } + + return nullptr; + } + + TPM_Signing_Operation(const TPM_PrivateKey& key, + const std::string& hash_name) : + m_key(key), + m_hash(HashFunction::create(hash_name)), + m_hash_id(pkcs_hash_id(hash_name)) + { + } + + void update(const byte msg[], size_t msg_len) override + { + m_hash->update(msg, msg_len); + } + + secure_vector<byte> sign(RandomNumberGenerator&) override + { + /* + * v1.2 TPMs will only sign with PKCS #1 v1.5 padding. SHA-1 is built + * in, all other hash inputs (TSS_HASH_OTHER) are treated as the + * concatenation of the hash OID and hash value and signed with just the + * 01FFFF... prefix. Even when using SHA-1 we compute the hash locally + * since it is going to be much faster than pushing data over the LPC bus. + */ + secure_vector<byte> msg_hash = m_hash->final(); + + std::vector<uint8_t> id_and_msg; + id_and_msg.reserve(m_hash_id.size() + msg_hash.size()); + id_and_msg.insert(id_and_msg.end(), m_hash_id.begin(), m_hash_id.end()); + id_and_msg.insert(id_and_msg.end(), msg_hash.begin(), msg_hash.end()); + + TSS_HCONTEXT ctx = m_key.ctx().handle(); + TSS_HHASH tpm_hash; + TSPI_CHECK_SUCCESS(::Tspi_Context_CreateObject(ctx, TSS_OBJECT_TYPE_HASH, TSS_HASH_OTHER, &tpm_hash)); + TSPI_CHECK_SUCCESS(::Tspi_Hash_SetHashValue(tpm_hash, id_and_msg.size(), id_and_msg.data())); + + BYTE* sig_bytes = nullptr; + UINT32 sig_len = 0; + TSPI_CHECK_SUCCESS(::Tspi_Hash_Sign(tpm_hash, m_key.handle(), &sig_len, &sig_bytes)); + secure_vector<uint8_t> sig(sig_bytes, sig_bytes + sig_len); + + // TODO: RAII for Context_FreeMemory + TSPI_CHECK_SUCCESS(::Tspi_Context_FreeMemory(ctx, sig_bytes)); + + // TODO: RAII for Context_CloseObject + TSPI_CHECK_SUCCESS(::Tspi_Context_CloseObject(ctx, tpm_hash)); + + return sig; + } + + private: + const TPM_PrivateKey& m_key; + std::unique_ptr<HashFunction> m_hash; + std::vector<uint8_t> m_hash_id; + }; + +} + +BOTAN_REGISTER_TYPE(PK_Ops::Signature, TPM_Signing_Operation, "RSA", + TPM_Signing_Operation::make, "tpm", 100); + +} |