diff options
-rwxr-xr-x | configure.py | 2 | ||||
-rw-r--r-- | doc/news.rst | 6 | ||||
-rw-r--r-- | src/lib/prov/tpm/info.txt | 13 | ||||
-rw-r--r-- | src/lib/prov/tpm/tpm.cpp | 458 | ||||
-rw-r--r-- | src/lib/prov/tpm/tpm.h | 182 | ||||
-rw-r--r-- | src/lib/prov/tpm/uuid.h | 101 |
6 files changed, 760 insertions, 2 deletions
diff --git a/configure.py b/configure.py index 925f2d768..0eb5eaa56 100755 --- a/configure.py +++ b/configure.py @@ -364,7 +364,7 @@ def process_command_line(args): help='minimize build') # Should be derived from info.txt but this runs too early - third_party = ['boost', 'bzip2', 'lzma', 'openssl', 'sqlite3', 'zlib'] + third_party = ['boost', 'bzip2', 'lzma', 'openssl', 'sqlite3', 'zlib', 'tpm'] for mod in third_party: mods_group.add_option('--with-%s' % (mod), diff --git a/doc/news.rst b/doc/news.rst index 1d6a8b87e..39753a752 100644 --- a/doc/news.rst +++ b/doc/news.rst @@ -15,7 +15,11 @@ Version 1.11.26, Not Yet Released * Enable RdRand entropy source on Windows/MSVC. GH #364 -* Add Intel's RdSeed as entropy source +* Add Intel's RdSeed as entropy source. GH #370 + +* Add preliminary support for accessing TPM v1.2 devices. Currently + random number generation, RSA key generation, and signing are + supported. Tested using Trousers and an ST TPM Version 1.11.25, 2015-12-07 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/lib/prov/tpm/info.txt b/src/lib/prov/tpm/info.txt new file mode 100644 index 000000000..f0860f3ec --- /dev/null +++ b/src/lib/prov/tpm/info.txt @@ -0,0 +1,13 @@ +define TPM 20151126 + +load_on vendor + +<libs> +all -> tspi +</libs> + +<requires> +hash_id +rsa +rng +</requires> 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); + +} diff --git a/src/lib/prov/tpm/tpm.h b/src/lib/prov/tpm/tpm.h new file mode 100644 index 000000000..ce5eabe1c --- /dev/null +++ b/src/lib/prov/tpm/tpm.h @@ -0,0 +1,182 @@ +/* +* TPM 1.2 interface +* (C) 2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/exceptn.h> +#include <botan/pk_keys.h> +#include <botan/bigint.h> +#include <botan/rng.h> +#include <botan/uuid.h> + +//TODO remove this +#include <tss/tspi.h> + +namespace Botan { + +class TPM_Error : public Exception + { + public: + TPM_Error(const std::string& err) : Exception(err) {} + }; + +/** +* Creates a connection to the TPM. All other TPM types take and hold +* a TPM_Context reference, so all other objects must be deallocated +* before ~TPM_Context runs. +* +* Use nullptr for the srk_password to indicate the well known secret +* (ie, an unencrypted SRK). This is usually what you want. +* +* TODO: handling owner password? +*/ +class BOTAN_DLL TPM_Context + { + public: + /** + * User callback for getting the PIN. Will be passed the best available + * description of what we are attempting to load. + */ + typedef std::function<std::string (std::string)> pin_cb; + + TPM_Context(pin_cb cb, const char* srk_password); + + ~TPM_Context(); + + // Get data from the TPM's RNG, whatever that is + void gen_random(uint8_t out[], size_t out_len); + + // Uses Tspi_TPM_StirRandom to add data to TPM's internal pool + void stir_random(const uint8_t in[], size_t in_len); + + std::string get_user_pin(const std::string& who) + { + return m_pin_cb(who); + } + + uint32_t current_counter(); + + TSS_HCONTEXT handle() const { return m_ctx; } + TSS_HKEY srk() const { return m_srk; } + + private: + std::function<std::string (std::string)> m_pin_cb; + TSS_HCONTEXT m_ctx; + TSS_HKEY m_srk; + TSS_HTPM m_tpm; + }; + +class BOTAN_DLL TPM_RNG : public RandomNumberGenerator + { + public: + TPM_RNG(TPM_Context& ctx) : m_ctx(ctx) {} + + void randomize(byte out[], size_t out_len) override + { + m_ctx.gen_random(out, out_len); + } + + void clear() override {} + + std::string name() const override { return "TPM_RNG"; } + + size_t reseed_with_sources(Entropy_Sources&, + size_t, + std::chrono::milliseconds) override + { + // TODO: poll and stir + return 0; + } + + void add_entropy(const byte in[], size_t in_len) override + { + m_ctx.stir_random(in, in_len); + } + + bool is_seeded() const override { return true; } + private: + TPM_Context& m_ctx; +}; + +enum class TPM_Storage_Type { User, System }; + +/* +* Also implements the public interface, but does not have usable +* TODO: derive from RSA_PublicKey??? +*/ +class BOTAN_DLL TPM_PrivateKey : public Private_Key + { + public: + // TODO: key import? + + /* + * Create a new key on the TPM parented to the SRK + * @param bits must be 1024 or 2048 + */ + TPM_PrivateKey(TPM_Context& ctx, size_t bits, const char* key_password); + + // reference an existing TPM key using URL syntax from GnuTLS + // "tpmkey:uuid=79f07ca9-73ac-478a-9093-11ca6702e774;storage=user" + //TPM_PrivateKey(TPM_Context& ctx, const std::string& tpm_url); + + TPM_PrivateKey(TPM_Context& ctx, + const std::string& uuid, + TPM_Storage_Type storage_type); + + TPM_PrivateKey(TPM_Context& ctx, + const std::vector<uint8_t>& blob); + + /** + * If the key is not currently registered under a known UUID, + * generates a new random UUID and registers the key. + * Returns the access URL. + */ + std::string register_key(TPM_Storage_Type storage_type); + + /* + * Returns the list of all keys (in URL format) registered with the system + */ + static std::vector<std::string> registered_keys(TPM_Context& ctx); + + size_t estimated_strength() const; + + size_t max_input_bits() const; + + AlgorithmIdentifier algorithm_identifier() const; + + std::vector<byte> x509_subject_public_key() const; + + secure_vector<byte> pkcs8_private_key() const; // not implemented + + std::unique_ptr<Public_Key> public_key() const; + + bool check_key(RandomNumberGenerator& rng, bool) const override; + + std::string algo_name() const { return "RSA"; } // ??? + + std::vector<uint8_t> export_blob() const; + + TPM_Context& ctx() const { return m_ctx; } + + TSS_HKEY handle() const { return m_key; } + private: + BigInt get_n() const; + BigInt get_e() const; + + TPM_Context& m_ctx; + TSS_HKEY m_key; + + // Only set for registered keys + UUID m_uuid; + TPM_Storage_Type m_storage; + + // Lazily computed in get_n, get_e + mutable BigInt m_n, m_e; + }; + +// TODO: NVRAM interface +// TODO: PCR measurement, writing, key locking + +} diff --git a/src/lib/prov/tpm/uuid.h b/src/lib/prov/tpm/uuid.h new file mode 100644 index 000000000..3c35da1f4 --- /dev/null +++ b/src/lib/prov/tpm/uuid.h @@ -0,0 +1,101 @@ +/* +* UUID type +* (C) 2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/secmem.h> +#include <botan/hex.h> +#include <sstream> + +namespace Botan { + +// TODO: move to util? +class UUID + { + public: + // Represents an unassigned UUID object + UUID() : m_uuid(0) {} + + UUID(RandomNumberGenerator& rng) + { + m_uuid.resize(16); + rng.randomize(m_uuid.data(), m_uuid.size()); + + // Mark as a random UUID (RFC 4122 sec 4.4) + m_uuid[6] = 0x40 | (m_uuid[6] & 0x0F); + + // Set two reserved bits + m_uuid[8] = 0xC0 | (m_uuid[8] & 0x3F); + } + + UUID(const std::vector<uint8_t>& blob) + { + if(blob.size() != 16) + { + throw Invalid_Argument("Bad UUID blob " + hex_encode(blob)); + } + + m_uuid = blob; + } + + UUID(const std::string& uuid_str) + { + if(uuid_str.size() != 36 || + uuid_str[8] != '-' || + uuid_str[14] != '-' || + uuid_str[19] != '-' || + uuid_str[24] != '-') + { + throw Invalid_Argument("Bad UUID '" + uuid_str + "'"); + } + + std::string just_hex; + for(size_t i = 0; i != uuid_str.size(); ++i) + { + char c = uuid_str[i]; + + if(c == '-') + continue; + + just_hex += c; + } + + m_uuid = hex_decode(just_hex); + + if(m_uuid.size() != 16) + { + throw Invalid_Argument("Bad UUID '" + uuid_str + "'"); + } + } + + + std::string to_string() const + { + std::string h = hex_encode(m_uuid); + + h.insert(8, "-"); + h.insert(14, "-"); + h.insert(19, "-"); + h.insert(24, "-"); + + return h; + } + + const std::vector<uint8_t> binary_value() const { return m_uuid; } + + bool operator==(const UUID& other) + { + return m_uuid == other.m_uuid; + } + + bool operator!=(const UUID& other) { return !(*this == other); } + + bool is_valid() const { return m_uuid.size() == 16; } + + private: + std::vector<uint8_t> m_uuid; + }; + +} |