| /* |
| * Copyright 2013 <James.Bottomley@HansenPartnership.com> |
| * |
| * see COPYING file |
| */ |
| |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <sys/types.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| |
| #include <openssl/x509.h> |
| #include <openssl/bio.h> |
| #include <openssl/err.h> |
| #include <openssl/pem.h> |
| |
| #define __STDC_VERSION__ 199901L |
| #include <efi.h> |
| |
| #include <kernel_efivars.h> |
| #include <openssl_sign.h> |
| #include <guid.h> |
| #include <sha256.h> |
| #include <version.h> |
| #include "efiauthenticated.h" |
| |
| #define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0])) |
| |
| static void |
| usage(const char *progname) |
| { |
| printf("Usage: %s: [-a] [-e] [-d <list>[-<entry>]] [-k <key>] [-g <guid>] [-b <file>|-f <file>|-c file] <var>\n", progname); |
| } |
| |
| static void |
| help(const char *progname) |
| { |
| usage(progname); |
| printf("Manipulate the UEFI key database via the efivarfs filesystem\n\n" |
| "Options:\n" |
| "\t-a\tappend a value to the variable instead of replacing it\n" |
| "\t-e\tuse EFI Signature List instead of signed update (only works in Setup Mode\n" |
| "\t-b <binfile>\tAdd hash of <binfile> to the signature list\n" |
| "\t-f <file>\tAdd or Replace the key file (.esl or .auth) to the <var>\n" |
| "\t-c <file>\tAdd or Replace the x509 certificate to the <var> (with <guid> if provided)\n" |
| "\t-g <guid>\tOptional <guid> for the X509 Certificate\n" |
| "\t-k <key>\tSecret key file for authorising User Mode updates\n" |
| "\t-d <list>[-<entry>]\tDelete the signature list <list> (or just a single <entry> within the list)\n" |
| "\t--engine <eng>\tUse engine <eng> for private key\n" |
| ); |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| char *variables[] = { "PK", "KEK", "db", "dbx" }; |
| char *signedby[] = { "PK", "PK", "KEK", "KEK" }; |
| char *engine = NULL; |
| EFI_GUID *owners[] = { &GV_GUID, &GV_GUID, &SIG_DB, &SIG_DB }; |
| EFI_GUID *owner, guid = MOK_OWNER; |
| int i, esl_mode = 0, fd, ret, delsig = -1, delentry = -1; |
| struct stat st; |
| uint32_t attributes = EFI_VARIABLE_NON_VOLATILE |
| | EFI_VARIABLE_RUNTIME_ACCESS |
| | EFI_VARIABLE_BOOTSERVICE_ACCESS |
| | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; |
| char *hash_mode = NULL, *file = NULL, *var, *progname = argv[0], *buf, |
| *name, *crt_file = NULL, *key_file = NULL; |
| |
| |
| while (argc > 1 && argv[1][0] == '-') { |
| if (strcmp("--version", argv[1]) == 0) { |
| version(progname); |
| exit(0); |
| } else if (strcmp("--help", argv[1]) == 0) { |
| help(progname); |
| exit(0); |
| } else if(strcmp(argv[1], "-a") == 0) { |
| attributes |= EFI_VARIABLE_APPEND_WRITE; |
| argv += 1; |
| argc -= 1; |
| } if (strcmp(argv[1], "-e") == 0) { |
| esl_mode = 1; |
| argv += 1; |
| argc -= 1; |
| } else if (strcmp(argv[1], "-b") == 0) { |
| hash_mode = argv[2]; |
| argv += 2; |
| argc -= 2; |
| } else if (strcmp(argv[1], "-f") == 0) { |
| file = argv[2]; |
| argv += 2; |
| argc -= 2; |
| } else if (strcmp(argv[1], "-g") == 0) { |
| if (str_to_guid(argv[2], &guid)) { |
| fprintf(stderr, "Invalid GUID %s\n", argv[2]); |
| exit(1); |
| } |
| argv += 2; |
| argc -= 2; |
| } else if (strcmp(argv[1], "-c") == 0) { |
| crt_file = argv[2]; |
| argv += 2; |
| argc -= 2; |
| } else if (strcmp(argv[1], "-k") == 0) { |
| key_file = argv[2]; |
| argv += 2; |
| argc -= 2; |
| } else if (strcmp(argv[1], "-d") == 0) { |
| sscanf(argv[2], "%d-%d", &delsig, &delentry); |
| argv += 2; |
| argc -= 2; |
| } else if (strcmp(argv[1], "--engine") == 0) { |
| engine = argv[2]; |
| argv += 2; |
| argc -= 2; |
| } else { |
| /* unrecognised option */ |
| break; |
| } |
| } |
| |
| if (argc != 2) { |
| usage(progname); |
| exit(1); |
| } |
| |
| var = argv[1]; |
| |
| for(i = 0; i < ARRAY_SIZE(variables); i++) { |
| if (strcmp(var, variables[i]) == 0) { |
| owner = owners[i]; |
| break; |
| } |
| } |
| if (i == ARRAY_SIZE(variables)) { |
| fprintf(stderr, "Invalid Variable %s\nVariable must be one of: ", var); |
| for (i = 0; i < ARRAY_SIZE(variables); i++) |
| fprintf(stderr, "%s ", variables[i]); |
| fprintf(stderr, "\n"); |
| exit(1); |
| } |
| |
| if (delsig == -1 && (!!file + !!hash_mode + !!crt_file != 1)) { |
| fprintf(stderr, "must specify exactly one of -f, -b or -c\n"); |
| exit(1); |
| } |
| |
| kernel_variable_init(); |
| ERR_load_crypto_strings(); |
| OpenSSL_add_all_digests(); |
| OpenSSL_add_all_ciphers(); |
| |
| name = file ? file : hash_mode; |
| if (delsig != -1) { |
| uint32_t len; |
| int status = get_variable_alloc(variables[i], owners[i], NULL, |
| &len, (uint8_t **)&buf); |
| if (status == ENOENT) { |
| fprintf(stderr, "Variable %s has no entries\n", variables[i]); |
| exit(1); |
| } |
| EFI_SIGNATURE_LIST *CertList = (EFI_SIGNATURE_LIST *)buf; |
| EFI_SIGNATURE_DATA *Cert; |
| int size, DataSize = len, count = 0; |
| |
| certlist_for_each_certentry(CertList, buf, size, DataSize) { |
| int Index = 0; |
| |
| if (count++ != delsig) |
| continue; |
| if (delentry == -1) |
| goto found; |
| certentry_for_each_cert(Cert, CertList) { |
| if (Index++ == delentry) |
| goto found; |
| } |
| } |
| if (delentry == -1) |
| fprintf(stderr, "signature %d does not exist in %s\n", delsig, variables[i]); |
| else |
| fprintf(stderr, "signature %d-%d does not exist in %s\n", delsig, delentry, variables[i]); |
| exit(1); |
| found: |
| ; |
| int certs = (CertList->SignatureListSize - sizeof(EFI_SIGNATURE_LIST) - CertList->SignatureHeaderSize) / CertList->SignatureSize; |
| if (certs == 1 || delentry == -1) { |
| /* delete entire sig list + data */ |
| DataSize -= CertList->SignatureListSize; |
| if (DataSize > 0) |
| memcpy(CertList, (void *) CertList + CertList->SignatureListSize, DataSize - ((char *) CertList - buf)); |
| } else { |
| int remain = DataSize - ((char *)Cert - buf) - CertList->SignatureSize; |
| /* only delete single sig */ |
| DataSize -= CertList->SignatureSize; |
| CertList->SignatureListSize -= CertList->SignatureSize; |
| if (remain > 0) |
| memcpy(Cert, (void *)Cert + CertList->SignatureSize, remain); |
| } |
| st.st_size = DataSize; /* reduce length of buf */ |
| esl_mode = 1; |
| } else if (name) { |
| fd = open(name, O_RDONLY); |
| if (fd < 0) { |
| fprintf(stderr, "Failed to read file %s: ", name); |
| perror(""); |
| exit(1); |
| } |
| if (fstat(fd, &st) < 0) { |
| perror("stat failed"); |
| exit(1); |
| } |
| buf = malloc(st.st_size); |
| read(fd, buf, st.st_size); |
| close(fd); |
| } else { |
| X509 *X = NULL; |
| BIO *bio; |
| char *crt_file_ext = &crt_file[strlen(crt_file) - 4]; |
| |
| esl_mode = 1; |
| |
| bio = BIO_new_file(crt_file, "r"); |
| if (!bio) { |
| fprintf(stderr, "Failed to load certificate from %s\n", crt_file); |
| ERR_print_errors_fp(stderr); |
| exit(1); |
| } |
| if (strcasecmp(crt_file_ext, ".der") == 0 |
| || strcasecmp(crt_file_ext, ".cer") == 0) |
| /* DER format */ |
| X = d2i_X509_bio(bio, NULL); |
| else |
| /* else assume PEM */ |
| X = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
| if (!X) { |
| fprintf(stderr, "Failed to load certificate from %s\n", crt_file); |
| ERR_print_errors_fp(stderr); |
| exit(1); |
| } |
| BIO_free_all(bio); |
| |
| int cert_len = i2d_X509(X, NULL); |
| cert_len += sizeof(EFI_SIGNATURE_LIST) + OFFSET_OF(EFI_SIGNATURE_DATA, SignatureData); |
| EFI_SIGNATURE_LIST *esl = malloc(cert_len); |
| unsigned char *tmp = (unsigned char *)esl + sizeof(EFI_SIGNATURE_LIST) + OFFSET_OF(EFI_SIGNATURE_DATA, SignatureData); |
| i2d_X509(X, &tmp); |
| esl->SignatureListSize = cert_len; |
| esl->SignatureSize = (cert_len - sizeof(EFI_SIGNATURE_LIST)); |
| esl->SignatureHeaderSize = 0; |
| esl->SignatureType = EFI_CERT_X509_GUID; |
| |
| EFI_SIGNATURE_DATA *sig_data = (void *)esl + sizeof(EFI_SIGNATURE_LIST); |
| |
| sig_data->SignatureOwner = guid; |
| |
| buf = (char *)esl; |
| st.st_size = cert_len; |
| } |
| |
| if (hash_mode) { |
| uint8_t hash[SHA256_DIGEST_SIZE]; |
| EFI_STATUS status; |
| int len; |
| |
| esl_mode = 1; |
| attributes |= EFI_VARIABLE_APPEND_WRITE; |
| status = sha256_get_pecoff_digest_mem(buf, st.st_size, hash); |
| free(buf); |
| if (status != EFI_SUCCESS) { |
| fprintf(stderr, "Failed to get hash of %s\n", name); |
| exit(1); |
| } |
| buf = (char *)hash_to_esl(&guid, &len, hash); |
| st.st_size = len; |
| } |
| |
| if (esl_mode && (!variable_is_setupmode() || strcmp(variables[i], "PK") == 0)) { |
| if (!key_file) { |
| fprintf(stderr, "Can't update variable%s without a key\n", variable_is_setupmode() ? "" : " in User Mode"); |
| exit(1); |
| } |
| EVP_PKEY *pkey = read_private_key(engine, key_file); |
| if (!pkey) { |
| fprintf(stderr, "error reading private key %s\n", key_file); |
| exit(1); |
| } |
| |
| uint8_t *esl; |
| uint32_t esl_len; |
| int ret = get_variable_alloc(signedby[i], &GV_GUID, NULL, |
| &esl_len, &esl); |
| if (ret != 0) { |
| fprintf(stderr, "Failed to get %s: ", signedby[i]); |
| perror(""); |
| exit(1); |
| } |
| EFI_SIGNATURE_LIST *CertList = (EFI_SIGNATURE_LIST *)esl; |
| int DataSize = esl_len, size; |
| |
| X509 *X = NULL; |
| |
| certlist_for_each_certentry(CertList, esl, size, DataSize) { |
| EFI_SIGNATURE_DATA *Cert; |
| if (compare_guid(&CertList->SignatureType, &X509_GUID) != 0) |
| continue; |
| |
| certentry_for_each_cert(Cert, CertList) { |
| const unsigned char *psig = (unsigned char *)Cert->SignatureData; |
| X = d2i_X509(NULL, &psig, CertList->SignatureSize); |
| if (X509_check_private_key(X, pkey)) |
| goto out; |
| X = NULL; |
| } |
| } |
| out: |
| if (!X) { |
| fprintf(stderr, "No public key matching %s in %s\n", key_file, signedby[i]); |
| exit (1); |
| } |
| |
| EFI_TIME timestamp; |
| time_t t; |
| unsigned char *tmp; |
| int sigsize; |
| |
| struct tm *tm; |
| memset(×tamp, 0, sizeof(timestamp)); |
| time(&t); |
| tm = gmtime(&t); |
| /* FIXME: currently timestamp is one year into future because of |
| * the way we set up the secure environment */ |
| timestamp.Year = tm->tm_year + 1900 + 1; |
| timestamp.Month = tm->tm_mon + 1; |
| timestamp.Day = tm->tm_mday; |
| timestamp.Hour = tm->tm_hour; |
| timestamp.Minute = tm->tm_min; |
| timestamp.Second = tm->tm_sec; |
| |
| /* signature is over variable name (no null and uc16 |
| * chars), the vendor GUID, the attributes, the |
| * timestamp and the contents */ |
| int signbuflen = strlen(var)*2 + sizeof(EFI_GUID) + sizeof(attributes) + sizeof(timestamp) + st.st_size; |
| char *signbuf = malloc(signbuflen); |
| char *ptr = signbuf; |
| int j; |
| for (j = 0; j < strlen(var); j++) { |
| *(ptr++) = var[j]; |
| *(ptr++) = 0; |
| } |
| memcpy(ptr, owners[i], sizeof(*owners[i])); |
| ptr += sizeof(*owners[i]); |
| memcpy(ptr, &attributes, sizeof(attributes)); |
| ptr += sizeof(attributes); |
| memcpy(ptr, ×tamp, sizeof(timestamp)); |
| ptr += sizeof(timestamp); |
| memcpy(ptr, buf, st.st_size); |
| |
| sign_efi_var_ssl(signbuf, signbuflen, pkey, X, &tmp, &sigsize); |
| |
| EFI_VARIABLE_AUTHENTICATION_2 *var_auth = malloc(sizeof(EFI_VARIABLE_AUTHENTICATION_2) + sigsize); |
| var_auth->TimeStamp = timestamp; |
| var_auth->AuthInfo.CertType = EFI_CERT_TYPE_PKCS7_GUID; |
| var_auth->AuthInfo.Hdr.dwLength = sigsize + OFFSET_OF(WIN_CERTIFICATE_UEFI_GUID, CertData); |
| var_auth->AuthInfo.Hdr.wRevision = 0x0200; |
| var_auth->AuthInfo.Hdr.wCertificateType = WIN_CERT_TYPE_EFI_GUID; |
| memcpy(var_auth->AuthInfo.CertData, tmp, sigsize); |
| ERR_print_errors_fp(stderr); |
| |
| /* new update now consists of two parts: the |
| * authentication header with the signature and the |
| * payload (the original esl) */ |
| int siglen = OFFSET_OF(EFI_VARIABLE_AUTHENTICATION_2, AuthInfo.CertData) + sigsize; |
| char *newbuf = malloc(siglen + st.st_size); |
| |
| memcpy(newbuf, var_auth, siglen); |
| memcpy(newbuf + siglen, buf, st.st_size); |
| |
| free(buf); |
| free(esl); |
| free(var_auth); |
| buf = newbuf; |
| st.st_size = siglen + st.st_size; |
| esl_mode = 0; |
| } |
| |
| if (esl_mode) { |
| ret = set_variable_esl(var, owner, attributes, st.st_size, buf); |
| } else { |
| ret = set_variable(var, owner, attributes, st.st_size, buf); |
| } |
| |
| if (ret == EACCES) { |
| fprintf(stderr, "Cannot write to %s, wrong filesystem permissions\n", var); |
| exit(1); |
| } else if (ret != 0) { |
| fprintf(stderr, "Failed to update %s: ", var); |
| perror(""); |
| exit(1); |
| } |
| |
| return 0; |
| } |
| |