From 8b214b9d1c51f49d977e93b66378ed4f73790c8b Mon Sep 17 00:00:00 2001 From: Brett Grandbois Date: Tue, 15 May 2018 10:55:49 +1000 Subject: [PATCH] lib/security: add in openssl support Refactor to export a generic API rather than specific gpg_ prefixes by changing gpg.h to security.h and renaming some of the exports. Break out the common and specific functionality into common.c and none.c/gpg.c/openssl.c for no/gpgme/openssl modes respectively. gpgme should work as before OpenSSL support works like this: The pb-lockdown file is a PKCS12 file or X509 certificate or PEM-encoded raw public key. To follow the current conventions the presence of a PKCS12 file as a lockdown signals decrypt mode because of the presence of the private key, anything else signals signature verification mode. The keyring path is currently ignored but in the future could be used to point to an X509 certificate chain for validity checking. Because of this self-signed certificates are currently supported and really just used as a public key container. Signature verification mode supports: * Cryptographic Message Syntax (CMS) as detached S/MIME, this is really more for consistency for the encryption mode (see below). This mode requires the lockdown file to be an X509 certificate. A sample creation command would be: openssl cms -sign -in (infile) -out (outfile) -binary -nocerts \ -inkey (private key) -signer (recipient certificate) * Raw signature digest as output from openssl dgst -sign command. This mode can have the lockdown file be an X509 certificate or a PEM raw public key but the digest algorithm must be pre-defined by the VERIFY_DIGEST configure argument. The default is SHA256. A sample creation command would be: openssl dgst -sign (private key) -out (outfile) -(digest mode) \ (infile) Decryption mode supports: * CMS signed-envelope as attached S/MIME. This is for consistency with the current expectation of no external file for decryption. Some future enhancement could be to come up with some proprietary external file format containing the cipher used, the encrypted cipher key, and the IV (if necessary). A sample creation command would be: openssl cms -sign -in (infile) -signer (recipient certificate) \ -binary -nocerts -nodetach -inkey (private key) | \ openssl cms -encrypt -(cipher mode) -out (outfile) \ (recipient certificate) The PKCS12 file is expecting the private key to have password of NULL or "" as there is currently no mechanism to supply a custom one. Signed-off-by: Brett Grandbois Signed-off-by: Samuel Mendoza-Jonas --- lib/Makefile.am | 42 ++-- lib/security/common.c | 230 +++++++++++++++++++ lib/security/gpg.c | 202 +---------------- lib/security/gpg.h | 83 ------- lib/security/none.c | 61 +++++ lib/security/openssl.c | 476 ++++++++++++++++++++++++++++++++++++++++ lib/security/security.h | 46 ++++ 7 files changed, 849 insertions(+), 291 deletions(-) create mode 100644 lib/security/common.c delete mode 100644 lib/security/gpg.h create mode 100644 lib/security/none.c create mode 100644 lib/security/openssl.c create mode 100644 lib/security/security.h diff --git a/lib/Makefile.am b/lib/Makefile.am index 8f68202..0088e0b 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -18,14 +18,20 @@ noinst_LTLIBRARIES += $(core_lib) lib_libpbcore_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ - -DPREFIX='"$(prefix)"' + -DPREFIX='"$(prefix)"' \ + $(OPENSSL_INCLUDES) -if WITH_GPGME -gpg_int_SOURCES = lib/security/gpg.h \ - lib/security/gpg.c -else -gpg_int_SOURCES = -endif +lib_libpbcore_la_LIBADD = \ + $(GPGME_LIBS) \ + $(OPENSSL_LIBS) + +lib_libpbcore_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(OPENSSL_LDFLAGS) + +lib_libpbcore_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(GPGME_CFLAGS) lib_libpbcore_la_SOURCES = \ lib/ccan/endian/endian.h \ @@ -59,19 +65,27 @@ lib_libpbcore_la_SOURCES = \ lib/util/util.h \ lib/flash/config.h \ lib/flash/flash.h \ - $(gpg_int_SOURCES) + lib/security/security.h if ENABLE_MTD lib_libpbcore_la_SOURCES += \ lib/flash/flash.c -lib_libpbcore_la_CPPFLAGS += \ - $(AM_CPPFLAGS) - -lib_libpbcore_la_LDFLAGS = \ - $(AM_LDFLAGS) \ +lib_libpbcore_la_LDFLAGS += \ $(LIBFLASH_LIBS) +endif +if WITH_GPGME lib_libpbcore_la_SOURCES += \ - lib/flash/flash.c + lib/security/common.c \ + lib/security/gpg.c +else +if WITH_OPENSSL +lib_libpbcore_la_SOURCES += \ + lib/security/common.c \ + lib/security/openssl.c +else +lib_libpbcore_la_SOURCES += \ + lib/security/none.c +endif endif diff --git a/lib/security/common.c b/lib/security/common.c new file mode 100644 index 0000000..df04054 --- /dev/null +++ b/lib/security/common.c @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2016 Raptor Engineering, LLC + * Copyright (C) 2018 Opengear, Inc + * + * 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 + +#include "security.h" + +struct pb_url * 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 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->decrypt_files) ? + boot_task->local_cmdline_signature : NULL; + + if ((!boot_task->verify_signature) && (!boot_task->decrypt_files)) + 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); + } + + if (boot_task->verify_signature) { + /* Check signatures */ + if (verify_file_signature(kernel_filename, + local_image_signature, + authorized_signatures_handle, + KEYRING_PATH)) + result = KEXEC_LOAD_SIGNATURE_FAILURE; + if (verify_file_signature(cmdline_template, + local_cmdline_signature, + authorized_signatures_handle, + KEYRING_PATH)) + result = KEXEC_LOAD_SIGNATURE_FAILURE; + + if (boot_task->local_initrd_signature) + if (verify_file_signature(initrd_filename, + local_initrd_signature, + authorized_signatures_handle, + KEYRING_PATH)) + result = KEXEC_LOAD_SIGNATURE_FAILURE; + if (boot_task->local_dtb_signature) + if (verify_file_signature(dtb_filename, + local_dtb_signature, + authorized_signatures_handle, + KEYRING_PATH)) + result = KEXEC_LOAD_SIGNATURE_FAILURE; + + /* Clean up */ + if (cmdline_handle) { + fclose(cmdline_handle); + unlink(cmdline_template); + } + fclose(authorized_signatures_handle); + } else if (boot_task->decrypt_files) { + /* Decrypt files */ + if (decrypt_file(kernel_filename, + authorized_signatures_handle, + KEYRING_PATH)) + result = KEXEC_LOAD_DECRYPTION_FALURE; + if (verify_file_signature(cmdline_template, + local_cmdline_signature, + authorized_signatures_handle, + KEYRING_PATH)) + result = KEXEC_LOAD_SIGNATURE_FAILURE; + if (boot_task->local_initrd) + if (decrypt_file(initrd_filename, + authorized_signatures_handle, + KEYRING_PATH)) + result = KEXEC_LOAD_DECRYPTION_FALURE; + if (boot_task->local_dtb) + if (decrypt_file(dtb_filename, + authorized_signatures_handle, + KEYRING_PATH)) + result = KEXEC_LOAD_DECRYPTION_FALURE; + + /* Clean up */ + if (cmdline_handle) { + fclose(cmdline_handle); + unlink(cmdline_template); + } + fclose(authorized_signatures_handle); + } + + return result; +} + +void validate_boot_files_cleanup(struct boot_task *boot_task) { + if ((boot_task->verify_signature) || (boot_task->decrypt_files)) { + 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); + } +} + diff --git a/lib/security/gpg.c b/lib/security/gpg.c index 76e2c6c..761d6ce 100644 --- a/lib/security/gpg.c +++ b/lib/security/gpg.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -34,7 +35,9 @@ #include #include -#include "gpg.h" +#include + +#include "security.h" /* * If --with-signed-boot is enabled lib/security provides the ability to handle @@ -45,21 +48,6 @@ * 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 decrypt_file(const char *filename, FILE *authorized_signatures_handle, const char *keyring_path) { @@ -362,181 +350,6 @@ int verify_file_signature(const char *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->decrypt_files) ? - boot_task->local_cmdline_signature : NULL; - - if ((!boot_task->verify_signature) && (!boot_task->decrypt_files)) - 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); - } - - if (boot_task->verify_signature) { - /* 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); - } else if (boot_task->decrypt_files) { - /* Decrypt files */ - if (decrypt_file(kernel_filename, - authorized_signatures_handle, - "/etc/gpg")) - result = KEXEC_LOAD_DECRYPTION_FALURE; - if (verify_file_signature(cmdline_template, - local_cmdline_signature, - authorized_signatures_handle, - "/etc/gpg")) - result = KEXEC_LOAD_SIGNATURE_FAILURE; - if (boot_task->local_initrd) - if (decrypt_file(initrd_filename, - authorized_signatures_handle, - "/etc/gpg")) - result = KEXEC_LOAD_DECRYPTION_FALURE; - if (boot_task->local_dtb) - if (decrypt_file(dtb_filename, - authorized_signatures_handle, - "/etc/gpg")) - result = KEXEC_LOAD_DECRYPTION_FALURE; - - /* 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) || (boot_task->decrypt_files)) { - 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() { /* assume most restrictive lockdown type */ int ret = PB_LOCKDOWN_SIGN; @@ -559,8 +372,8 @@ int lockdown_status() { 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-1] == '\r')) + auth_sig_len--; auth_sig_line[auth_sig_len] = 0; if (strcmp(auth_sig_line, "ENCRYPTED") == 0) { /* first line indicates encrypted files @@ -571,5 +384,6 @@ int lockdown_status() { } free(auth_sig_line); - return ret; + return ret; } + diff --git a/lib/security/gpg.h b/lib/security/gpg.h deleted file mode 100644 index 6efc3d2..0000000 --- a/lib/security/gpg.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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, - PB_LOCKDOWN_DECRYPT = 2, -}; - -#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 decrypt_file(const char * 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 decrypt_file(const char * 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/security/none.c b/lib/security/none.c new file mode 100644 index 0000000..8ef54e1 --- /dev/null +++ b/lib/security/none.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 Raptor Engineering, LLC + * Copyright (C) 2018 Opengear, Inc + * + * 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 "security.h" + +int lockdown_status(void) { return PB_LOCKDOWN_NONE; } + +struct pb_url * 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 decrypt_file(const char * filename __attribute__((unused)), + FILE * authorized_signatures_handle __attribute__((unused)), + const char * keyring_path __attribute__((unused))) +{ + return -1; +} + +int validate_boot_files(struct boot_task *boot_task __attribute__((unused))) +{ + return 0; +} + +void validate_boot_files_cleanup(struct boot_task *boot_task __attribute__((unused))) +{} + diff --git a/lib/security/openssl.c b/lib/security/openssl.c new file mode 100644 index 0000000..03ea332 --- /dev/null +++ b/lib/security/openssl.c @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2018 Opengear + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "security.h" + +static const EVP_MD *s_verify_md = NULL; + +static __attribute__((constructor)) void crypto_init(void) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + OPENSSL_no_config(); + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + ERR_load_CMS_strings(); +#endif + + s_verify_md = EVP_get_digestbyname(VERIFY_DIGEST); + if (!s_verify_md) + pb_log("Specified OpenSSL digest '%s' not found\n", VERIFY_DIGEST); + +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +static __attribute__((destructor)) void crypto_fini(void) +{ + EVP_cleanup(); + ERR_free_strings(); +} +#endif + +static int pb_log_print_errors_cb(const char *str, + size_t len __attribute__((unused)), + void *u __attribute__((unused))) +{ + pb_log(" %s\n", str); + return 0; +} + +static int get_pkcs12(FILE *keyfile, X509 **cert, EVP_PKEY **priv) +{ + PKCS12 *p12 = NULL; + int ok = 0; + + rewind(keyfile); + + p12 = d2i_PKCS12_fp(keyfile, NULL); + if (p12) { + /* + * annoying but NULL and "" are two valid but different + * default passwords + */ + if (!PKCS12_parse(p12, NULL, priv, cert, NULL) && + !PKCS12_parse(p12, "", priv, cert, NULL)) { + pb_log("%s: Error parsing OpenSSL PKCS12:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + } else + ok = 1; + + PKCS12_free(p12); + } + + return ok; +} + +static X509 *get_cert(FILE *keyfile) +{ + EVP_PKEY *priv = NULL; + X509 *cert = NULL; + + if (get_pkcs12(keyfile, &cert, &priv)) { + EVP_PKEY_free(priv); + } else { + rewind(keyfile); + ERR_clear_error(); + cert = PEM_read_X509(keyfile, NULL, NULL, NULL); + } + + return cert; +} + +static STACK_OF(X509) *get_cert_stack(FILE *keyfile) +{ + STACK_OF(X509) *certs = sk_X509_new_null(); + X509 *cert = NULL; + + if (certs) { + cert = get_cert(keyfile); + if (cert) + sk_X509_push(certs, get_cert(keyfile)); + } else { + pb_log("%s: Error allocating OpenSSL X509 stack:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + } + + return certs; +} + + +static EVP_PKEY *get_public_key(FILE *keyfile) +{ + EVP_PKEY *pkey = NULL; + X509 *cert = NULL; + + /* + * walk through supported file types looking for a public key: + * + * 1. PKCS12 + * 2. PEM encoded X509 + * 3. PEM encoded raw public key + * + * someday in the future maybe utilize the keyring_path + * as an input for X509_STORE_load_locations for certificate + * validity checking + */ + + cert = get_cert(keyfile); + if (cert) { + pkey = X509_get_pubkey(cert); + X509_free(cert); + } else { + rewind(keyfile); + ERR_clear_error(); + pkey = PEM_read_PUBKEY(keyfile, NULL, NULL, NULL); + } + + /* handles both cases */ + if (!pkey) { + pb_log("%s: Error loading OpenSSL public key:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + } + + return pkey; +} + +int decrypt_file(const char *filename, + FILE *authorized_signatures_handle, + const char *keyring_path __attribute__((unused))) +{ + BIO *content_bio = NULL, *file_bio = NULL, *out_bio = NULL; + STACK_OF(X509) *certs = NULL; + CMS_ContentInfo *cms = NULL; + EVP_PKEY *priv = NULL; + X509 *cert = NULL; + int nok = -1; + char *outptr; + long outl; + int bytes; + + if (!get_pkcs12(authorized_signatures_handle, &cert, &priv)) { + pb_log("%s: Error opening OpenSSL decrypt authorization file:\n", + __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + file_bio = BIO_new_file(filename, "r"); + if (!file_bio) { + pb_log("%s: Error opening OpenSSL decrypt cipher file '%s':\n", + __func__, filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + out_bio = BIO_new(BIO_s_mem()); + if (!out_bio) { + pb_log("%s: Error allocating OpenSSL decrypt output buffer:\n", + __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + /* right now only support signed-envelope CMS */ + + cms = SMIME_read_CMS(file_bio, &content_bio); + if (!cms) { + pb_log("%s: Error parsing OpenSSL CMS decrypt '%s'\n", + __func__, filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + BIO_free(content_bio); + content_bio = BIO_new(BIO_s_mem()); + if (!content_bio) { + pb_log("%s: Error allocating OpenSSL decrypt content buffer:\n", + __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + if (!CMS_decrypt(cms, priv, cert, NULL, out_bio, 0)) { + pb_log("%s: Error in OpenSSL CMS decrypt '%s'\n", + __func__, filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + certs = sk_X509_new_null(); + if (!certs) { + pb_log("%s: Error allocating OpenSSL X509 stack:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + sk_X509_push(certs, cert); + + CMS_ContentInfo_free(cms); + + cms = SMIME_read_CMS(out_bio, &content_bio); + if (!cms) { + pb_log("%s: Error parsing OpenSSL CMS decrypt verify:\n", + __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + /* this is a mem BIO so failure is 0 or -1 */ + if (BIO_reset(out_bio) < 1) { + pb_log("%s: Error resetting OpenSSL decrypt output buffer:\n", + __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + /* in this mode its attached content */ + if (!CMS_verify(cms, certs, NULL, content_bio, out_bio, + CMS_NO_SIGNER_CERT_VERIFY | CMS_BINARY)) { + pb_log("%s: Failed OpenSSL CMS decrypt verify:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + /* reopen the file so we force a truncation */ + BIO_free(file_bio); + file_bio = BIO_new_file(filename, "w"); + if (!file_bio) { + pb_log("%s: Error opening OpenSSL decrypt output file '%s'\n", + __func__, filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + outl = BIO_get_mem_data(out_bio, &outptr); + + while (outl) { + bytes = BIO_write(file_bio, outptr, outl); + if (bytes > 0) { + outl -= (long)bytes; + outptr += bytes; + + } else if (bytes < 0) { + pb_log("%s: OpenSSL decrypt output write failure on file '%s':\n", + __func__, filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + } + + if (!outl) + nok = 0; + +out: + if (cms) + CMS_ContentInfo_free(cms); + BIO_free(file_bio); + BIO_free(content_bio); + BIO_free(out_bio); + X509_free(cert); + sk_X509_free(certs); + EVP_PKEY_free(priv); + return nok; +} + +int verify_file_signature(const char *plaintext_filename, + const char *signature_filename, + FILE *authorized_signatures_handle, + const char *keyring_path __attribute__((unused))) +{ + BIO *signature_bio = NULL, *plaintext_bio = NULL, *content_bio = NULL; + STACK_OF(X509) *certs = NULL; + CMS_ContentInfo *cms = NULL; + ssize_t bytes_read = -1; + EVP_MD_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + char *sigbuf = NULL; + char rdbuf[8192]; + int nok = -1; + int siglen; + + plaintext_bio = BIO_new_file(plaintext_filename, "r"); + if (!plaintext_bio) { + pb_log("%s: Error opening OpenSSL verify plaintext file '%s'\n", + __func__, plaintext_filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + signature_bio = BIO_new_file(signature_filename, "r"); + if (!signature_bio) { + pb_log("%s: Error opening OpenSSL verify signature file '%s'\n", + __func__, signature_filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + /* first check CMS */ + cms = SMIME_read_CMS(signature_bio, &content_bio); + if (cms) { + certs = get_cert_stack(authorized_signatures_handle); + + /* + * this has to always be detached, which means we always + * ignore content_bio and we have to set the NO_SIGNER_CERT_VERIFY + * until such time we implement the keyring_path as a X509_STORE + */ + + if (!CMS_verify(cms, certs, NULL, plaintext_bio, NULL, + CMS_DETACHED | CMS_NO_SIGNER_CERT_VERIFY | CMS_BINARY)) { + pb_log("%s: Failed OpenSSL CMS verify:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + nok = 0; + + } else { + + /* for explicit dgst mode we need an explicit md defined */ + if (!s_verify_md) + goto out; + + ctx = EVP_MD_CTX_create(); + + if (!ctx) { + pb_log("%s: Error allocating OpenSSL MD ctx:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + pkey = get_public_key(authorized_signatures_handle); + if (!pkey) + goto out; + + if (EVP_DigestVerifyInit(ctx, NULL, s_verify_md, NULL, pkey) < 1) { + pb_log("%s: Error initializing OpenSSL verify:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + while (bytes_read) { + bytes_read = BIO_read(plaintext_bio, rdbuf, 8192); + if (bytes_read > 0) { + if (EVP_DigestVerifyUpdate(ctx, rdbuf, (size_t)(bytes_read)) < 1) { + pb_log("%s: OpenSSL digest update failure on file '%s':\n", + __func__, plaintext_filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + } else if (bytes_read < 0) { + pb_log("%s: OpenSSL read failure on file '%s':\n", + __func__, plaintext_filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + } + + /* + * can't do signature buffer as an update so have to read in whole file + * would be handy if there was some sort of BIO_read_all but there + * doesn't seem to be so rather than reinvent the wheel close it and + * use the existing support + */ + BIO_free(signature_bio); + signature_bio = NULL; + + if (read_file(NULL, signature_filename, &sigbuf, &siglen)) { + pb_log("%s: Error reading OpenSSL signature file '%s'\n", + __func__, signature_filename); + goto out; + } + + if (EVP_DigestVerifyFinal(ctx, (unsigned char*)sigbuf, siglen)) + nok = 0; + else { + pb_log("%s: Error finalizing OpenSSL verify:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + } + } + +out: + if (cms) + CMS_ContentInfo_free(cms); + talloc_free(sigbuf); + sk_X509_free(certs); + BIO_free(plaintext_bio); + BIO_free(signature_bio); + BIO_free(content_bio); + EVP_PKEY_free(pkey); + EVP_MD_CTX_destroy(ctx); + return nok; +} + +int lockdown_status(void) +{ + /* + * if it's a PKCS12 then we're in decrypt mode since we have the + * private key, otherwise it's sign mode + * + * someday add in support for runtime determination based on what + * files come back in the async sig file load? + */ + FILE *authorized_signatures_handle = NULL; + int ret = PB_LOCKDOWN_SIGN; + PKCS12 *p12 = NULL; + + if (access(LOCKDOWN_FILE, F_OK) == -1) + return PB_LOCKDOWN_NONE; + + /* determine lockdown type */ + + authorized_signatures_handle = fopen(LOCKDOWN_FILE, "r"); + if (authorized_signatures_handle) { + p12 = d2i_PKCS12_fp(authorized_signatures_handle, NULL); + if (p12) { + ret = PB_LOCKDOWN_DECRYPT; + PKCS12_free(p12); + } + fclose(authorized_signatures_handle); + } + + return ret; +} + diff --git a/lib/security/security.h b/lib/security/security.h new file mode 100644 index 0000000..9b1ae57 --- /dev/null +++ b/lib/security/security.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 Raptor Engineering, LLC + * Copyright (C) 2018 Opengear, Inc + * + * 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_SECURITY_H +#define _PB_SECURITY_H + +#include + +enum { + PB_LOCKDOWN_NONE = 0, + PB_LOCKDOWN_SIGN = 1, + PB_LOCKDOWN_DECRYPT = 2 +}; + + +int lockdown_status(void); + +struct pb_url * 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 decrypt_file(const char *filename, + FILE * authorized_signatures_handle, const char * keyring_path); + +int validate_boot_files(struct boot_task *boot_task); + +void validate_boot_files_cleanup(struct boot_task *boot_task); + +#endif // _PB_SECURITY_H + -- 2.39.2