diff mbox series

[U-Boot,RFC,08/15] efi_loader: variable: support variable authentication

Message ID 20190918012643.25192-9-takahiro.akashi@linaro.org
State RFC
Delegated to: Heinrich Schuchardt
Headers show
Series efi_loader: add secure boot support | expand

Commit Message

AKASHI Takahiro Sept. 18, 2019, 1:26 a.m. UTC
With this commit, EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
is supported for authenticated variables and the system secure state
will transfer between setup mode and user mode as UEFI specification
section 32.3 describes.

Internally, authentication data is stored as part of authenticated
variable's value. It is nothing but a pkcs7 message (but we need some
wrapper, see efi_variable_parse_signature()) and will be validated by
efi_variable_authenticate(), hence efi_signature_verify_with_db().

Associated time value will be encoded in "{...,time=...}" along with
other UEFI variable's attributes.

Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
---
 include/efi_loader.h          |   4 +
 lib/efi_loader/efi_variable.c | 855 ++++++++++++++++++++++++++++++----
 2 files changed, 757 insertions(+), 102 deletions(-)
diff mbox series

Patch

diff --git a/include/efi_loader.h b/include/efi_loader.h
index c75ee5fcb6ba..caac8efba89a 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -162,6 +162,7 @@  extern const efi_guid_t efi_guid_image_security_database;
 extern const efi_guid_t efi_guid_sha256;
 extern const efi_guid_t efi_guid_cert_x509;
 extern const efi_guid_t efi_guid_cert_x509_sha256;
+extern const efi_guid_t efi_guid_cert_type_pkcs7;
 
 extern unsigned int __efi_runtime_start, __efi_runtime_stop;
 extern unsigned int __efi_runtime_rel_start, __efi_runtime_rel_stop;
@@ -699,6 +700,9 @@  efi_status_t efi_image_region_add(efi_image_regions *ctx,
 
 void efi_sigstore_free(efi_signature_store *ctx);
 efi_signature_store *efi_sigstore_parse_sigdb(u16 *name);
+
+bool efi_secure_boot_enabled(void);
+efi_status_t efi_init_secure_boot(void);
 #endif /* CONFIG_EFI_SECURE_BOOT */
 
 #else /* CONFIG_IS_ENABLED(EFI_LOADER) */
diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c
index 5d1ee50a606e..fa706f1ad6b8 100644
--- a/lib/efi_loader/efi_variable.c
+++ b/lib/efi_loader/efi_variable.c
@@ -11,8 +11,25 @@ 
 #include <efi_loader.h>
 #include <hexdump.h>
 #include <env_internal.h>
+#include <rtc.h>
 #include <search.h>
 #include <uuid.h>
+#include <linux/compat.h>
+/*
+ * avoid duplicated inclusion:
+ * #include "../lib/crypto/x509_parser.h"
+ */
+#include "../lib/crypto/pkcs7_parser.h"
+
+enum efi_secure_mode {
+	EFI_MODE_SETUP,
+	EFI_MODE_USER,
+	EFI_MODE_AUDIT,
+	EFI_MODE_DEPLOYED,
+};
+
+const efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID;
+static bool efi_secure_boot;
 
 #define READ_ONLY BIT(31)
 
@@ -109,7 +126,7 @@  static const char *prefix(const char *str, const char *prefix)
  * @attrp:	pointer to UEFI attributes
  * Return:	pointer to remainder of U-Boot variable value
  */
-static const char *parse_attr(const char *str, u32 *attrp)
+static const char *parse_attr(const char *str, u32 *attrp, u64 *timep)
 {
 	u32 attr = 0;
 	char sep = '{';
@@ -132,6 +149,12 @@  static const char *parse_attr(const char *str, u32 *attrp)
 			attr |= EFI_VARIABLE_BOOTSERVICE_ACCESS;
 		} else if ((s = prefix(str, "run"))) {
 			attr |= EFI_VARIABLE_RUNTIME_ACCESS;
+		} else if ((s = prefix(str, "time="))) {
+			attr |= EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
+			hex2bin((u8 *)timep, s, sizeof(*timep));
+			s += sizeof(*timep) * 2;
+		} else if (*str == '}') {
+			break;
 		} else {
 			printf("invalid attribute: %s\n", str);
 			break;
@@ -148,49 +171,454 @@  static const char *parse_attr(const char *str, u32 *attrp)
 	return str;
 }
 
-/**
- * efi_get_variable() - retrieve value of a UEFI variable
- *
- * This function implements the GetVariable runtime service.
- *
- * See the Unified Extensible Firmware Interface (UEFI) specification for
- * details.
- *
- * @variable_name:	name of the variable
- * @vendor:		vendor GUID
- * @attributes:		attributes of the variable
- * @data_size:		size of the buffer to which the variable value is copied
- * @data:		buffer to which the variable value is copied
- * Return:		status code
+static efi_status_t efi_set_variable_internal(u16 *variable_name,
+					      const efi_guid_t *vendor,
+					      u32 attributes,
+					      efi_uintn_t data_size,
+					      const void *data,
+					      bool ro_check);
+
+static efi_status_t efi_transfer_secure_state(enum efi_secure_mode mode)
+{
+	u32 attributes;
+	u8 val;
+	efi_status_t ret;
+
+	debug("Secure state from %d to %d\n", efi_secure_mode, mode);
+
+	attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS |
+		     EFI_VARIABLE_RUNTIME_ACCESS;
+	if (mode == EFI_MODE_DEPLOYED) {
+		val = 1;
+		ret = efi_set_variable_internal(L"SecureBoot",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 0;
+		ret = efi_set_variable_internal(L"SetupMode",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 0;
+		ret = efi_set_variable_internal(L"AuditMode",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 1;
+		ret = efi_set_variable_internal(L"DeployedMode",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+
+		efi_secure_boot = true;
+	} else if (mode == EFI_MODE_AUDIT) {
+		ret = efi_set_variable_internal(L"PK",
+						&efi_global_variable_guid,
+						attributes,
+						0, NULL,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 0;
+		ret = efi_set_variable_internal(L"SecureBoot",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 1;
+		ret = efi_set_variable_internal(L"SetupMode",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 1;
+		ret = efi_set_variable_internal(L"AuditMode",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 0;
+		ret = efi_set_variable_internal(L"DeployedMode",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+
+		efi_secure_boot = true;
+	} else if (mode == EFI_MODE_USER) {
+		val = 1;
+		ret = efi_set_variable_internal(L"SecureBoot",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 0;
+		ret = efi_set_variable_internal(L"SetupMode",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 0;
+		ret = efi_set_variable_internal(L"AuditMode",
+						&efi_global_variable_guid,
+						attributes,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 0;
+		ret = efi_set_variable_internal(L"DeployedMode",
+						&efi_global_variable_guid,
+						attributes,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+
+		efi_secure_boot = true;
+	} else if (mode == EFI_MODE_SETUP) {
+		val = 0;
+		ret = efi_set_variable_internal(L"SecureBoot",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 1;
+		ret = efi_set_variable_internal(L"SetupMode",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 0;
+		ret = efi_set_variable_internal(L"AuditMode",
+						&efi_global_variable_guid,
+						attributes,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+		val = 0;
+		ret = efi_set_variable_internal(L"DeployedMode",
+						&efi_global_variable_guid,
+						attributes | READ_ONLY,
+						sizeof(val), &val,
+						false);
+		if (ret != EFI_SUCCESS)
+			goto err;
+	} else {
+		return EFI_INVALID_PARAMETER;
+	}
+
+	return EFI_SUCCESS;
+
+err:
+	/* TODO: failsafe */
+	printf("ERROR: Secure state transition failed\n");
+	return ret;
+}
+
+efi_status_t efi_init_secure_boot(void)
+{
+	efi_uintn_t size = 0;
+	efi_status_t ret;
+
+	ret = EFI_CALL(efi_get_variable(L"PK", &efi_global_variable_guid,
+					NULL, &size, NULL));
+	if (ret == EFI_BUFFER_TOO_SMALL && IS_ENABLED(CONFIG_EFI_SECURE_BOOT))
+		ret = efi_transfer_secure_state(EFI_MODE_USER);
+	else
+		ret = efi_transfer_secure_state(EFI_MODE_SETUP);
+
+	return ret;
+}
+
+/* check if secure boot is enabled by trying to read PK */
+bool efi_secure_boot_enabled(void)
+{
+	return efi_secure_boot;
+}
+
+#ifdef CONFIG_EFI_SECURE_BOOT
+static u8 pkcs7_hdr[] = {
+	/* SEQUENCE */
+	0x30, 0x82, 0x05, 0xc7,
+	/* OID: pkcs7-signedData */
+	0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02,
+	/* Context Structured? */
+	0xa0, 0x82, 0x05, 0xb8,
+};
+
+/*
+ * This function is a wrapper for pkcs7_parse_message() now.
+ * TODO: Any better way?
  */
-efi_status_t EFIAPI efi_get_variable(u16 *variable_name,
-				     const efi_guid_t *vendor, u32 *attributes,
-				     efi_uintn_t *data_size, void *data)
+static struct pkcs7_message *efi_variable_parse_signature(const void *buf,
+							  size_t buflen)
+{
+	u8 *ebuf;
+	size_t ebuflen, len;
+	struct pkcs7_message *msg;
+
+	/*
+	 * This is the best assumption to check if the binary is
+	 * already in a form of pkcs7's signedData.
+	 */
+	if (buflen > sizeof(pkcs7_hdr) &&
+	    !memcmp(&((u8 *)buf)[4], &pkcs7_hdr[4], 11)) {
+		msg = pkcs7_parse_message(buf, buflen);
+		goto out;
+	}
+
+	/*
+	 * Otherwise, we should add a dummy prefix sequence for pkcs7
+	 * message parser to be able to process.
+	 * NOTE: EDK2 also uses similar hack in WrapPkcs7Data()
+	 * in CryptoPkg/Library/BaseCryptLib/Pk/CryptPkcs7VerifyCommon.c
+	 */
+	debug("Fake prefix added to authentication data\n");
+	ebuflen = sizeof(pkcs7_hdr) + buflen;
+	if (ebuflen <= 0x7f) {
+		debug("Data is too short\n");
+		return NULL;
+	}
+
+	ebuf = malloc(ebuflen);
+	if (!ebuf) {
+		debug("Out of memory\n");
+		return NULL;
+	}
+
+	memcpy(ebuf, pkcs7_hdr, sizeof(pkcs7_hdr));
+	memcpy(ebuf + sizeof(pkcs7_hdr), buf, buflen);
+	len = ebuflen - 4;
+	ebuf[2] = (len >> 8) & 0xff;
+	ebuf[3] = len & 0xff;
+	len = ebuflen - 0x13;
+	ebuf[0x11] = (len >> 8) & 0xff;
+	ebuf[0x12] = len & 0xff;
+
+	msg = pkcs7_parse_message(ebuf, ebuflen);
+
+	free(ebuf);
+
+out:
+	if (IS_ERR(msg))
+		return NULL;
+
+	return msg;
+}
+
+/*
+ * Called by efi_set_variable() to verify that the input is correct.
+ * Will replace the given data pointer with another that points to
+ * the actual data to store in the internal memory.
+ */
+static efi_status_t efi_variable_authenticate(u16 *variable,
+					      const efi_guid_t *vendor,
+					      efi_uintn_t *data_size,
+					      const void **data, u32 given_attr,
+					      u32 *env_attr, u64 *time)
+{
+	const struct efi_variable_authentication_2 *auth;
+	efi_signature_store *truststore, *truststore2;
+	struct pkcs7_message *var_sig;
+	efi_image_regions *regs;
+	const struct efi_time *timestamp;
+	struct rtc_time tm;
+	efi_status_t ret;
+
+	var_sig = NULL;
+	truststore = NULL;
+	truststore2 = NULL;
+	regs = NULL;
+	ret = EFI_SECURITY_VIOLATION;
+
+	if (*data_size < sizeof(struct efi_variable_authentication_2))
+		goto err;
+
+	/* authentication data */
+	auth = *data;
+	if (*data_size < (sizeof(auth->time_stamp)
+				+ auth->auth_info.hdr.dwLength))
+		goto err;
+
+	if (guidcmp(&auth->auth_info.cert_type, &efi_guid_cert_type_pkcs7))
+		goto err;
+
+	*data += sizeof(auth->time_stamp) + auth->auth_info.hdr.dwLength;
+	*data_size -= (sizeof(auth->time_stamp)
+				+ auth->auth_info.hdr.dwLength);
+
+	/* FIXME: What to do when one attr has time but other one does not? */
+	timestamp = &auth->time_stamp;
+	memset(&tm, 0, sizeof(tm));
+	tm.tm_year = timestamp->year;
+	tm.tm_mon = timestamp->month;
+	tm.tm_mday = timestamp->day;
+	tm.tm_hour = timestamp->hour;
+	tm.tm_min = timestamp->minute;
+	tm.tm_sec = timestamp->second;
+
+	if (!efi_secure_boot_enabled()) {
+		/* finished checking */
+		*time = rtc_mktime(&tm);
+		return EFI_SUCCESS;
+	}
+
+	if (rtc_mktime(&tm) <= *time)
+		goto err;
+
+	/* data to be digested */
+	regs = calloc(1, sizeof(*regs));
+	if (!regs)
+		goto err;
+	efi_image_region_add(regs, (uint8_t *)variable,
+			     (uint8_t *)variable
+				+ u16_strlen(variable) * sizeof(u16), 1);
+	efi_image_region_add(regs, (uint8_t *)vendor,
+			     (uint8_t *)vendor + sizeof(*vendor), 1);
+	efi_image_region_add(regs, (uint8_t *)&given_attr,
+			     (uint8_t *)&given_attr + sizeof(given_attr), 1);
+	efi_image_region_add(regs, (uint8_t *)timestamp,
+			     (uint8_t *)timestamp + sizeof(*timestamp), 1);
+	efi_image_region_add(regs, (uint8_t *)*data,
+			     (uint8_t *)*data + *data_size, 1);
+
+	/* variable's signature list */
+	if (auth->auth_info.hdr.dwLength < sizeof(auth->auth_info))
+		goto err;
+	var_sig = efi_variable_parse_signature(auth->auth_info.cert_data,
+					       auth->auth_info.hdr.dwLength
+						   - sizeof(auth->auth_info));
+	if (IS_ERR(var_sig)) {
+		debug("Parsing variable's signature failed\n");
+		var_sig = NULL;
+		goto err;
+	}
+
+	/* signature database used for authentication */
+	if (u16_strcmp(variable, L"PK") == 0 ||
+	    u16_strcmp(variable, L"KEK") == 0) {
+		/* with PK */
+		truststore = efi_sigstore_parse_sigdb(L"PK");
+		if (!truststore)
+			goto err;
+	} else if (u16_strcmp(variable, L"db") == 0 ||
+		   u16_strcmp(variable, L"dbx") == 0) {
+		/* with PK and KEK */
+		truststore = efi_sigstore_parse_sigdb(L"KEK");
+		truststore2 = efi_sigstore_parse_sigdb(L"PK");
+
+		if (!truststore) {
+			if (!truststore2)
+				goto err;
+
+			truststore = truststore2;
+			truststore2 = NULL;
+		}
+	} else {
+		/* TODO: what UEFI spec says for other variables? */
+		goto err;
+	}
+
+	/* verify signature */
+	if (efi_signature_verify_with_db(regs, var_sig, truststore)) {
+		debug("Verified\n");
+	} else {
+		if (truststore2) {
+			if (efi_signature_verify_with_db(regs, var_sig,
+							 truststore2)) {
+				debug("Verified\n");
+			} else {
+				debug("Verifying variable's signature failed\n");
+				goto err;
+			}
+		} else {
+			debug("Verifying variable's signature failed\n");
+			goto err;
+		}
+	}
+
+	/* finished checking */
+	*time = rtc_mktime(&tm);
+	ret = EFI_SUCCESS;
+
+err:
+	efi_sigstore_free(truststore);
+	efi_sigstore_free(truststore2);
+	pkcs7_free_message(var_sig);
+	free(regs);
+
+	return ret;
+}
+#else
+static efi_status_t efi_variable_authenticate(u16 *variable,
+					      const efi_guid_t *vendor,
+					      efi_uintn_t *data_size,
+					      const void **data, u32 given_attr,
+					      u32 *env_attr, u64 *time)
+{
+	return EFI_SUCCESS;
+}
+#endif /* CONFIG_EFI_SECURE_BOOT */
+
+static
+efi_status_t EFIAPI efi_get_variable_common(u16 *variable_name,
+					    const efi_guid_t *vendor,
+					    u32 *attributes,
+					    efi_uintn_t *data_size, void *data,
+					    bool is_non_volatile)
 {
 	char *native_name;
 	efi_status_t ret;
 	unsigned long in_size;
-	const char *val, *s;
+	const char *val = NULL, *s;
+	u64 time = 0;
 	u32 attr;
 
-	EFI_ENTRY("\"%ls\" %pUl %p %p %p", variable_name, vendor, attributes,
-		  data_size, data);
-
 	if (!variable_name || !vendor || !data_size)
 		return EFI_EXIT(EFI_INVALID_PARAMETER);
 
 	ret = efi_to_native(&native_name, variable_name, vendor);
 	if (ret)
-		return EFI_EXIT(ret);
+		return ret;
 
 	EFI_PRINT("get '%s'\n", native_name);
 
 	val = env_get(native_name);
 	free(native_name);
 	if (!val)
-		return EFI_EXIT(EFI_NOT_FOUND);
+		return EFI_NOT_FOUND;
 
-	val = parse_attr(val, &attr);
+	val = parse_attr(val, &attr, &time);
 
 	in_size = *data_size;
 
@@ -199,7 +627,7 @@  efi_status_t EFIAPI efi_get_variable(u16 *variable_name,
 
 		/* number of hexadecimal digits must be even */
 		if (len & 1)
-			return EFI_EXIT(EFI_DEVICE_ERROR);
+			return EFI_DEVICE_ERROR;
 
 		/* two characters per byte: */
 		len /= 2;
@@ -210,11 +638,13 @@  efi_status_t EFIAPI efi_get_variable(u16 *variable_name,
 			goto out;
 		}
 
-		if (!data)
-			return EFI_EXIT(EFI_INVALID_PARAMETER);
+		if (!data) {
+			debug("Variable with no data shouldn't exist.\n");
+			return EFI_INVALID_PARAMETER;
+		}
 
 		if (hex2bin(data, s, len))
-			return EFI_EXIT(EFI_DEVICE_ERROR);
+			return EFI_DEVICE_ERROR;
 
 		EFI_PRINT("got value: \"%s\"\n", s);
 	} else if ((s = prefix(val, "(utf8)"))) {
@@ -227,8 +657,10 @@  efi_status_t EFIAPI efi_get_variable(u16 *variable_name,
 			goto out;
 		}
 
-		if (!data)
-			return EFI_EXIT(EFI_INVALID_PARAMETER);
+		if (!data) {
+			debug("Variable with no data shouldn't exist.\n");
+			return EFI_INVALID_PARAMETER;
+		}
 
 		memcpy(data, s, len);
 		((char *)data)[len] = '\0';
@@ -236,13 +668,67 @@  efi_status_t EFIAPI efi_get_variable(u16 *variable_name,
 		EFI_PRINT("got value: \"%s\"\n", (char *)data);
 	} else {
 		EFI_PRINT("invalid value: '%s'\n", val);
-		return EFI_EXIT(EFI_DEVICE_ERROR);
+		return EFI_DEVICE_ERROR;
 	}
 
 out:
 	if (attributes)
 		*attributes = attr & EFI_VARIABLE_MASK;
 
+	return ret;
+}
+
+static
+efi_status_t EFIAPI efi_get_volatile_variable(u16 *variable_name,
+					      const efi_guid_t *vendor,
+					      u32 *attributes,
+					      efi_uintn_t *data_size,
+					      void *data)
+{
+	return efi_get_variable_common(variable_name, vendor, attributes,
+				       data_size, data, false);
+}
+
+efi_status_t EFIAPI efi_get_nonvolatile_variable(u16 *variable_name,
+						 const efi_guid_t *vendor,
+						 u32 *attributes,
+						 efi_uintn_t *data_size,
+						 void *data)
+{
+	return efi_get_variable_common(variable_name, vendor, attributes,
+				       data_size, data, true);
+}
+
+/**
+ * efi_efi_get_variable() - retrieve value of a UEFI variable
+ *
+ * This function implements the GetVariable runtime service.
+ *
+ * See the Unified Extensible Firmware Interface (UEFI) specification for
+ * details.
+ *
+ * @variable_name:	name of the variable
+ * @vendor:		vendor GUID
+ * @attributes:		attributes of the variable
+ * @data_size:		size of the buffer to which the variable value is copied
+ * @data:		buffer to which the variable value is copied
+ * Return:		status code
+ */
+efi_status_t EFIAPI efi_get_variable(u16 *variable_name,
+				     const efi_guid_t *vendor, u32 *attributes,
+				     efi_uintn_t *data_size, void *data)
+{
+	efi_status_t ret;
+
+	EFI_ENTRY("\"%ls\" %pUl %p %p %p", variable_name, vendor, attributes,
+		  data_size, data);
+
+	ret = efi_get_volatile_variable(variable_name, vendor, attributes,
+					data_size, data);
+	if (ret == EFI_NOT_FOUND)
+		ret = efi_get_nonvolatile_variable(variable_name, vendor,
+						   attributes, data_size, data);
+
 	return EFI_EXIT(ret);
 }
 
@@ -275,6 +761,7 @@  static efi_status_t parse_uboot_variable(char *variable,
 {
 	char *guid, *name, *end, c;
 	unsigned long name_len;
+	u64 time;
 	u16 *p;
 
 	guid = strchr(variable, '_');
@@ -309,7 +796,7 @@  static efi_status_t parse_uboot_variable(char *variable,
 	*(name - 1) = c;
 
 	/* attributes */
-	parse_attr(end, attributes);
+	parse_attr(end, attributes, &time);
 
 	return EFI_SUCCESS;
 }
@@ -391,7 +878,7 @@  efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size,
 		list_len = hexport_r(&env_htab, '\n',
 				     H_MATCH_REGEX | H_MATCH_KEY,
 				     &efi_variables_list, 0, 1, regexlist);
-		/* 1 indicates that no match was found */
+
 		if (list_len <= 1)
 			return EFI_EXIT(EFI_NOT_FOUND);
 
@@ -404,132 +891,296 @@  efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size,
 	return EFI_EXIT(ret);
 }
 
-/**
- * efi_set_variable() - set value of a UEFI variable
- *
- * This function implements the SetVariable runtime service.
- *
- * See the Unified Extensible Firmware Interface (UEFI) specification for
- * details.
- *
- * @variable_name:	name of the variable
- * @vendor:		vendor GUID
- * @attributes:		attributes of the variable
- * @data_size:		size of the buffer with the variable value
- * @data:		buffer with the variable value
- * Return:		status code
- */
-efi_status_t EFIAPI efi_set_variable(u16 *variable_name,
-				     const efi_guid_t *vendor, u32 attributes,
-				     efi_uintn_t data_size, const void *data)
+static
+efi_status_t EFIAPI efi_set_variable_common(u16 *variable_name,
+					    const efi_guid_t *vendor,
+					    u32 attributes,
+					    efi_uintn_t data_size,
+					    const void *data,
+					    bool ro_check,
+					    bool is_non_volatile)
 {
-	char *native_name = NULL, *oval, *val = NULL, *s;
-	size_t oval_size;
-	efi_status_t ret = EFI_SUCCESS;
+	char *native_name = NULL, *odata = NULL, *val = NULL, *s;
+	efi_uintn_t odata_size;
+	bool append, delete;
+	u64 time = 0;
 	u32 attr;
+	efi_status_t ret = EFI_SUCCESS;
 
-	EFI_ENTRY("\"%ls\" %pUl %x %zu %p", variable_name, vendor, attributes,
-		  data_size, data);
+	debug("%s: set '%s'\n", __func__, native_name);
 
 	if (!variable_name || !*variable_name || !vendor ||
 	    ((attributes & EFI_VARIABLE_RUNTIME_ACCESS) &&
 	     !(attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS))) {
 		ret = EFI_INVALID_PARAMETER;
-		goto out;
+		goto err;
 	}
 
 	ret = efi_to_native(&native_name, variable_name, vendor);
 	if (ret)
-		goto out;
-
-#define ACCESS_ATTR (EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS)
-
-	if ((data_size == 0) || !(attributes & ACCESS_ATTR)) {
-		/* delete the variable: */
-		env_set(native_name, NULL);
-		ret = EFI_SUCCESS;
-		goto out;
+		goto err;
+
+	/* check if a variable exists */
+	odata_size = 0;
+	attr = 0;
+	ret = EFI_CALL(efi_get_variable(variable_name, vendor, &attr,
+					&odata_size, NULL));
+	if (ret == EFI_BUFFER_TOO_SMALL) {
+		if ((is_non_volatile && !(attr & EFI_VARIABLE_NON_VOLATILE)) ||
+		    (!is_non_volatile && (attr & EFI_VARIABLE_NON_VOLATILE))) {
+			ret = EFI_INVALID_PARAMETER;
+			goto err;
+		}
 	}
 
-	oval = env_get(native_name);
-	if (oval) {
-		oval = parse_attr(oval, &attr);
+	append = !!(attributes & EFI_VARIABLE_APPEND_WRITE);
+	attributes &= ~(u32)EFI_VARIABLE_APPEND_WRITE;
+	delete = (!data_size || !(attributes &
+				  (EFI_VARIABLE_BOOTSERVICE_ACCESS |
+				   EFI_VARIABLE_RUNTIME_ACCESS)));
 
-		if (attr & READ_ONLY) {
+	/* check attributes */
+	if (odata_size) {
+		if (ro_check && (attr & READ_ONLY)) {
 			ret = EFI_WRITE_PROTECTED;
-			goto out;
+			goto err;
 		}
 
 		/* attributes won't be changed */
-		if (attr != (attributes & ~EFI_VARIABLE_APPEND_WRITE)) {
+		if (!delete &&
+		    ((ro_check && attr != attributes) ||
+		     (!ro_check && ((attr & ~(u32)READ_ONLY)
+				    != (attributes & ~(u32)READ_ONLY))))) {
 			ret = EFI_INVALID_PARAMETER;
+			goto err;
+		}
+	} else {
+		if (delete || append) {
+			ret = EFI_NOT_FOUND;
+			goto err;
+		}
+	}
+
+	if (((!u16_strcmp(variable_name, L"PK") ||
+	      !u16_strcmp(variable_name, L"KEK")) &&
+		!guidcmp(vendor, &efi_global_variable_guid)) ||
+	    ((!u16_strcmp(variable_name, L"db") ||
+	      !u16_strcmp(variable_name, L"dbx")) &&
+		!guidcmp(vendor, &efi_guid_image_security_database))) {
+		/* authentication is mandatory */
+		if (!(attributes &
+		      EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)) {
+			debug("%ls: AUTHENTICATED_WRITE_ACCESS required\n",
+			      variable_name);
+			ret = EFI_INVALID_PARAMETER;
+			goto err;
+		}
+	}
+
+	/* authenticate a variable */
+	if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) {
+		if (attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) {
+			ret = EFI_INVALID_PARAMETER;
+			goto err;
+		}
+		if (attributes &
+		    EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) {
+			ret = efi_variable_authenticate(variable_name, vendor,
+							&data_size, &data,
+							attributes, &attr,
+							&time);
+			if (ret != EFI_SUCCESS)
+				goto err;
+
+			/* last chance to check for delete */
+			if (!data_size)
+				delete = true;
+		}
+	} else {
+		if (attributes &
+		    (EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS |
+		     EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)) {
+			debug("Secure boot is not configured\n");
+			ret = EFI_INVALID_PARAMETER;
+			goto err;
+		}
+	}
+
+	/* delete a variable */
+	if (delete) {
+		ret = EFI_SUCCESS;
+		if (odata_size) {
+			val = NULL;
 			goto out;
 		}
+		ret = EFI_SUCCESS;
+		goto err; /* not error, but nothing to do */
+	}
 
-		if (attributes & EFI_VARIABLE_APPEND_WRITE) {
-			if (!prefix(oval, "(blob)")) {
-				/* TODO: should support utf8? */
-				return EFI_DEVICE_ERROR;
-				goto out;
-			}
-			oval_size = strlen(oval);
-		} else {
-			oval_size = 0;
+	if (append) {
+		odata = malloc(odata_size);
+		if (!odata) {
+			return EFI_OUT_OF_RESOURCES;
+			goto err;
 		}
+		ret = EFI_CALL(efi_get_variable(variable_name, vendor,
+						&attr, &odata_size, odata));
+		if (ret != EFI_SUCCESS)
+			goto err;
 	} else {
-		oval_size = 0;
+		odata_size = 0;
 	}
 
-	val = malloc(oval_size + 2 * data_size
-		     + strlen("{ro,run,boot,nv}(blob)") + 1);
+	val = malloc(2 * odata_size + 2 * data_size
+		     + strlen("{ro,run,boot,nv,time=0123456701234567}(blob)")
+		     + 1);
 	if (!val) {
 		ret = EFI_OUT_OF_RESOURCES;
-		goto out;
+		goto err;
 	}
 
 	s = val;
 
-	/* store attributes */
-	attributes &= (EFI_VARIABLE_NON_VOLATILE |
+	/*
+	 * store attributes
+	 */
+	attributes &= (READ_ONLY |
+		       EFI_VARIABLE_NON_VOLATILE |
 		       EFI_VARIABLE_BOOTSERVICE_ACCESS |
-		       EFI_VARIABLE_RUNTIME_ACCESS);
+		       EFI_VARIABLE_RUNTIME_ACCESS |
+		       EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS);
 	s += sprintf(s, "{");
 	while (attributes) {
-		u32 attr = 1 << (ffs(attributes) - 1);
+		attr = 1 << (ffs(attributes) - 1);
 
-		if (attr == EFI_VARIABLE_NON_VOLATILE)
+		if (attr == READ_ONLY) {
+			s += sprintf(s, "ro");
+		} else if (attr == EFI_VARIABLE_NON_VOLATILE) {
 			s += sprintf(s, "nv");
-		else if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS)
+		} else if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS) {
 			s += sprintf(s, "boot");
-		else if (attr == EFI_VARIABLE_RUNTIME_ACCESS)
+		} else if (attr == EFI_VARIABLE_RUNTIME_ACCESS) {
 			s += sprintf(s, "run");
+		} else if (attr ==
+			   EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) {
+			s += sprintf(s, "time=");
+			s = bin2hex(s, (u8 *)&time, sizeof(time));
+		}
 
 		attributes &= ~attr;
 		if (attributes)
 			s += sprintf(s, ",");
 	}
 	s += sprintf(s, "}");
-
-	if (oval_size)
-		/* APPEND_WRITE */
-		s += sprintf(s, oval);
-	else
-		s += sprintf(s, "(blob)");
+	s += sprintf(s, "(blob)");
 
 	/* store payload: */
+	if (append)
+		s = bin2hex(s, odata, odata_size);
 	s = bin2hex(s, data, data_size);
 	*s = '\0';
 
 	EFI_PRINT("setting: %s=%s\n", native_name, val);
 
-	if (env_set(native_name, val))
+out:
+	if (env_set(native_name, val)) {
 		ret = EFI_DEVICE_ERROR;
+	} else {
+		if ((u16_strcmp(variable_name, L"PK") == 0 &&
+		     guidcmp(vendor, &efi_global_variable_guid) == 0)) {
+			ret = efi_transfer_secure_state(
+					(delete ? EFI_MODE_SETUP :
+						  EFI_MODE_USER));
+			if (ret != EFI_SUCCESS)
+				goto err;
+		}
+		ret = EFI_SUCCESS;
+	}
 
-out:
+err:
 	free(native_name);
+	free(odata);
 	free(val);
 
-	return EFI_EXIT(ret);
+	return ret;
+}
+
+static
+efi_status_t EFIAPI efi_set_volatile_variable(u16 *variable_name,
+					      const efi_guid_t *vendor,
+					      u32 attributes,
+					      efi_uintn_t data_size,
+					      const void *data,
+					      bool ro_check)
+{
+	return efi_set_variable_common(variable_name, vendor, attributes,
+				       data_size, data, ro_check, false);
+}
+
+efi_status_t EFIAPI efi_set_nonvolatile_variable(u16 *variable_name,
+						 const efi_guid_t *vendor,
+						 u32 attributes,
+						 efi_uintn_t data_size,
+						 const void *data,
+						 bool ro_check)
+{
+	efi_status_t ret;
+
+	ret = efi_set_variable_common(variable_name, vendor, attributes,
+				      data_size, data, ro_check, true);
+
+	return ret;
+}
+
+static efi_status_t efi_set_variable_internal(u16 *variable_name,
+					      const efi_guid_t *vendor,
+					      u32 attributes,
+					      efi_uintn_t data_size,
+					      const void *data,
+					      bool ro_check)
+{
+	efi_status_t ret;
+
+	if (attributes & EFI_VARIABLE_NON_VOLATILE)
+		ret = efi_set_nonvolatile_variable(variable_name, vendor,
+						   attributes,
+						   data_size, data, ro_check);
+	else
+		ret = efi_set_volatile_variable(variable_name, vendor,
+						attributes, data_size, data,
+						ro_check);
+
+	return ret;
+}
+
+/**
+ * efi_set_variable() - set value of a UEFI variable
+ *
+ * This function implements the SetVariable runtime service.
+ *
+ * See the Unified Extensible Firmware Interface (UEFI) specification for
+ * details.
+ *
+ * @variable_name:	name of the variable
+ * @vendor:		vendor GUID
+ * @attributes:		attributes of the variable
+ * @data_size:		size of the buffer with the variable value
+ * @data:		buffer with the variable value
+ * Return:		status code
+ */
+efi_status_t EFIAPI efi_set_variable(u16 *variable_name,
+				     const efi_guid_t *vendor, u32 attributes,
+				     efi_uintn_t data_size, const void *data)
+{
+	EFI_ENTRY("\"%ls\" %pUl %x %zu %p", variable_name, vendor, attributes,
+		  data_size, data);
+
+	/* READ_ONLY bit is not part of API */
+	attributes &= ~(u32)READ_ONLY;
+
+	return EFI_EXIT(efi_set_variable_internal(variable_name, vendor,
+						  attributes, data_size, data,
+						  true));
 }
 
 /**