+static efi_status_t efi_set_secure_state(int sec_boot, int setup_mode,
+ int audit_mode, int deployed_mode)
+{
+ u32 attributes;
+ efi_status_t ret;
+
+ attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS |
+ READ_ONLY;
+ ret = efi_set_variable_common(L"SecureBoot", &efi_global_variable_guid,
+ attributes, sizeof(sec_boot), &sec_boot,
+ false);
+ if (ret != EFI_SUCCESS)
+ goto err;
+
+ ret = efi_set_variable_common(L"SetupMode", &efi_global_variable_guid,
+ attributes, sizeof(setup_mode),
+ &setup_mode, false);
+ if (ret != EFI_SUCCESS)
+ goto err;
+
+ ret = efi_set_variable_common(L"AuditMode", &efi_global_variable_guid,
+ attributes, sizeof(audit_mode),
+ &audit_mode, false);
+ if (ret != EFI_SUCCESS)
+ goto err;
+
+ ret = efi_set_variable_common(L"DeployedMode",
+ &efi_global_variable_guid, attributes,
+ sizeof(deployed_mode), &deployed_mode,
+ false);
+err:
+ return ret;
+}
+
+/**
+ * efi_transfer_secure_state - handle a secure boot state transition
+ * @mode: new state
+ *
+ * Depending on @mode, secure boot related variables are updated.
+ * Those variables are *read-only* for users, efi_set_variable_common()
+ * is called here.
+ *
+ * Return: status code
+ */
+static efi_status_t efi_transfer_secure_state(enum efi_secure_mode mode)
+{
+ efi_status_t ret;
+
+ debug("Switching secure state from %d to %d\n", efi_secure_mode, mode);
+
+ if (mode == EFI_MODE_DEPLOYED) {
+ ret = efi_set_secure_state(1, 0, 0, 1);
+ if (ret != EFI_SUCCESS)
+ goto err;
+
+ efi_secure_boot = true;
+ } else if (mode == EFI_MODE_AUDIT) {
+ ret = efi_set_variable_common(L"PK", &efi_global_variable_guid,
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS,
+ 0, NULL, false);
+ if (ret != EFI_SUCCESS)
+ goto err;
+
+ ret = efi_set_secure_state(0, 1, 1, 0);
+ if (ret != EFI_SUCCESS)
+ goto err;
+
+ efi_secure_boot = true;
+ } else if (mode == EFI_MODE_USER) {
+ ret = efi_set_secure_state(1, 0, 0, 0);
+ if (ret != EFI_SUCCESS)
+ goto err;
+
+ efi_secure_boot = true;
+ } else if (mode == EFI_MODE_SETUP) {
+ ret = efi_set_secure_state(0, 1, 0, 0);
+ if (ret != EFI_SUCCESS)
+ goto err;
+ } else {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ efi_secure_mode = mode;
+
+ return EFI_SUCCESS;
+
+err:
+ /* TODO: What action should be taken here? */
+ printf("ERROR: Secure state transition failed\n");
+ return ret;
+}
+
+/**
+ * efi_init_secure_state - initialize secure boot state
+ *
+ * Return: status code
+ */
+static efi_status_t efi_init_secure_state(void)
+{
+ enum efi_secure_mode mode;
+ efi_uintn_t size;
+ efi_status_t ret;
+
+ /*
+ * TODO:
+ * Since there is currently no "platform-specific" installation
+ * method of Platform Key, we can't say if VendorKeys is 0 or 1
+ * precisely.
+ */
+
+ size = 0;
+ ret = efi_get_variable_common(L"PK", &efi_global_variable_guid,
+ NULL, &size, NULL);
+ if (ret == EFI_BUFFER_TOO_SMALL) {
+ if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT))
+ mode = EFI_MODE_USER;
+ else
+ mode = EFI_MODE_SETUP;
+
+ efi_vendor_keys = 0;
+ } else if (ret == EFI_NOT_FOUND) {
+ mode = EFI_MODE_SETUP;
+ efi_vendor_keys = 1;
+ } else {
+ goto err;
+ }
+
+ ret = efi_transfer_secure_state(mode);
+ if (ret == EFI_SUCCESS)
+ ret = efi_set_variable_common(L"VendorKeys",
+ &efi_global_variable_guid,
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS |
+ READ_ONLY,
+ sizeof(efi_vendor_keys),
+ &efi_vendor_keys, false);
+
+err:
+ return ret;
+}
+
+/**
+ * efi_secure_boot_enabled - return if secure boot is enabled or not
+ *
+ * Return: true if enabled, false if disabled
+ */
+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,
+};
+
+/**
+ * efi_variable_parse_signature - parse a signature in variable
+ * @buf: Pointer to variable's value
+ * @buflen: Length of @buf
+ *
+ * Parse a signature embedded in variable's value and instantiate
+ * a pkcs7_message structure. Since pkcs7_parse_message() accepts only
+ * pkcs7's signedData, some header needed be prepended for correctly
+ * parsing authentication data, particularly for variable's.
+ *
+ * Return: Pointer to pkcs7_message structure on success, NULL on error
+ */
+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
+ * TODO:
+ * The header should be composed in a more refined manner.
+ */
+ debug("Makeshift 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;
+}
+
+/**
+ * efi_variable_authenticate - authenticate a variable
+ * @variable: Variable name in u16
+ * @vendor: Guid of variable
+ * @data_size: Size of @data
+ * @data: Pointer to variable's value
+ * @given_attr: Attributes to be given at SetVariable()
+ * @env_attr: Attributes that an existing variable holds
+ * @time: signed time that an existing variable holds
+ *
+ * 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.
+ * On success, @data and @data_size will be replaced with variable's
+ * actual data, excluding authentication data, and its size, and variable's
+ * attributes and signed time will also be returned in @env_attr and @time,
+ * respectively.
+ *
+ * Return: status code
+ */
+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;
+ struct efi_signature_store *truststore, *truststore2;
+ struct pkcs7_message *var_sig;
+ struct efi_image_regions *regs;
+ struct efi_time timestamp;
+ struct rtc_time tm;
+ u64 new_time;
+ 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);
+
+ memcpy(×tamp, &auth->time_stamp, sizeof(timestamp));
+ 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;
+ new_time = rtc_mktime(&tm);
+
+ if (!efi_secure_boot_enabled()) {
+ /* finished checking */
+ *time = new_time;
+ return EFI_SUCCESS;
+ }
+
+ if (new_time <= *time)
+ goto err;
+
+ /* data to be digested */
+ regs = calloc(sizeof(*regs) + sizeof(struct image_region) * 5, 1);
+ if (!regs)
+ goto err;
+ regs->max = 5;
+ 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 *)×tamp,
+ (uint8_t *)×tamp + 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 (!var_sig) {
+ debug("Parsing variable's signature failed\n");
+ 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: support private authenticated variables */
+ goto err;
+ }
+
+ /* verify signature */
+ if (efi_signature_verify_with_sigdb(regs, var_sig, truststore, NULL)) {
+ debug("Verified\n");
+ } else {
+ if (truststore2 &&
+ efi_signature_verify_with_sigdb(regs, var_sig,
+ truststore2, NULL)) {
+ debug("Verified\n");
+ } 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 efi_get_variable_common(u16 *variable_name,
+ const efi_guid_t *vendor,
+ u32 *attributes,
+ efi_uintn_t *data_size, void *data)