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)
This commit is contained in:
Dhanuka Warusadura 2021-07-02 17:30:06 +05:30
parent 10e5e7abe3
commit 63907d907e
7 changed files with 633 additions and 2 deletions

View File

@ -3,7 +3,7 @@ stages:
- deploy - deploy
variables: 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}'" CPPCHECK_OPTIONS: "--enable=warning --enable=style --enable=performance --enable=portability --std=c99 --template='{id}:{file}:{line},{severity},{message}'"
fedora:Werror: fedora:Werror:
@ -24,7 +24,7 @@ fedora:Werror:
- eval `dbus-launch --sh-syntax` - eval `dbus-launch --sh-syntax`
- 'tpm2-abrmd --logger=stdout --tcti=swtpm: --session --allow-root --flush-all &' - 'tpm2-abrmd --logger=stdout --tcti=swtpm: --session --allow-root --flush-all &'
- 'export TCTI=tabrmd:bus_type=session' - '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 compile -C _build
- meson test -C _build - meson test -C _build
artifacts: artifacts:

484
egg/egg-tpm2.c Normal file
View File

@ -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 <tss2/tss2_esys.h>
#include <tss2/tss2_mu.h>
#include <tss2/tss2_rc.h>
#include <tss2/tss2_tctildr.h>
#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;
}

39
egg/egg-tpm2.h Normal file
View File

@ -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 <glib.h>
#include <gio/gio.h>
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

View File

@ -18,6 +18,14 @@ if get_option('gcrypt')
libegg_deps += gcrypt_dep libegg_deps += gcrypt_dep
endif endif
if get_option('tpm2')
libegg_sources += [
'egg-tpm2.c',
]
libegg_deps += tss2_deps
endif
libegg = static_library('egg', libegg = static_library('egg',
libegg_sources, libegg_sources,
dependencies: libegg_deps, dependencies: libegg_deps,
@ -37,6 +45,12 @@ if get_option('gcrypt')
] ]
endif endif
if get_option('tpm2')
test_names += [
'test-tpm2',
]
endif
foreach _test : test_names foreach _test : test_names
test_bin = executable(_test, test_bin = executable(_test,

78
egg/test-tpm2.c Normal file
View File

@ -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();
}

View File

@ -36,6 +36,17 @@ gcrypt_dep = dependency('libgcrypt',
required: get_option('gcrypt'), 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 # Libraries
math = meson.get_compiler('c').find_library('m') 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_STRING', meson.project_name())
conf.set_quoted('PACKAGE_VERSION', meson.project_version()) conf.set_quoted('PACKAGE_VERSION', meson.project_version())
conf.set('WITH_GCRYPT', get_option('gcrypt')) conf.set('WITH_GCRYPT', get_option('gcrypt'))
conf.set('WITH_TPM', get_option('tpm2'))
if get_option('gcrypt') if get_option('gcrypt')
conf.set_quoted('LIBGCRYPT_VERSION', min_libgcrypt_version) conf.set_quoted('LIBGCRYPT_VERSION', min_libgcrypt_version)
endif endif
if get_option('tpm2')
conf.set_quoted('TSS2_VERSION', min_tss2_version)
endif
conf.set('WITH_DEBUG', get_option('debugging')) conf.set('WITH_DEBUG', get_option('debugging'))
conf.set('_DEBUG', get_option('debugging')) conf.set('_DEBUG', get_option('debugging'))
conf.set('HAVE_MLOCK', meson.get_compiler('c').has_function('mlock')) conf.set('HAVE_MLOCK', meson.get_compiler('c').has_function('mlock'))

View File

@ -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('introspection', type: 'boolean', value: true, description: 'Create GIR file.')
option('bashcompdir', type: 'string', value: '', description: 'Override default location for bash completion files') 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('bash_completion', type: 'feature', value: 'auto', description: 'Install bash completion files')
option('tpm2', type: 'boolean', value: false, description: 'With TPM2 Software Stack')