From 63907d907ee9b37f498b762e08efd37ba1f59c6f Mon Sep 17 00:00:00 2001 From: Dhanuka Warusadura Date: Fri, 2 Jul 2021 17:30:06 +0530 Subject: [PATCH] Add TPM2 API and its implementations to egg These changes define the TPM2 API and add its implementations to the incubation area (egg/). Summary of the public API: `egg_tpm2_initialize`: Start a TPM context. `egg_tpm2_finalize`: End a TPM context. `egg_tpm2_generate_master_password`: Generate and returns an encrypted master password in `GBytes` format. TSS Marshaling, GVariant serialization is used. `egg_tpm2_decrypt_master_password`: Decrypts a master password generated from `egg_tpm2_generate_master_password`. TSS Unmarshaling, GVariant deserialization is used. TPM2 API: TSS Enhanced System API (ESAPI) Proposal: [extend file backend to use TPM2 derived encryption keys](https://gitlab.gnome.org/Teams/Engagement/gsoc-2021/-/issues/13) Related MRs: [#86](https://gitlab.gnome.org/GNOME/libsecret/-/merge_requests/86) Related Issues: [#63](https://gitlab.gnome.org/GNOME/libsecret/-/issues/63) --- .gitlab-ci.yml | 4 +- egg/egg-tpm2.c | 484 ++++++++++++++++++++++++++++++++++++++++++++++ egg/egg-tpm2.h | 39 ++++ egg/meson.build | 14 ++ egg/test-tpm2.c | 78 ++++++++ meson.build | 15 ++ meson_options.txt | 1 + 7 files changed, 633 insertions(+), 2 deletions(-) create mode 100644 egg/egg-tpm2.c create mode 100644 egg/egg-tpm2.h create mode 100644 egg/test-tpm2.c diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d1a68d4..731d02d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: - deploy variables: - DEPENDENCIES: dbus-x11 diffutils gcc gjs meson ninja-build python3-dbus python3-gobject redhat-rpm-config + DEPENDENCIES: dbus-x11 diffutils gcc gjs meson ninja-build python3-dbus python3-gobject redhat-rpm-config tpm2-tss-devel CPPCHECK_OPTIONS: "--enable=warning --enable=style --enable=performance --enable=portability --std=c99 --template='{id}:{file}:{line},{severity},{message}'" fedora:Werror: @@ -24,7 +24,7 @@ fedora:Werror: - eval `dbus-launch --sh-syntax` - 'tpm2-abrmd --logger=stdout --tcti=swtpm: --session --allow-root --flush-all &' - 'export TCTI=tabrmd:bus_type=session' - - meson _build -Dwerror=true -Dc_args=-Wno-error=deprecated-declarations + - meson _build -Dwerror=true -Dc_args=-Wno-error=deprecated-declarations -Dtpm2=true - meson compile -C _build - meson test -C _build artifacts: diff --git a/egg/egg-tpm2.c b/egg/egg-tpm2.c new file mode 100644 index 0000000..2812aea --- /dev/null +++ b/egg/egg-tpm2.c @@ -0,0 +1,484 @@ +/* libsecret - TSS interface implementations for libsecret + * + * Copyright (C) 2021 Dhanuka Warusadura + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Author: Dhanuka Warusadura +*/ + +#include "config.h" +#include "egg-tpm2.h" +#include +#include +#include +#include + +#define MAX_BYTE_SIZE 64 + +struct EggTpm2Context { + TSS2_TCTI_CONTEXT *tcti_context; + ESYS_CONTEXT *esys_context; + ESYS_TR primary_key; +}; + +static gboolean +egg_tpm2_generate_primary_key(EggTpm2Context *context, + GError **error) +{ + TSS2_RC ret; + + TPM2B_SENSITIVE_CREATE sensitive_params = { + .size = 0, + .sensitive = { + .userAuth = { + .size = 0, + .buffer = {0}, + }, + .data = { + .size = 0, + .buffer = {0}, + }, + }, + }; + + TPM2B_PUBLIC public_key_param = { + .size = 0, + .publicArea = { + .type = TPM2_ALG_RSA, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = ( + TPMA_OBJECT_USERWITHAUTH | + TPMA_OBJECT_RESTRICTED | + TPMA_OBJECT_DECRYPT | + TPMA_OBJECT_FIXEDTPM | + TPMA_OBJECT_FIXEDPARENT | + TPMA_OBJECT_SENSITIVEDATAORIGIN), + .authPolicy = { + .size = 0, + }, + .parameters.rsaDetail = { + .symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB + }, + .scheme = { + .scheme = TPM2_ALG_NULL + }, + .keyBits = 2048, + .exponent = 0, + }, + .unique.rsa = { + .size = 0, + .buffer = {}, + }, + }, + }; + + TPM2B_DATA outside_info = { + .size = 0, + .buffer = {}, + }; + + TPML_PCR_SELECTION pcrs = { + .count = 0, + }; + + TPM2B_PUBLIC *public; + TPM2B_CREATION_DATA *creation_data; + TPM2B_DIGEST *hash; + TPMT_TK_CREATION *ticket; + + ret = Esys_CreatePrimary(context->esys_context, ESYS_TR_RH_OWNER, + ESYS_TR_PASSWORD, ESYS_TR_NONE, + ESYS_TR_NONE, &sensitive_params, + &public_key_param, &outside_info, + &pcrs, &context->primary_key, &public, + &creation_data, &hash, &ticket); + + if (ret != TSS2_RC_SUCCESS) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Esys_CreatePrimary: %s", Tss2_RC_Decode(ret)); + return FALSE; + } + + Esys_Free(public); + Esys_Free(creation_data); + Esys_Free(hash); + Esys_Free(ticket); + + return TRUE; +} + +static GBytes * +egg_tpm2_generate_random_data(EggTpm2Context *context, + GError **error) +{ + gboolean status = FALSE; + TSS2_RC ret; + TPM2B_DIGEST *random_data; + GBytes *bytes; + + status = egg_tpm2_generate_primary_key(context, error); + if (!status) + return NULL; + + ret = Esys_GetRandom(context->esys_context, ESYS_TR_NONE, + ESYS_TR_NONE, ESYS_TR_NONE, MAX_BYTE_SIZE, + &random_data); + + if (ret != TSS2_RC_SUCCESS) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Esys_GetRandom: %s", Tss2_RC_Decode(ret)); + return NULL; + } + + bytes = g_bytes_new(random_data->buffer, random_data->size); + Esys_Free(random_data); + + return bytes; +} + +EggTpm2Context * +egg_tpm2_initialize(GError **error) +{ + TSS2_RC ret; + EggTpm2Context *context; + gsize n_context; + const gchar *tcti_conf; + + n_context = 1; + context = g_new(EggTpm2Context, n_context); + tcti_conf = g_getenv("TCTI"); + ret = Tss2_TctiLdr_Initialize(tcti_conf, &context->tcti_context); + + if (ret != TSS2_RC_SUCCESS) { + egg_tpm2_finalize(context); + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Tss2_TctiLdr_Initialize: %s", + Tss2_RC_Decode(ret)); + return NULL; + } + + ret = Esys_Initialize(&context->esys_context, + context->tcti_context, NULL); + if (ret != TSS2_RC_SUCCESS) { + egg_tpm2_finalize(context); + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Esys_Initialize: %s", Tss2_RC_Decode(ret)); + return NULL; + } + + ret = Esys_Startup(context->esys_context, TPM2_SU_CLEAR); + if (ret != TSS2_RC_SUCCESS) { + egg_tpm2_finalize(context); + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Esys_Startup: %s", Tss2_RC_Decode(ret)); + return NULL; + } + + return context; +} + +void +egg_tpm2_finalize(EggTpm2Context *context) +{ + if (context->esys_context) + Esys_Finalize(&context->esys_context); + + if (context->tcti_context) + Tss2_TctiLdr_Finalize(&context->tcti_context); + + g_free(context); +} + +GBytes * +egg_tpm2_generate_master_password(EggTpm2Context *context, + GError **error) +{ + TSS2_RC ret; + TPM2B_PRIVATE *out_private; + TPM2B_PUBLIC *out_public; + TPM2B_CREATION_DATA *creation_data; + TPM2B_DIGEST *hash; + TPMT_TK_CREATION *ticket; + gconstpointer data; + gsize size; + GBytes *input; + GBytes *output; + + TPM2B_SENSITIVE_CREATE in_sensitive = { + .size = 0, + .sensitive = { + .data = { + .size = MAX_BYTE_SIZE + } + } + }; + + TPM2B_PUBLIC in_public = { + .size = 0, + .publicArea = { + .type = TPM2_ALG_KEYEDHASH, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = ( + TPMA_OBJECT_USERWITHAUTH | + TPMA_OBJECT_FIXEDTPM | + TPMA_OBJECT_FIXEDPARENT), + .authPolicy = { + .size = 0, + }, + .parameters.keyedHashDetail = { + .scheme = { + .scheme = TPM2_ALG_NULL, + .details = { + .hmac = { + .hashAlg = + TPM2_ALG_SHA256 + } + } + } + }, + .unique.keyedHash = { + .size = 0, + .buffer = {}, + }, + } + }; + + TPM2B_DATA outside_info = { + .size = 0, + .buffer = {} + }; + + TPML_PCR_SELECTION pcrs = { + .count = 0 + }; + + input = egg_tpm2_generate_random_data(context, error); + if (!input) { + g_bytes_unref(input); + return NULL; + } + + data = g_bytes_get_data(input, &size); + g_bytes_unref(input); + + if (size > sizeof(in_sensitive.sensitive.data.buffer)) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Input is too long"); + return NULL; + } + + memcpy(in_sensitive.sensitive.data.buffer, data, size); + in_sensitive.sensitive.data.size = size; + + ret = Esys_Create(context->esys_context, context->primary_key, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &in_sensitive, &in_public, &outside_info, + &pcrs, &out_private, &out_public, &creation_data, + &hash, &ticket); + if (ret != TSS2_RC_SUCCESS) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Esys_Create: %s", Tss2_RC_Decode(ret)); + return NULL; + } + + gsize out_private_offset = 0; + gsize out_public_offset = 0; + GVariant *out_private_variant; + GVariant *out_public_variant; + GVariant *variant; + + guint8 marshaled_out_private[sizeof(*out_private)]; + guint8 marshaled_out_public[sizeof(*out_public)]; + + ret = Tss2_MU_TPM2B_PRIVATE_Marshal(out_private, + marshaled_out_private, + sizeof(marshaled_out_private), + &out_private_offset); + if (ret != TSS2_RC_SUCCESS) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Tss2_MU_TPM2B_PRIVATE_Marshal: %s", + Tss2_RC_Decode(ret)); + return NULL; + } + + out_private_variant = g_variant_new_fixed_array( + G_VARIANT_TYPE_BYTE, + marshaled_out_private, + out_private_offset, + sizeof(guint8)); + + ret = Tss2_MU_TPM2B_PUBLIC_Marshal(out_public, + marshaled_out_public, + sizeof(marshaled_out_public), + &out_public_offset); + if (ret != TSS2_RC_SUCCESS) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Tss2_MU_TPM2B_PUBLIC_Marshal: %s", + Tss2_RC_Decode(ret)); + return NULL; + } + + out_public_variant = g_variant_new_fixed_array( + G_VARIANT_TYPE_BYTE, + marshaled_out_public, + out_public_offset, + sizeof(guint8)); + + variant = g_variant_new("(u@ayu@ay)", + out_private_offset, out_private_variant, + out_public_offset, out_public_variant); + + output = g_variant_get_data_as_bytes(variant); + + g_variant_unref(variant); + Esys_Free(out_public); + Esys_Free(out_private); + Esys_Free(creation_data); + Esys_Free(hash); + Esys_Free(ticket); + + return output; +} + +GBytes * +egg_tpm2_decrypt_master_password(EggTpm2Context *context, + GBytes *input, + GError **error) +{ + TSS2_RC ret; + GBytes *output; + TPM2B_SENSITIVE_DATA *out_data; + GVariant *variant; + gconstpointer data; + gsize out_private_offset = 0; + gsize out_public_offset = 0; + gsize count = 0; + gsize offset = 0; + GVariant *out_private_variant; + GVariant *out_public_variant; + ESYS_TR out_key; + + variant = g_variant_new_from_bytes(G_VARIANT_TYPE( + "(uayuay)"), + input, + TRUE); + + g_variant_get(variant, "(u@ayu@ay)", + &out_private_offset, &out_private_variant, + &out_public_offset, &out_public_variant); + g_variant_unref(variant); + + data = g_variant_get_fixed_array(out_private_variant, + &count, + sizeof(guint8)); + guint8 *marshaled_out_private = g_memdup(data, count); + count = 0; + + TPM2B_PRIVATE out_private = { + .size = 0 + }; + ret = Tss2_MU_TPM2B_PRIVATE_Unmarshal(marshaled_out_private, + out_private_offset, + &offset, + &out_private); + + g_variant_unref(out_private_variant); + g_free(marshaled_out_private); + + if (ret != TSS2_RC_SUCCESS) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Tss2_MU_TPM2B_PRIVATE_Unmarshal: %s", + Tss2_RC_Decode(ret)); + return NULL; + } + + offset = 0; + data = g_variant_get_fixed_array(out_public_variant, + &count, + sizeof(guint8)); + guint8 *marshaled_out_public = g_memdup(data, count); + + TPM2B_PUBLIC out_public = { + .size = 0 + }; + ret = Tss2_MU_TPM2B_PUBLIC_Unmarshal(marshaled_out_public, + out_public_offset, + &offset, + &out_public); + + g_variant_unref(out_public_variant); + g_free(marshaled_out_public); + + if (ret != TSS2_RC_SUCCESS) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Tss2_MU_TPM2B_PUBLIC_Unmarshal: %s", + Tss2_RC_Decode(ret)); + return NULL; + } + + ret = Esys_Load(context->esys_context, context->primary_key, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &out_private, &out_public, &out_key); + if (ret != TSS2_RC_SUCCESS) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Esys_Load: %s", Tss2_RC_Decode(ret)); + return NULL; + } + + ret = Esys_Unseal(context->esys_context, out_key, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &out_data); + if (ret != TSS2_RC_SUCCESS) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Esys_Unseal: %s", Tss2_RC_Decode(ret)); + return NULL; + } + + output = g_bytes_new(out_data->buffer, out_data->size); + Esys_Free(out_data); + + return output; +} diff --git a/egg/egg-tpm2.h b/egg/egg-tpm2.h new file mode 100644 index 0000000..43409cd --- /dev/null +++ b/egg/egg-tpm2.h @@ -0,0 +1,39 @@ +/* libsecret - TSS interface for libsecret + * + * Copyright (C) 2021 Dhanuka Warusadura + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Author: Dhanuka Warusadura +*/ + +#ifndef EGG_TPM2_H_ +#define EGG_TPM2_H_ + +#include +#include + +typedef struct EggTpm2Context EggTpm2Context; + +EggTpm2Context *egg_tpm2_initialize (GError **); +void egg_tpm2_finalize (EggTpm2Context *); +GBytes *egg_tpm2_generate_master_password (EggTpm2Context *, + GError **); +GBytes *egg_tpm2_decrypt_master_password (EggTpm2Context *, + GBytes *, + GError **); + +#endif diff --git a/egg/meson.build b/egg/meson.build index ac7c024..d2badac 100644 --- a/egg/meson.build +++ b/egg/meson.build @@ -18,6 +18,14 @@ if get_option('gcrypt') libegg_deps += gcrypt_dep endif +if get_option('tpm2') + libegg_sources += [ + 'egg-tpm2.c', + ] + + libegg_deps += tss2_deps +endif + libegg = static_library('egg', libegg_sources, dependencies: libegg_deps, @@ -37,6 +45,12 @@ if get_option('gcrypt') ] endif +if get_option('tpm2') + test_names += [ + 'test-tpm2', + ] +endif + foreach _test : test_names test_bin = executable(_test, diff --git a/egg/test-tpm2.c b/egg/test-tpm2.c new file mode 100644 index 0000000..218c310 --- /dev/null +++ b/egg/test-tpm2.c @@ -0,0 +1,78 @@ +/* libsecret - Test TSS interface for libsecret + * + * Copyright (C) 2021 Dhanuka Warusadura + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * Author: Dhanuka Warusadura +*/ + +#include "egg-tpm2.h" + +void +test_egg_tpm2_generate_master_password(void) +{ + EggTpm2Context *context; + GBytes *result; + + GError *error = NULL; + g_assert_no_error(error); + context = egg_tpm2_initialize(&error); + g_assert_nonnull(context); + result = egg_tpm2_generate_master_password(context, &error); + g_assert_nonnull(result); + egg_tpm2_finalize(context); + g_bytes_unref(result); +} + +void +test_egg_tpm2_decrypt_master_password(void) +{ + EggTpm2Context *context; + GBytes *result, *decrypted1, *decrypted2; + + GError *error = NULL; + g_assert_no_error(error); + context = egg_tpm2_initialize(&error); + g_assert_nonnull(context); + result = egg_tpm2_generate_master_password(context, &error); + g_assert_nonnull(result); + decrypted1 = egg_tpm2_decrypt_master_password(context, result, + &error); + g_assert_nonnull(decrypted1); + decrypted2 = egg_tpm2_decrypt_master_password(context, result, + &error); + g_assert_nonnull(decrypted2); + g_assert(g_bytes_equal(decrypted1, decrypted2)); + egg_tpm2_finalize(context); + g_bytes_unref(result); + g_bytes_unref(decrypted1); + g_bytes_unref(decrypted2); +} + +int +main (int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func( + "/tpm/test_egg_tpm2_generate_master_password", + test_egg_tpm2_generate_master_password); + g_test_add_func( + "/tpm/test_egg_tpm2_decrypt_master_password", + test_egg_tpm2_decrypt_master_password); + + return g_test_run(); +} diff --git a/meson.build b/meson.build index 909d0a1..f659a47 100644 --- a/meson.build +++ b/meson.build @@ -36,6 +36,17 @@ gcrypt_dep = dependency('libgcrypt', required: get_option('gcrypt'), ) +min_tss2_version = '3.0.3' +tss2_esys = dependency('tss2-esys', version: '>=' + min_tss2_version, required: get_option('tpm2')) +tss2_mu = dependency('tss2-mu', version: '>=' + min_tss2_version, required: get_option('tpm2')) +tss2_rc = dependency('tss2-rc', version: '>=' + min_tss2_version, required: get_option('tpm2')) +tss2_tctildr = dependency('tss2-tctildr', version: '>=' + min_tss2_version, required: get_option('tpm2')) + +tss2_deps = [] +if tss2_esys.found() and tss2_mu.found() and tss2_rc.found() and tss2_tctildr.found() + tss2_deps += [tss2_esys, tss2_mu, tss2_rc, tss2_tctildr] +endif + # Libraries math = meson.get_compiler('c').find_library('m') @@ -48,9 +59,13 @@ conf.set_quoted('PACKAGE_NAME', meson.project_name()) conf.set_quoted('PACKAGE_STRING', meson.project_name()) conf.set_quoted('PACKAGE_VERSION', meson.project_version()) conf.set('WITH_GCRYPT', get_option('gcrypt')) +conf.set('WITH_TPM', get_option('tpm2')) if get_option('gcrypt') conf.set_quoted('LIBGCRYPT_VERSION', min_libgcrypt_version) endif +if get_option('tpm2') + conf.set_quoted('TSS2_VERSION', min_tss2_version) +endif conf.set('WITH_DEBUG', get_option('debugging')) conf.set('_DEBUG', get_option('debugging')) conf.set('HAVE_MLOCK', meson.get_compiler('c').has_function('mlock')) diff --git a/meson_options.txt b/meson_options.txt index 8858a33..24c8f02 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -6,3 +6,4 @@ option('gtk_doc', type: 'boolean', value: true, description: 'Build reference do option('introspection', type: 'boolean', value: true, description: 'Create GIR file.') option('bashcompdir', type: 'string', value: '', description: 'Override default location for bash completion files') option('bash_completion', type: 'feature', value: 'auto', description: 'Install bash completion files') +option('tpm2', type: 'boolean', value: false, description: 'With TPM2 Software Stack')