[PATCH 2/2] Add encrypted file support

Timothy Pearson tpearson at raptorengineering.com
Tue Aug 2 03:10:15 AEST 2016


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 |  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;
+
+	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;
+	}
+	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__);
+		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 */
+		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;
+
+			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;
+
+			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) {
-- 
1.7.9.5



More information about the Petitboot mailing list