[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