diff options
-rw-r--r-- | discover/boot.c | 42 | ||||
-rw-r--r-- | discover/boot.h | 2 | ||||
-rw-r--r-- | lib/security/gpg.c | 292 | ||||
-rw-r--r-- | lib/security/gpg.h | 11 |
4 files changed, 314 insertions, 33 deletions
diff --git a/discover/boot.c b/discover/boot.c index c4ddfef..c25627d 100644 --- a/discover/boot.c +++ b/discover/boot.c @@ -51,9 +51,14 @@ static int kexec_load(struct boot_task *boot_task) boot_task->local_image_override = NULL; if ((result = gpg_validate_boot_files(boot_task))) { - if (result == KEXEC_LOAD_SIGNATURE_FAILURE) { + if (result == KEXEC_LOAD_DECRYPTION_FALURE) { pb_log("%s: Aborting kexec due to" - " signature verification failure\n", __func__); + " decryption failure\n", __func__); + goto abort_kexec; + } + if (result == KEXEC_LOAD_SIGNATURE_FAILURE) { + pb_log("%s: Aborting kexec due to signature" + " verification failure\n", __func__); goto abort_kexec; } } @@ -391,7 +396,13 @@ static void boot_process(struct load_url_result *result, void *data) load_pending(task->dtb_signature) || load_pending(task->cmdline_signature)) return; + } + if (task->decrypt_files) { + if (load_pending(task->cmdline_signature)) + return; + } + if (task->verify_signature) { if (check_load(task, "kernel image signature", task->image_signature) || check_load(task, "initrd signature", @@ -402,6 +413,14 @@ static void boot_process(struct load_url_result *result, void *data) task->cmdline_signature)) goto no_sig_load; } + if (task->decrypt_files) { + if (load_pending(task->cmdline_signature)) + return; + + if (check_load(task, "command line signature", + task->cmdline_signature)) + goto no_decrypt_sig_load; + } /* we make a copy of the local paths, as the boot hooks might update * and/or create these */ @@ -416,6 +435,8 @@ static void boot_process(struct load_url_result *result, void *data) task->initrd_signature->local : NULL; task->local_dtb_signature = task->dtb_signature ? task->dtb_signature->local : NULL; + } + if (task->verify_signature || task->decrypt_files) { task->local_cmdline_signature = task->cmdline_signature ? task->cmdline_signature->local : NULL; } @@ -426,7 +447,11 @@ static void boot_process(struct load_url_result *result, void *data) _("performing kexec_load")); rc = kexec_load(task); - if (rc == KEXEC_LOAD_SIGNATURE_FAILURE) { + if (rc == KEXEC_LOAD_DECRYPTION_FALURE) { + update_status(task->status_fn, task->status_arg, + BOOT_STATUS_ERROR, _("decryption failed")); + } + else if (rc == KEXEC_LOAD_SIGNATURE_FAILURE) { update_status(task->status_fn, task->status_arg, BOOT_STATUS_ERROR, _("signature verification failed")); @@ -446,6 +471,8 @@ no_sig_load: cleanup_load(task->image_signature); cleanup_load(task->initrd_signature); cleanup_load(task->dtb_signature); + +no_decrypt_sig_load: cleanup_load(task->cmdline_signature); no_load: @@ -494,6 +521,7 @@ struct boot_task *boot(void *ctx, struct discover_boot_option *opt, struct boot_task *boot_task; const char *boot_desc; int rc; + int lockdown_type; if (opt && opt->option->name) boot_desc = opt->option->name; @@ -533,7 +561,9 @@ struct boot_task *boot(void *ctx, struct discover_boot_option *opt, boot_task->status_fn = status_fn; boot_task->status_arg = status_arg; - boot_task->verify_signature = (lockdown_status() == PB_LOCKDOWN_SIGN); + lockdown_type = lockdown_status(); + boot_task->verify_signature = (lockdown_type == PB_LOCKDOWN_SIGN); + boot_task->decrypt_files = (lockdown_type == PB_LOCKDOWN_DECRYPT); if (cmd && cmd->boot_args) { boot_task->args = talloc_strdup(boot_task, cmd->boot_args); @@ -551,7 +581,7 @@ struct boot_task *boot(void *ctx, struct discover_boot_option *opt, boot_task->boot_tty = config ? config->boot_tty : NULL; } - if (boot_task->verify_signature) { + if (boot_task->verify_signature || boot_task->decrypt_files) { if (cmd && cmd->args_sig_file) { cmdline_sig = pb_url_parse(opt, cmd->args_sig_file); } else if (opt && opt->args_sig_file) { @@ -590,7 +620,9 @@ struct boot_task *boot(void *ctx, struct discover_boot_option *opt, rc |= start_url_load(boot_task, "dtb signature", dtb_sig, &boot_task->dtb_signature); } + } + if (boot_task->verify_signature || boot_task->decrypt_files) { rc |= start_url_load(boot_task, "kernel command line signature", cmdline_sig, &boot_task->cmdline_signature); diff --git a/discover/boot.h b/discover/boot.h index 2190495..2d99b7f 100644 --- a/discover/boot.h +++ b/discover/boot.h @@ -32,6 +32,7 @@ struct boot_task { bool dry_run; bool cancelled; bool verify_signature; + bool decrypt_files; struct load_url_result *image_signature; struct load_url_result *initrd_signature; struct load_url_result *dtb_signature; @@ -43,6 +44,7 @@ struct boot_task { }; enum { + KEXEC_LOAD_DECRYPTION_FALURE = 252, KEXEC_LOAD_SIG_SETUP_INVALID = 253, KEXEC_LOAD_SIGNATURE_FAILURE = 254, }; diff --git a/lib/security/gpg.c b/lib/security/gpg.c index a377b55..41d1306 100644 --- a/lib/security/gpg.c +++ b/lib/security/gpg.c @@ -60,6 +60,178 @@ struct pb_url * gpg_get_signature_url(void *ctx, struct pb_url *base_file) return signature_file; } +int decrypt_file(const char *filename, + FILE *authorized_signatures_handle, const char *keyring_path) +{ + int result = 0; + int valid = 0; + size_t bytes_read = 0; + unsigned char buffer[8192]; + + if (filename == NULL) + return -1; + + gpgme_signature_t verification_signatures; + gpgme_verify_result_t verification_result; + gpgme_data_t ciphertext_data; + gpgme_data_t plaintext_data; + gpgme_engine_info_t enginfo; + gpgme_ctx_t gpg_context; + gpgme_error_t err; + + /* 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(&plaintext_data); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: Could not create GPG plaintext data buffer\n", + __func__); + return -1; + } + err = gpgme_data_new_from_file(&ciphertext_data, filename, 1); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: Could not create GPG ciphertext data buffer" + " from file '%s'\n", __func__, filename); + return -1; + } + + /* Decrypt and verify file */ + err = gpgme_op_decrypt_verify(gpg_context, ciphertext_data, + plaintext_data); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: Could not decrypt file\n", __func__); + 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) { + pb_log("%s: Good signature for key ID '%s' ('%s')\n", + __func__, + verification_signatures->fpr, 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); + } + else { + pb_log("%s: Signature for key ID '%s' ('%s') invalid." + " Status: %08x\n", __func__, + verification_signatures->fpr, filename, + verification_signatures->status); + } + verification_signatures = verification_signatures->next; + } + + gpgme_data_release(ciphertext_data); + + if (valid) { + /* Write decrypted file over ciphertext */ + FILE *plaintext_file_handle = NULL; + plaintext_file_handle = fopen(filename, "wb"); + if (!plaintext_file_handle) { + pb_log("%s: Could not create GPG plaintext file '%s'\n", + __func__, filename); + gpgme_data_release(plaintext_data); + gpgme_release(gpg_context); + return -1; + } + gpgme_data_seek(plaintext_data, 0, SEEK_SET); + if (err != GPG_ERR_NO_ERROR) { + pb_log("%s: Could not seek in GPG plaintext buffer\n", + __func__); + gpgme_data_release(plaintext_data); + gpgme_release(gpg_context); + fclose(plaintext_file_handle); + return -1; + } + while ((bytes_read = gpgme_data_read(plaintext_data, buffer, + 8192)) > 0) { + size_t l2 = fwrite(buffer, 1, bytes_read, + plaintext_file_handle); + if (l2 < bytes_read) { + if (ferror(plaintext_file_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; + } + } + fclose(plaintext_file_handle); + } + + /* Clean up */ + gpgme_data_release(plaintext_data); + gpgme_release(gpg_context); + + if (!valid) { + pb_log("%s: Incorrect GPG signature\n", __func__); + return -1; + } + + pb_log("%s: GPG signature for decrypted file '%s' verified\n", + __func__, filename); + + return result; +} + int verify_file_signature(const char *plaintext_filename, const char *signature_filename, FILE *authorized_signatures_handle, const char *keyring_path) @@ -208,10 +380,11 @@ int gpg_validate_boot_files(struct boot_task *boot_task) { 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) ? + const char* local_cmdline_signature = + (boot_task->verify_signature || boot_task->decrypt_files) ? boot_task->local_cmdline_signature : NULL; - if (!boot_task->verify_signature) + if ((!boot_task->verify_signature) && (!boot_task->decrypt_files)) return result; /* Load authorized signatures file */ @@ -283,38 +456,71 @@ int gpg_validate_boot_files(struct boot_task *boot_task) { 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")) + if (boot_task->verify_signature) { + /* Check signatures */ + if (verify_file_signature(kernel_filename, + local_image_signature, + authorized_signatures_handle, + "/etc/gpg")) + if (verify_file_signature(cmdline_template, + local_cmdline_signature, + authorized_signatures_handle, + "/etc/gpg")) + + 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); + /* Clean up */ + if (cmdline_handle) { + fclose(cmdline_handle); + unlink(cmdline_template); + } + fclose(authorized_signatures_handle); } - fclose(authorized_signatures_handle); return result; } void gpg_validate_boot_files_cleanup(struct boot_task *boot_task) { - if (boot_task->verify_signature) { + 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); @@ -330,8 +536,38 @@ void gpg_validate_boot_files_cleanup(struct boot_task *boot_task) { } int lockdown_status() { + /* assume most restrictive lockdown type */ + int ret = PB_LOCKDOWN_SIGN; + if (access(LOCKDOWN_FILE, F_OK) == -1) return PB_LOCKDOWN_NONE; - else - return PB_LOCKDOWN_SIGN; + + /* determine lockdown type */ + FILE *authorized_signatures_handle = NULL; + authorized_signatures_handle = fopen(LOCKDOWN_FILE, "r"); + if (!authorized_signatures_handle) + return ret; + + char *auth_sig_line = NULL; + size_t auth_sig_len = 0; + ssize_t auth_sig_read; + rewind(authorized_signatures_handle); + if ((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, "ENCRYPTED") == 0) { + /* first line indicates encrypted files + * expected. enable decryption. + */ + ret = PB_LOCKDOWN_DECRYPT; + } + } + free(auth_sig_line); + + return ret; }
\ No newline at end of file diff --git a/lib/security/gpg.h b/lib/security/gpg.h index fb418bb..6efc3d2 100644 --- a/lib/security/gpg.h +++ b/lib/security/gpg.h @@ -23,6 +23,7 @@ enum { PB_LOCKDOWN_NONE = 0, PB_LOCKDOWN_SIGN = 1, + PB_LOCKDOWN_DECRYPT = 2, }; #if defined(HAVE_LIBGPGME) @@ -37,6 +38,9 @@ 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); @@ -59,6 +63,13 @@ int verify_file_signature(const char *plaintext_filename __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; |