[PATCH 3/3] [V6] Add encrypted file support

Samuel Mendoza-Jonas sam at mendozajonas.com
Wed Aug 17 13:47:10 AEST 2016


On Tue, 2016-08-16 at 17:28 -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.
> 
> Signed-off-by: Timothy Pearson <tpearson at raptorengineering.com>
> ---
>  discover/boot.c             |  42 ++++++-
>  discover/boot.h             |   2 +
>  discover/gpg.c              | 294 +++++++++++++++++++++++++++++++++++++++-----
>  discover/gpg.h              |   8 ++
>  ui/ncurses/nc-boot-editor.c |   9 +-
>  5 files changed, 319 insertions(+), 36 deletions(-)
> 
> diff --git a/discover/boot.c b/discover/boot.c
> index 38be536..8fc4065 100644
> --- a/discover/boot.c
> +++ b/discover/boot.c
> @@ -51,9 +51,14 @@ static int kexec_load(struct boot_task *boot_task)
>  
>  	if ((result = gpg_validate_boot_files(boot_task, &local_initrd,
>  		&local_dtb, &local_image))) {
> -		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;
>  		}
>  	}
> @@ -387,7 +392,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",
> @@ -398,6 +409,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 */
> @@ -412,6 +431,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;
>  	}
> @@ -422,7 +443,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"));
> @@ -442,6 +467,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:
> @@ -490,6 +517,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;
> @@ -529,7 +557,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);
> @@ -547,7 +577,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) {
> @@ -603,7 +633,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 5f6e874..72517f4 100644
> --- a/discover/boot.h
> +++ b/discover/boot.h
> @@ -26,6 +26,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;
> @@ -37,6 +38,7 @@ struct boot_task {
>  };
>  
>  enum {
> +	KEXEC_LOAD_DECRYPTION_FALURE = 252,
>  	KEXEC_LOAD_SIG_SETUP_INVALID = 253,
>  	KEXEC_LOAD_SIGNATURE_FAILURE = 254,
>  };
> diff --git a/discover/gpg.c b/discover/gpg.c
> index e1c1d04..d23d29c 100644
> --- a/discover/gpg.c
> +++ b/discover/gpg.c
> @@ -95,6 +95,173 @@ int copy_file_to_destination(const char * source_file,
>  	return result;
>  }
>  
> +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;

Previous comment about strncmp() applies here too

> +			}
> +			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);
> +			return -1;

Is some cleanup being skipped here?

> +		}
> +		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__);
> +			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__);
> +				}

Same comment from before about break-ing

> +				else {
> +					/* No space on destination device */
> +					result = -1;
> +					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__);
> +		return -1;
> +	}
> +	else {

No need for else {}. Does it make sense to return an error before the
copy loop above?

> +		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)
> @@ -235,10 +402,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)) {
>  		char kernel_filename[MAX_FILENAME_SIZE];
>  		char initrd_filename[MAX_FILENAME_SIZE];
>  		char dtb_filename[MAX_FILENAME_SIZE];
> @@ -324,32 +492,67 @@ 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"))
> +		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"))
> +			if (verify_file_signature(cmdline_template,
> +				local_cmdline_signature,
> +				authorized_signatures_handle,
> +				"/etc/gpg"))
>  				result = KEXEC_LOAD_SIGNATURE_FAILURE;
> -
> -		/* Clean up */
> -		if (cmdline_handle) {
> -			fclose(cmdline_handle);
> -			unlink(cmdline_template);
> +			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) {

if () {
	// foo
} else {
	// bar
}

> +			/* 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);
>  		}
> -		fclose(authorized_signatures_handle);
>  	}
>  
>  	return result;
> @@ -358,7 +561,7 @@ int gpg_validate_boot_files(struct boot_task *boot_task,
>  void gpg_validate_boot_files_cleanup(struct boot_task *boot_task,
>  	const char** local_initrd, const char** local_dtb,
>  	const char** local_image) {
> -	if (boot_task->verify_signature) {
> +	if ((boot_task->verify_signature) || (boot_task->decrypt_files)) {
>  		unlink(*local_image);
>  		if (*local_initrd)
>  			unlink(*local_initrd);
> @@ -374,8 +577,39 @@ void gpg_validate_boot_files_cleanup(struct boot_task *boot_task,
>  }
>  
>  int lockdown_status() {
> -	if (access(LOCKDOWN_FILE, F_OK) == -1)
> -		return PB_LOCKDOWN_NONE;
> -	else
> -		return PB_LOCKDOWN_SIGN;
> +	/* assume most restrictive lockdown type */
> +	int ret = PB_LOCKDOWN_SIGN;
> +
> +	if (access(LOCKDOWN_FILE, F_OK) == -1) {
> +		ret = PB_LOCKDOWN_NONE;
> +	}
> +	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.
> +					 */
> +					ret = PB_LOCKDOWN_DECRYPT;

Same comment about strncmp()

> +				}
> +			}
> +			free(auth_sig_line);
> +		}
> +	}
> +
> +	return ret;
>  }
> \ No newline at end of file
> diff --git a/discover/gpg.h b/discover/gpg.h
> index 9b452b8..0194236 100644
> --- a/discover/gpg.h
> +++ b/discover/gpg.h
> @@ -26,6 +26,7 @@
>  enum {
>  	PB_LOCKDOWN_NONE	= 0,
>  	PB_LOCKDOWN_SIGN	= 1,
> +	PB_LOCKDOWN_DECRYPT	= 2,
>  };
>  
>  #if defined(HAVE_LIBGPGME)
> @@ -41,6 +42,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,
>  	const char** local_initrd, const char** local_dtb,
>  	const char** local_image);
> @@ -60,6 +64,10 @@ int verify_file_signature(const char * plaintext_filename,
>  	const char * signature_filename, FILE * authorized_signatures_handle,
>  	const char * keyring_path) { return -1; }
>  
> +int decrypt_file(const char * filename,
> +	FILE * authorized_signatures_handle, const char * keyring_path)
> +	{ return -1; }
> +
>  int gpg_validate_boot_files(struct boot_task *boot_task,
>  	const char** local_initrd, const char** local_dtb,
>  	const char** local_image) { return 0; }
> diff --git a/ui/ncurses/nc-boot-editor.c b/ui/ncurses/nc-boot-editor.c
> index 9788fef..13fd6e5 100644
> --- a/ui/ncurses/nc-boot-editor.c
> +++ b/ui/ncurses/nc-boot-editor.c
> @@ -208,6 +208,9 @@ static struct pb_boot_data *boot_editor_prepare_data(
>  			boot_editor->widgets.args_sig_file_f);
>  		bd->args_sig_file = conditional_prefix(bd, prefix, s);
>  	}
> +	else {
> +		bd->args_sig_file = NULL;
> +	}
>  
>  	return bd;
>  }
> @@ -532,6 +535,10 @@ static void boot_editor_setup_widgets(struct boot_editor *boot_editor,
>  		boot_editor->widgets.args_sig_file_f = widget_new_textbox(set,
>  				0, 0, field_size, boot_editor->args_sig_file);
>  	}
> +	else {
> +		boot_editor->widgets.args_sig_file_l = NULL;
> +		boot_editor->widgets.args_sig_file_f = NULL;

These nc-boot-editor changes look like they should be in the first patch?

> +	}
>  
>  	boot_editor->widgets.ok_b = widget_new_button(set, 0, 0, 10,
>  					_("OK"), ok_click, boot_editor);
> @@ -629,7 +636,7 @@ struct boot_editor *boot_editor_init(struct cui *cui,
>  		if (boot_editor->use_signature_files)
>  			boot_editor->args_sig_file = bd->args_sig_file;
>  		else
> -			boot_editor->args_sig_file = "";
> +			boot_editor->args_sig_file = talloc_strdup(bd, "");
>  		boot_editor_find_device(boot_editor, bd, sysinfo);
>  	} else {
>  		boot_editor->image = boot_editor->initrd =



More information about the Petitboot mailing list