[PATCH 2/2] Add encrypted file support
Samuel Mendoza-Jonas
sam at ozlabs.au.ibm.com
Tue Aug 2 14:16:56 AEST 2016
On Mon, 2016-08-01 at 12:10 -0500, Timothy Pearson wrote:
> In certain cases, such as network booting over an untrusted connection,
> it may be useful to fully encrypt and sign the kernel files.
>
> Enable fully encrypted boot using builtin keyring via the addition of
> the string "ENCRYPTED" to the first line of the /etc/pb-lockdown file.
> This disables detached (plaintext) signature verification.
What is the origin of the pb-lockdown file? Is it possible to verify
that it hasn't been tampered with (ie. if a user has managed to drop to
the shell)? Presumably this assumes the initrd from the PNOR can be
trusted.
>
> Signed-off-by: Timothy Pearson <tpearson at raptorengineering.com>
> ---
> discover/boot.c | 251 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 231 insertions(+), 20 deletions(-)
>
> diff --git a/discover/boot.c b/discover/boot.c
> index eb65f31..06ec57c 100644
> --- a/discover/boot.c
> +++ b/discover/boot.c
> @@ -36,6 +36,7 @@ enum {
> };
>
> enum {
> + KEXEC_LOAD_DECRYPTION_FALURE = 252,
> KEXEC_LOAD_SIG_SETUP_INVALID = 253,
> KEXEC_LOAD_SIGNATURE_FAILURE = 254,
> };
> @@ -54,6 +55,7 @@ struct boot_task {
> bool cancelled;
> #if defined(HAVE_LIBGPGME)
> bool verify_signature;
> + bool decrypt_files;
> struct load_url_result *image_signature;
> struct load_url_result *initrd_signature;
> struct load_url_result *dtb_signature;
> @@ -118,6 +120,158 @@ static int copy_file_to_destination(const char * source_file, char * destination
> }
>
> #if defined(HAVE_LIBGPGME)
> +static 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 11;
Same comment about the error codes - if they're not being read it's fine
to just return -1.
> +
> + gpgme_ctx_t gpg_context;
> + gpgme_error_t err;
> + gpgme_data_t ciphertext_data;
> + gpgme_data_t plaintext_data;
> + gpgme_engine_info_t enginfo;
> + gpgme_verify_result_t verification_result;
> + gpgme_signature_t verification_signatures;
> +
> + /* 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__);
> + result = 1;
> + return result;
> + }
> + err = gpgme_get_engine_info(&enginfo);
> + if (err != GPG_ERR_NO_ERROR) {
> + pb_log("%s: GPG engine failed to initialize\n", __func__);
> + result = 2;
> + return result;
> + }
> + err = gpgme_new(&gpg_context);
> + if (err != GPG_ERR_NO_ERROR) {
> + pb_log("%s: GPG context could not be created\n", __func__);
> + result = 3;
> + return result;
> + }
> + 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__);
> + result = 4;
> + return result;
> + }
This looks the same as the init in verify_file_signature() - would it
make sense to have this in it's own function that gets called once
before verify_file_signature() and decrypt_file()?
> + 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);
80 chars
> + if (err != GPG_ERR_NO_ERROR) {
> + pb_log("%s: Could not set GPG engine information\n", __func__);
> + result = 5;
> + return result;
> + }
> + err = gpgme_data_new(&plaintext_data);
> + if (err != GPG_ERR_NO_ERROR) {
> + pb_log("%s: Could not create GPG plaintext data buffer\n", __func__);
> + result = 6;
> + return result;
> + }
> + 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);
> + result = 7;
> + return result;
> + }
> +
> + /* 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__);
> + result = 8;
> + return result;
> + }
> + 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 */
Ah so this is probably why the source files get copied to /tmp/ in
patch 1.
> + 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);
> + result = 9;
> + return result;
> + }
> + 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__);
> + result = 10;
> + return result;
> + }
> + 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 = 2;
> + pb_log("%s: failed: temporary storage full\n", __func__);
> + }
> + }
> + }
> + 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__);
> + result = 9;
> + return result;
> + }
> + else {
> + pb_log("%s: GPG signature for decrypted file '%s' verified\n", __func__, filename);
> + }
> +
> + return result;
> +}
> +
> static int verify_file_signature(const char * plaintext_filename, const char * signature_filename, FILE * authorized_signatures_handle, const char * keyring_path)
> {
> int result = 0;
> @@ -256,7 +410,7 @@ static int kexec_load(struct boot_task *boot_task)
> 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;
>
> - if (boot_task->verify_signature) {
> + if ((boot_task->verify_signature) || (boot_task->decrypt_files)) {
> int max_filename_size = 8192;
> char kernel_filename[max_filename_size];
> char initrd_filename[max_filename_size];
> @@ -306,21 +460,41 @@ static int kexec_load(struct boot_task *boot_task)
> if (boot_task->local_dtb)
> local_dtb = strdup(dtb_filename);
>
> - /* Check signatures */
> - if (verify_file_signature(kernel_filename, local_image_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"))
> + 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 (boot_task->local_dtb_signature)
> - if (verify_file_signature(dtb_filename, local_dtb_signature, authorized_signatures_handle, "/etc/gpg"))
> - result = KEXEC_LOAD_SIGNATURE_FAILURE;
> -
> - fclose(authorized_signatures_handle);
> -
> - if (result == KEXEC_LOAD_SIGNATURE_FAILURE) {
> - pb_log("%s: Aborting kexec due to signature verification failure\n", __func__);
> - goto abort_kexec;
> + 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;
If the verify_file_signature(..initrd..) fails but
verify_file_signature(..dtb..) passes, will this mistakenly set result
again and think both have passed?
> +
> + fclose(authorized_signatures_handle);
> +
> + if (result == KEXEC_LOAD_SIGNATURE_FAILURE) {
> + pb_log("%s: Aborting kexec due to signature verification failure\n", __func__);
> + goto abort_kexec;
> + }
> + }
> + else if (boot_task->decrypt_files) {
> + /* Decrypt files */
> + if (decrypt_file(kernel_filename, authorized_signatures_handle, "/etc/gpg"))
> + result = KEXEC_LOAD_DECRYPTION_FALURE;
> + 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;
Same problem here?
> +
> + fclose(authorized_signatures_handle);
> +
> + if (result == KEXEC_LOAD_DECRYPTION_FALURE) {
> + pb_log("%s: Aborting kexec due to decryption failure\n", __func__);
> + goto abort_kexec;
> + }
> }
> }
> #endif
> @@ -360,7 +534,7 @@ static int kexec_load(struct boot_task *boot_task)
>
> #if defined(HAVE_LIBGPGME)
> abort_kexec:
> - if (boot_task->verify_signature) {
> + if ((boot_task->verify_signature) || (boot_task->decrypt_files)) {
> unlink(local_image);
> if (local_initrd)
> unlink(local_initrd);
> @@ -687,7 +861,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"));
> }
> @@ -800,10 +978,43 @@ struct boot_task *boot(void *ctx, struct discover_boot_option *opt,
> boot_task->status_fn = status_fn;
> boot_task->status_arg = status_arg;
> #if defined(HAVE_LIBGPGME)
> - if (access(LOCKDOWN_FILE, F_OK) == -1)
> + if (access(LOCKDOWN_FILE, F_OK) == -1) {
> boot_task->verify_signature = false;
> - else
> - boot_task->verify_signature = true;
> + boot_task->decrypt_files = false;
> + }
> + else {
> + /* determine lockdown type */
> + FILE *authorized_signatures_handle = NULL;
> + authorized_signatures_handle = fopen(LOCKDOWN_FILE, "r");
> + if (authorized_signatures_handle) {
> + 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. */
> + boot_task->verify_signature = false;
> + boot_task->decrypt_files = true;
> + }
> + }
> + else {
> + /* file present but empty. assume most restrictive lockdown type */
> + boot_task->verify_signature = true;
> + boot_task->decrypt_files = false;
> + }
> + free(auth_sig_line);
> + }
> + else {
> + /* assume most restrictive lockdown type */
> + boot_task->verify_signature = true;
> + boot_task->decrypt_files = false;
> + }
> + }
> #endif
>
> if (cmd && cmd->boot_args) {
More information about the Petitboot
mailing list