From 86c9d34380b0074dab1ba89a569a94280d6999c4 Mon Sep 17 00:00:00 2001 From: "tpearson@raptorengineering.com" Date: Thu, 18 Aug 2016 04:45:47 -0500 Subject: Add support for GPG signature enforcement on booted kernels and related blobs This can be used to implement a form of organization-controlled secure boot, whereby kernels may be loaded from a variety of sources but they will only boot if a valid signature file is found for each component, and only if the signature is listed in the /etc/pb-lockdown file. Signed-off-by: Timothy Pearson Signed-off-by: Samuel Mendoza-Jonas (Minor build fixes and gpgme.m4, comment on secure boot in gpg.c) --- lib/Makefile.am | 10 +- lib/file/file.c | 77 ++++++++++ lib/file/file.h | 3 + lib/pb-protocol/pb-protocol.c | 13 ++ lib/security/gpg.c | 337 ++++++++++++++++++++++++++++++++++++++++++ lib/security/gpg.h | 72 +++++++++ lib/types/types.h | 2 + 7 files changed, 513 insertions(+), 1 deletion(-) create mode 100644 lib/security/gpg.c create mode 100644 lib/security/gpg.h (limited to 'lib') diff --git a/lib/Makefile.am b/lib/Makefile.am index 09bc1aa..bb7dfe4 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -20,6 +20,13 @@ lib_libpbcore_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ -DPREFIX='"$(prefix)"' +if WITH_GPGME +gpg_int_SOURCES = lib/security/gpg..h \ + lib/security/gpg.c +else +gpg_int_SOURCES = +endif + lib_libpbcore_la_SOURCES = \ lib/file/file.h \ lib/file/file.c \ @@ -50,7 +57,8 @@ lib_libpbcore_la_SOURCES = \ lib/util/util.c \ lib/util/util.h \ lib/flash/config.h \ - lib/flash/flash.h + lib/flash/flash.h \ + $(gpg_int_SOURCES) if ENABLE_MTD lib_libpbcore_la_SOURCES += \ diff --git a/lib/file/file.c b/lib/file/file.c index 1bde9fb..6a270a3 100644 --- a/lib/file/file.c +++ b/lib/file/file.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2013 Jeremy Kerr + * Copyright (C) 2016 Raptor Engineering, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,11 +24,87 @@ #include #include +#include #include "file.h" +#define MAX_FILENAME_SIZE 8192 +#define FILE_XFER_BUFFER_SIZE 8192 + static const int max_file_size = 1024 * 1024; +int copy_file_secure_dest(void *ctx, + const char *source_file, char **destination_file) { + int result = 0; + char template[] = "/tmp/petitbootXXXXXX"; + char dest_filename[MAX_FILENAME_SIZE] = ""; + FILE *source_handle = fopen(source_file, "r"); + int destination_fd = mkstemp(template); + FILE *destination_handle = fdopen(destination_fd, "w"); + if (!source_handle || !(destination_handle)) { + // handle open error + pb_log("%s: failed: unable to open source file '%s'\n", + __func__, source_file); + return -1; + } + + size_t l1; + unsigned char *buffer; + buffer = talloc_array(ctx, unsigned char, FILE_XFER_BUFFER_SIZE); + if (!buffer) { + pb_log("%s: failed: unable to allocate file transfer buffer\n", + __func__); + return -1; + } + + /* Copy data */ + while ((l1 = fread(buffer, 1, sizeof buffer, source_handle)) > 0) { + size_t l2 = fwrite(buffer, 1, l1, destination_handle); + if (l2 < l1) { + if (ferror(destination_handle)) { + /* General error */ + result = -1; + pb_log("%s: failed: unknown fault\n", __func__); + } + else { + /* No space on destination device */ + result = -1; + pb_log("%s: failed: temporary storage full\n", + __func__); + } + break; + } + } + + talloc_free(buffer); + + if (result) { + dest_filename[0] = '\0'; + } + else { + ssize_t r; + char readlink_buffer[MAX_FILENAME_SIZE]; + snprintf(readlink_buffer, MAX_FILENAME_SIZE, "/proc/self/fd/%d", + destination_fd); + r = readlink(readlink_buffer, dest_filename, + MAX_FILENAME_SIZE); + if (r < 0) { + /* readlink failed */ + result = -1; + pb_log("%s: failed: unable to obtain temporary filename" + "\n", __func__); + } + dest_filename[r] = '\0'; + } + + fclose(source_handle); + fclose(destination_handle); + + *destination_file = talloc_strdup(ctx, dest_filename); + + return result; +} + int read_file(void *ctx, const char *filename, char **bufp, int *lenp) { struct stat statbuf; diff --git a/lib/file/file.h b/lib/file/file.h index 8aa7d3c..a2744a0 100644 --- a/lib/file/file.h +++ b/lib/file/file.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2013 Jeremy Kerr + * Copyright (C) 2016 Raptor Engineering, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,6 +18,8 @@ #ifndef FILE_H #define FILE_H +int copy_file_secure_dest(void *ctx, + const char * source_file, char ** destination_file); int read_file(void *ctx, const char *filename, char **bufp, int *lenp); int replace_file(const char *filename, char *buf, int len); diff --git a/lib/pb-protocol/pb-protocol.c b/lib/pb-protocol/pb-protocol.c index 7887fb0..1560ef7 100644 --- a/lib/pb-protocol/pb-protocol.c +++ b/lib/pb-protocol/pb-protocol.c @@ -37,6 +37,7 @@ * 4-byte len, initrd_file * 4-byte len, dtb_file * 4-byte len, boot_args + * 4-byte len, args_sig_file * * action = 0x2: device remove message * payload: @@ -49,6 +50,7 @@ * 4-byte len, initrd_file * 4-byte len, dtb_file * 4-byte len, boot_args + * 4-byte len, args_sig_file * */ @@ -72,6 +74,7 @@ void pb_protocol_dump_device(const struct device *dev, const char *text, fprintf(stream, "%s\t\tinit: %s\n", text, opt->initrd_file); fprintf(stream, "%s\t\tdtb: %s\n", text, opt->dtb_file); fprintf(stream, "%s\t\targs: %s\n", text, opt->boot_args); + fprintf(stream, "%s\t\tasig: %s\n", text, opt->args_sig_file); } } @@ -197,6 +200,7 @@ int pb_protocol_boot_option_len(const struct boot_option *opt) 4 + optional_strlen(opt->initrd_file) + 4 + optional_strlen(opt->dtb_file) + 4 + optional_strlen(opt->boot_args) + + 4 + optional_strlen(opt->args_sig_file) + sizeof(opt->is_default); } @@ -207,6 +211,7 @@ int pb_protocol_boot_len(const struct boot_command *boot) 4 + optional_strlen(boot->initrd_file) + 4 + optional_strlen(boot->dtb_file) + 4 + optional_strlen(boot->boot_args) + + 4 + optional_strlen(boot->args_sig_file) + 4 + optional_strlen(boot->tty); } @@ -360,6 +365,7 @@ int pb_protocol_serialise_boot_option(const struct boot_option *opt, pos += pb_protocol_serialise_string(pos, opt->initrd_file); pos += pb_protocol_serialise_string(pos, opt->dtb_file); pos += pb_protocol_serialise_string(pos, opt->boot_args); + pos += pb_protocol_serialise_string(pos, opt->args_sig_file); *(bool *)pos = opt->is_default; pos += sizeof(bool); @@ -380,6 +386,7 @@ int pb_protocol_serialise_boot_command(const struct boot_command *boot, pos += pb_protocol_serialise_string(pos, boot->initrd_file); pos += pb_protocol_serialise_string(pos, boot->dtb_file); pos += pb_protocol_serialise_string(pos, boot->boot_args); + pos += pb_protocol_serialise_string(pos, boot->args_sig_file); pos += pb_protocol_serialise_string(pos, boot->tty); assert(pos <= buf + buf_len); @@ -750,6 +757,9 @@ int pb_protocol_deserialise_boot_option(struct boot_option *opt, if (read_string(opt, &pos, &len, &opt->boot_args)) goto out; + if (read_string(opt, &pos, &len, &opt->args_sig_file)) + goto out; + if (len < sizeof(bool)) goto out; opt->is_default = *(bool *)(pos); @@ -785,6 +795,9 @@ int pb_protocol_deserialise_boot_command(struct boot_command *cmd, if (read_string(cmd, &pos, &len, &cmd->boot_args)) goto out; + if (read_string(cmd, &pos, &len, &cmd->args_sig_file)) + goto out; + if (read_string(cmd, &pos, &len, &cmd->tty)) goto out; diff --git a/lib/security/gpg.c b/lib/security/gpg.c new file mode 100644 index 0000000..a377b55 --- /dev/null +++ b/lib/security/gpg.c @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2016 Raptor Engineering, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "gpg.h" + +/* + * If --with-signed-boot is enabled lib/security provides the ability to handle + * gpg-signed and/or encrypted boot sources (kernel, initrd, etc). + * This can be used to enable a form of secure boot, but it is important to + * recognise that it depends on the security of the entire system, for example + * a full trusted-boot implementation. Petitboot can not and will not be able + * to guarantee secure boot by itself. + */ + +struct pb_url * gpg_get_signature_url(void *ctx, struct pb_url *base_file) +{ + struct pb_url *signature_file = NULL; + + signature_file = pb_url_copy(ctx, base_file); + talloc_free(signature_file->file); + signature_file->file = talloc_asprintf(signature_file, + "%s.sig", base_file->file); + talloc_free(signature_file->path); + signature_file->path = talloc_asprintf(signature_file, + "%s.sig", base_file->path); + + return signature_file; +} + +int verify_file_signature(const char *plaintext_filename, + const char *signature_filename, FILE *authorized_signatures_handle, + const char *keyring_path) +{ + int valid = 0; + gpgme_signature_t verification_signatures; + gpgme_verify_result_t verification_result; + gpgme_data_t plaintext_data; + gpgme_data_t signature_data; + gpgme_engine_info_t enginfo; + gpgme_ctx_t gpg_context; + gpgme_error_t err; + + if (signature_filename == NULL) + return -1; + + /* Initialize gpgme */ + setlocale (LC_ALL, ""); + gpgme_check_version(NULL); + gpgme_set_locale(NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL)); + err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: OpenPGP support not available\n", __func__); + return -1; + } + err = gpgme_get_engine_info(&enginfo); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: GPG engine failed to initialize\n", __func__); + return -1; + } + err = gpgme_new(&gpg_context); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: GPG context could not be created\n", __func__); + return -1; + } + err = gpgme_set_protocol(gpg_context, GPGME_PROTOCOL_OpenPGP); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: GPG protocol could not be set\n", __func__); + return -1; + } + if (keyring_path) + err = gpgme_ctx_set_engine_info (gpg_context, + GPGME_PROTOCOL_OpenPGP, enginfo->file_name, + keyring_path); + else + err = gpgme_ctx_set_engine_info (gpg_context, + GPGME_PROTOCOL_OpenPGP, enginfo->file_name, + enginfo->home_dir); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: Could not set GPG engine information\n", __func__); + return -1; + } + err = gpgme_data_new_from_file(&plaintext_data, plaintext_filename, 1); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: Could not create GPG plaintext data buffer" + " from file '%s'\n", __func__, plaintext_filename); + return -1; + } + err = gpgme_data_new_from_file(&signature_data, signature_filename, 1); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: Could not create GPG signature data buffer" + " from file '%s'\n", __func__, signature_filename); + return -1; + } + + /* Check signature */ + err = gpgme_op_verify(gpg_context, signature_data, plaintext_data, + NULL); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: Could not verify file using GPG signature '%s'\n", + __func__, signature_filename); + return -1; + } + verification_result = gpgme_op_verify_result(gpg_context); + verification_signatures = verification_result->signatures; + while (verification_signatures) { + if (verification_signatures->status != GPG_ERR_NO_ERROR) { + /* Signature verification failure */ + pb_log("%s: Signature for key ID '%s' ('%s') invalid." + " Status: %08x\n", __func__, + verification_signatures->fpr, + signature_filename, + verification_signatures->status); + verification_signatures = verification_signatures->next; + continue; + } + + /* Signature check passed with no error */ + pb_log("%s: Good signature for key ID '%s' ('%s')\n", + __func__, verification_signatures->fpr, + signature_filename); + /* Verify fingerprint is present in + * authorized signatures file + */ + char *auth_sig_line = NULL; + size_t auth_sig_len = 0; + ssize_t auth_sig_read; + rewind(authorized_signatures_handle); + while ((auth_sig_read = getline(&auth_sig_line, + &auth_sig_len, + authorized_signatures_handle)) != -1) { + auth_sig_len = strlen(auth_sig_line); + while ((auth_sig_line[auth_sig_len-1] == '\n') + || (auth_sig_line[auth_sig_len-1] == '\r')) + auth_sig_len--; + auth_sig_line[auth_sig_len] = '\0'; + if (strcmp(auth_sig_line, + verification_signatures->fpr) == 0) + valid = 1; + } + free(auth_sig_line); + verification_signatures = verification_signatures->next; + } + + /* Clean up */ + gpgme_data_release(plaintext_data); + gpgme_data_release(signature_data); + gpgme_release(gpg_context); + + if (!valid) { + pb_log("%s: Incorrect GPG signature\n", __func__); + return -1; + } + + pb_log("%s: GPG signature '%s' for file '%s' verified\n", + __func__, signature_filename, plaintext_filename); + + return 0; +} + +int gpg_validate_boot_files(struct boot_task *boot_task) { + int result = 0; + char *kernel_filename = NULL; + char *initrd_filename = NULL; + char *dtb_filename = NULL; + + FILE *authorized_signatures_handle = NULL; + + char cmdline_template[] = "/tmp/petitbootXXXXXX"; + int cmdline_fd = mkstemp(cmdline_template); + FILE *cmdline_handle = NULL; + + const char* local_initrd_signature = (boot_task->verify_signature) ? + boot_task->local_initrd_signature : NULL; + const char* local_dtb_signature = (boot_task->verify_signature) ? + boot_task->local_dtb_signature : NULL; + const char* local_image_signature = (boot_task->verify_signature) ? + boot_task->local_image_signature : NULL; + const char* local_cmdline_signature = (boot_task->verify_signature) ? + boot_task->local_cmdline_signature : NULL; + + if (!boot_task->verify_signature) + return result; + + /* Load authorized signatures file */ + authorized_signatures_handle = fopen(LOCKDOWN_FILE, "r"); + if (!authorized_signatures_handle) { + pb_log("%s: unable to read lockdown file\n", __func__); + return KEXEC_LOAD_SIG_SETUP_INVALID; + } + + /* Copy files to temporary directory for verification / boot */ + result = copy_file_secure_dest(boot_task, + boot_task->local_image, + &kernel_filename); + if (result) { + pb_log("%s: image copy failed: (%d)\n", + __func__, result); + return result; + } + if (boot_task->local_initrd) { + result = copy_file_secure_dest(boot_task, + boot_task->local_initrd, + &initrd_filename); + if (result) { + pb_log("%s: initrd copy failed: (%d)\n", + __func__, result); + return result; + } + } + if (boot_task->local_dtb) { + result = copy_file_secure_dest(boot_task, + boot_task->local_dtb, + &dtb_filename); + if (result) { + pb_log("%s: dtb copy failed: (%d)\n", + __func__, result); + return result; + } + } + boot_task->local_image_override = talloc_strdup(boot_task, + kernel_filename); + if (boot_task->local_initrd) + boot_task->local_initrd_override = talloc_strdup(boot_task, + initrd_filename); + if (boot_task->local_dtb) + boot_task->local_dtb_override = talloc_strdup(boot_task, + dtb_filename); + + /* Write command line to temporary file for verification */ + if (cmdline_fd < 0) { + /* mkstemp failed */ + pb_log("%s: failed: unable to create command line" + " temporary file for verification\n", + __func__); + result = -1; + } + else { + cmdline_handle = fdopen(cmdline_fd, "w"); + } + if (!cmdline_handle) { + /* Failed to open file */ + pb_log("%s: failed: unable to write command line" + " temporary file for verification\n", + __func__); + result = -1; + } + else { + fwrite(boot_task->args, sizeof(char), + strlen(boot_task->args), cmdline_handle); + fflush(cmdline_handle); + } + + /* Check signatures */ + if (verify_file_signature(kernel_filename, + local_image_signature, + authorized_signatures_handle, "/etc/gpg")) + result = KEXEC_LOAD_SIGNATURE_FAILURE; + if (verify_file_signature(cmdline_template, + local_cmdline_signature, + authorized_signatures_handle, "/etc/gpg")) + result = KEXEC_LOAD_SIGNATURE_FAILURE; + if (boot_task->local_initrd_signature) + if (verify_file_signature(initrd_filename, + local_initrd_signature, + authorized_signatures_handle, "/etc/gpg")) + result = KEXEC_LOAD_SIGNATURE_FAILURE; + if (boot_task->local_dtb_signature) + if (verify_file_signature(dtb_filename, + local_dtb_signature, + authorized_signatures_handle, "/etc/gpg")) + result = KEXEC_LOAD_SIGNATURE_FAILURE; + + /* Clean up */ + if (cmdline_handle) { + fclose(cmdline_handle); + unlink(cmdline_template); + } + fclose(authorized_signatures_handle); + + return result; +} + +void gpg_validate_boot_files_cleanup(struct boot_task *boot_task) { + if (boot_task->verify_signature) { + unlink(boot_task->local_image_override); + if (boot_task->local_initrd_override) + unlink(boot_task->local_initrd_override); + if (boot_task->local_dtb_override) + unlink(boot_task->local_dtb_override); + + talloc_free(boot_task->local_image_override); + if (boot_task->local_initrd_override) + talloc_free(boot_task->local_initrd_override); + if (boot_task->local_dtb_override) + talloc_free(boot_task->local_dtb_override); + } +} + +int lockdown_status() { + if (access(LOCKDOWN_FILE, F_OK) == -1) + return PB_LOCKDOWN_NONE; + else + return PB_LOCKDOWN_SIGN; +} \ No newline at end of file diff --git a/lib/security/gpg.h b/lib/security/gpg.h new file mode 100644 index 0000000..fb418bb --- /dev/null +++ b/lib/security/gpg.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016 Raptor Engineering, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PB_GPG_H +#define _PB_GPG_H + +#include + +enum { + PB_LOCKDOWN_NONE = 0, + PB_LOCKDOWN_SIGN = 1, +}; + +#if defined(HAVE_LIBGPGME) +#include +#endif /* HAVE_LIBGPGME */ + +int lockdown_status(void); + +struct pb_url * gpg_get_signature_url(void *ctx, struct pb_url *base_file); + +int verify_file_signature(const char *plaintext_filename, + const char *signature_filename, FILE *authorized_signatures_handle, + const char *keyring_path); + +int gpg_validate_boot_files(struct boot_task *boot_task); + +void gpg_validate_boot_files_cleanup(struct boot_task *boot_task); + +#if !defined(HAVE_LIBGPGME) + +int lockdown_status(void) { return PB_LOCKDOWN_NONE; } + +struct pb_url * gpg_get_signature_url(void *ctx __attribute__((unused)), + struct pb_url *base_file __attribute__((unused))) +{ + return NULL; +} + +int verify_file_signature(const char *plaintext_filename __attribute__((unused)), + const char *signature_filename __attribute__((unused)), + FILE *authorized_signatures_handle __attribute__((unused)), + const char *keyring_path __attribute__((unused))) +{ + return -1; +} + +int gpg_validate_boot_files(struct boot_task *boot_task __attribute__((unused))) +{ + return 0; +} + +void gpg_validate_boot_files_cleanup(struct boot_task *boot_task __attribute__((unused))) +{} + +#endif /* HAVE_LIBGPGME */ + +#endif /* _PB_GPG_H */ \ No newline at end of file diff --git a/lib/types/types.h b/lib/types/types.h index 5c5f6ed..6b607cd 100644 --- a/lib/types/types.h +++ b/lib/types/types.h @@ -52,6 +52,7 @@ struct boot_option { char *initrd_file; char *dtb_file; char *boot_args; + char *args_sig_file; bool is_default; struct list_item list; @@ -65,6 +66,7 @@ struct boot_command { char *initrd_file; char *dtb_file; char *boot_args; + char *args_sig_file; char *tty; }; -- cgit v1.2.1