diff options
author | Tom Rini <trini@konsulko.com> | 2019-12-06 16:45:46 -0500 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2019-12-06 16:45:46 -0500 |
commit | d79ae6aa3087a6434b5ecdb51d20dca20c8e1596 (patch) | |
tree | ef06de49134213591e529ece83d4cec3095e893e | |
parent | bead4f2f2c85e1bf39d2c80ef733f1325eb336bb (diff) | |
parent | fb013eee68d08403572ef3c579f6688bbe33fd47 (diff) |
Merge branch '2019-12-06-master-imports'
- Allow for the sysboot command, which is used to parse extlinux.conf
files to be used without PXE support. There is no functional change
here aside from fixing distro boot in a few cases where we actually
lacked the ability to parse the extlinux.conf file
- Add the x509/pkcs7 parsers from Linux, a pre-requisite to EFI Secure
Boot support.
52 files changed, 8643 insertions, 1598 deletions
@@ -88,6 +88,7 @@ config DISTRO_DEFAULTS select CMD_PART if PARTITIONS select CMD_PING if CMD_NET select CMD_PXE if NET + select CMD_SYSBOOT select ENV_VARS_UBOOT_CONFIG select HUSH_PARSER select SUPPORT_RAW_INITRD @@ -1889,6 +1889,7 @@ checkarmreloc: u-boot fi tools/version.h: include/version.h + $(Q)mkdir -p $(dir $@) $(call if_changed,copy) envtools: scripts_basic $(version_h) $(timestamp_h) tools/version.h @@ -1957,6 +1958,7 @@ clean: $(clean-dirs) -o -name '*.ko.*' -o -name '*.su' -o -name '*.pyc' \ -o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \ -o -name '*.lex.c' -o -name '*.tab.[ch]' \ + -o -name '*.asn1.[ch]' \ -o -name '*.symtypes' -o -name 'modules.order' \ -o -name modules.builtin -o -name '.tmp_*.o.*' \ -o -name 'dsdt.aml' -o -name 'dsdt.asl.tmp' -o -name 'dsdt.c' \ diff --git a/cmd/Kconfig b/cmd/Kconfig index 3b3722feb4..bc8318d7fa 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1625,6 +1625,7 @@ config CMD_LED config CMD_DATE bool "date" default y if DM_RTC + select LIB_DATE help Enable the 'date' command for getting/setting the time/date in RTC devices. @@ -1671,6 +1672,12 @@ config CMD_SOUND sound init - set up sound system sound play - play a sound +config CMD_SYSBOOT + bool "sysboot" + select MENU + help + Boot image via local extlinux.conf file + config CMD_QFW bool "qfw" select QFW diff --git a/cmd/Makefile b/cmd/Makefile index 36b3ceca39..e87c2f1625 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -111,7 +111,7 @@ ifdef CONFIG_PCI obj-$(CONFIG_CMD_PCI) += pci.o endif obj-$(CONFIG_CMD_PINMUX) += pinmux.o -obj-$(CONFIG_CMD_PXE) += pxe.o +obj-$(CONFIG_CMD_PXE) += pxe.o pxe_utils.o obj-$(CONFIG_CMD_WOL) += wol.o obj-$(CONFIG_CMD_QFW) += qfw.o obj-$(CONFIG_CMD_READ) += read.o @@ -130,6 +130,7 @@ obj-$(CONFIG_CMD_SETEXPR) += setexpr.o obj-$(CONFIG_CMD_SPI) += spi.o obj-$(CONFIG_CMD_STRINGS) += strings.o obj-$(CONFIG_CMD_SMC) += smccc.o +obj-$(CONFIG_CMD_SYSBOOT) += sysboot.o pxe_utils.o obj-$(CONFIG_CMD_TERMINAL) += terminal.o obj-$(CONFIG_CMD_TIME) += time.o obj-$(CONFIG_CMD_TRACE) += trace.o diff --git a/cmd/nvedit.c b/cmd/nvedit.c index b5da375913..3420e0b985 100644 --- a/cmd/nvedit.c +++ b/cmd/nvedit.c @@ -682,6 +682,23 @@ char *env_get(const char *name) } /* + * Like env_get, but prints an error if envvar isn't defined in the + * environment. It always returns what env_get does, so it can be used in + * place of env_get without changing error handling otherwise. + */ +char *from_env(const char *envvar) +{ + char *ret; + + ret = env_get(envvar); + + if (!ret) + printf("missing environment variable: %s\n", envvar); + + return ret; +} + +/* * Look up variable from environment for restricted C runtime env. */ int env_get_f(const char *name, char *buf, unsigned len) @@ -6,23 +6,10 @@ #include <common.h> #include <command.h> -#include <env.h> -#include <malloc.h> -#include <mapmem.h> -#include <lcd.h> -#include <linux/string.h> -#include <linux/ctype.h> -#include <errno.h> -#include <linux/list.h> -#include <fs.h> -#include <splash.h> -#include <asm/io.h> -#include "menu.h" -#include "cli.h" - -#define MAX_TFTP_PATH_LEN 127 +#include "pxe_utils.h" +#ifdef CONFIG_CMD_NET const char *pxe_default_paths[] = { #ifdef CONFIG_SYS_SOC #ifdef CONFIG_SYS_BOARD @@ -35,102 +22,6 @@ const char *pxe_default_paths[] = { NULL }; -static bool is_pxe; - -/* - * Like env_get, but prints an error if envvar isn't defined in the - * environment. It always returns what env_get does, so it can be used in - * place of env_get without changing error handling otherwise. - */ -static char *from_env(const char *envvar) -{ - char *ret; - - ret = env_get(envvar); - - if (!ret) - printf("missing environment variable: %s\n", envvar); - - return ret; -} - -#ifdef CONFIG_CMD_NET -/* - * Convert an ethaddr from the environment to the format used by pxelinux - * filenames based on mac addresses. Convert's ':' to '-', and adds "01-" to - * the beginning of the ethernet address to indicate a hardware type of - * Ethernet. Also converts uppercase hex characters into lowercase, to match - * pxelinux's behavior. - * - * Returns 1 for success, -ENOENT if 'ethaddr' is undefined in the - * environment, or some other value < 0 on error. - */ -static int format_mac_pxe(char *outbuf, size_t outbuf_len) -{ - uchar ethaddr[6]; - - if (outbuf_len < 21) { - printf("outbuf is too small (%zd < 21)\n", outbuf_len); - - return -EINVAL; - } - - if (!eth_env_get_enetaddr_by_index("eth", eth_get_dev_index(), ethaddr)) - return -ENOENT; - - sprintf(outbuf, "01-%02x-%02x-%02x-%02x-%02x-%02x", - ethaddr[0], ethaddr[1], ethaddr[2], - ethaddr[3], ethaddr[4], ethaddr[5]); - - return 1; -} -#endif - -/* - * Returns the directory the file specified in the bootfile env variable is - * in. If bootfile isn't defined in the environment, return NULL, which should - * be interpreted as "don't prepend anything to paths". - */ -static int get_bootfile_path(const char *file_path, char *bootfile_path, - size_t bootfile_path_size) -{ - char *bootfile, *last_slash; - size_t path_len = 0; - - /* Only syslinux allows absolute paths */ - if (file_path[0] == '/' && !is_pxe) - goto ret; - - bootfile = from_env("bootfile"); - - if (!bootfile) - goto ret; - - last_slash = strrchr(bootfile, '/'); - - if (last_slash == NULL) - goto ret; - - path_len = (last_slash - bootfile) + 1; - - if (bootfile_path_size < path_len) { - printf("bootfile_path too small. (%zd < %zd)\n", - bootfile_path_size, path_len); - - return -1; - } - - strncpy(bootfile_path, bootfile, path_len); - - ret: - bootfile_path[path_len] = '\0'; - - return 1; -} - -static int (*do_getfile)(cmd_tbl_t *cmdtp, const char *file_path, char *file_addr); - -#ifdef CONFIG_CMD_NET static int do_get_tftp(cmd_tbl_t *cmdtp, const char *file_path, char *file_addr) { char *tftp_argv[] = {"tftp", NULL, NULL, NULL}; @@ -143,156 +34,6 @@ static int do_get_tftp(cmd_tbl_t *cmdtp, const char *file_path, char *file_addr) return 1; } -#endif - -static char *fs_argv[5]; - -static int do_get_ext2(cmd_tbl_t *cmdtp, const char *file_path, char *file_addr) -{ -#ifdef CONFIG_CMD_EXT2 - fs_argv[0] = "ext2load"; - fs_argv[3] = file_addr; - fs_argv[4] = (void *)file_path; - - if (!do_ext2load(cmdtp, 0, 5, fs_argv)) - return 1; -#endif - return -ENOENT; -} - -static int do_get_fat(cmd_tbl_t *cmdtp, const char *file_path, char *file_addr) -{ -#ifdef CONFIG_CMD_FAT - fs_argv[0] = "fatload"; - fs_argv[3] = file_addr; - fs_argv[4] = (void *)file_path; - - if (!do_fat_fsload(cmdtp, 0, 5, fs_argv)) - return 1; -#endif - return -ENOENT; -} - -static int do_get_any(cmd_tbl_t *cmdtp, const char *file_path, char *file_addr) -{ -#ifdef CONFIG_CMD_FS_GENERIC - fs_argv[0] = "load"; - fs_argv[3] = file_addr; - fs_argv[4] = (void *)file_path; - - if (!do_load(cmdtp, 0, 5, fs_argv, FS_TYPE_ANY)) - return 1; -#endif - return -ENOENT; -} - -/* - * As in pxelinux, paths to files referenced from files we retrieve are - * relative to the location of bootfile. get_relfile takes such a path and - * joins it with the bootfile path to get the full path to the target file. If - * the bootfile path is NULL, we use file_path as is. - * - * Returns 1 for success, or < 0 on error. - */ -static int get_relfile(cmd_tbl_t *cmdtp, const char *file_path, - unsigned long file_addr) -{ - size_t path_len; - char relfile[MAX_TFTP_PATH_LEN+1]; - char addr_buf[18]; - int err; - - err = get_bootfile_path(file_path, relfile, sizeof(relfile)); - - if (err < 0) - return err; - - path_len = strlen(file_path); - path_len += strlen(relfile); - - if (path_len > MAX_TFTP_PATH_LEN) { - printf("Base path too long (%s%s)\n", - relfile, - file_path); - - return -ENAMETOOLONG; - } - - strcat(relfile, file_path); - - printf("Retrieving file: %s\n", relfile); - - sprintf(addr_buf, "%lx", file_addr); - - return do_getfile(cmdtp, relfile, addr_buf); -} - -/* - * Retrieve the file at 'file_path' to the locate given by 'file_addr'. If - * 'bootfile' was specified in the environment, the path to bootfile will be - * prepended to 'file_path' and the resulting path will be used. - * - * Returns 1 on success, or < 0 for error. - */ -static int get_pxe_file(cmd_tbl_t *cmdtp, const char *file_path, - unsigned long file_addr) -{ - unsigned long config_file_size; - char *tftp_filesize; - int err; - char *buf; - - err = get_relfile(cmdtp, file_path, file_addr); - - if (err < 0) - return err; - - /* - * the file comes without a NUL byte at the end, so find out its size - * and add the NUL byte. - */ - tftp_filesize = from_env("filesize"); - - if (!tftp_filesize) - return -ENOENT; - - if (strict_strtoul(tftp_filesize, 16, &config_file_size) < 0) - return -EINVAL; - - buf = map_sysmem(file_addr + config_file_size, 1); - *buf = '\0'; - unmap_sysmem(buf); - - return 1; -} - -#ifdef CONFIG_CMD_NET - -#define PXELINUX_DIR "pxelinux.cfg/" - -/* - * Retrieves a file in the 'pxelinux.cfg' folder. Since this uses get_pxe_file - * to do the hard work, the location of the 'pxelinux.cfg' folder is generated - * from the bootfile path, as described above. - * - * Returns 1 on success or < 0 on error. - */ -static int get_pxelinux_path(cmd_tbl_t *cmdtp, const char *file, - unsigned long pxefile_addr_r) -{ - size_t base_len = strlen(PXELINUX_DIR); - char path[MAX_TFTP_PATH_LEN+1]; - - if (base_len + strlen(file) > MAX_TFTP_PATH_LEN) { - printf("path (%s%s) too long, skipping\n", - PXELINUX_DIR, file); - return -ENAMETOOLONG; - } - - sprintf(path, PXELINUX_DIR "%s", file); - - return get_pxe_file(cmdtp, path, pxefile_addr_r); -} /* * Looks for a pxe file with a name based on the pxeuuid environment variable. @@ -355,7 +96,6 @@ static int pxe_ipaddr_paths(cmd_tbl_t *cmdtp, unsigned long pxefile_addr_r) return -ENOENT; } - /* * Entry point for the 'pxe get' command. * This Follows pxelinux's rules to download a config file from a tftp server. @@ -388,7 +128,7 @@ do_pxe_get(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) return 1; err = strict_strtoul(pxefile_addr_str, 16, - (unsigned long *)&pxefile_addr_r); + (unsigned long *)&pxefile_addr_r); if (err < 0) return 1; @@ -417,1224 +157,8 @@ do_pxe_get(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) return 1; } -#endif - -/* - * Wrapper to make it easier to store the file at file_path in the location - * specified by envaddr_name. file_path will be joined to the bootfile path, - * if any is specified. - * - * Returns 1 on success or < 0 on error. - */ -static int get_relfile_envaddr(cmd_tbl_t *cmdtp, const char *file_path, const char *envaddr_name) -{ - unsigned long file_addr; - char *envaddr; - - envaddr = from_env(envaddr_name); - - if (!envaddr) - return -ENOENT; - - if (strict_strtoul(envaddr, 16, &file_addr) < 0) - return -EINVAL; - - return get_relfile(cmdtp, file_path, file_addr); -} - -/* - * A note on the pxe file parser. - * - * We're parsing files that use syslinux grammar, which has a few quirks. - * String literals must be recognized based on context - there is no - * quoting or escaping support. There's also nothing to explicitly indicate - * when a label section completes. We deal with that by ending a label - * section whenever we see a line that doesn't include. - * - * As with the syslinux family, this same file format could be reused in the - * future for non pxe purposes. The only action it takes during parsing that - * would throw this off is handling of include files. It assumes we're using - * pxe, and does a tftp download of a file listed as an include file in the - * middle of the parsing operation. That could be handled by refactoring it to - * take a 'include file getter' function. - */ - -/* - * Describes a single label given in a pxe file. - * - * Create these with the 'label_create' function given below. - * - * name - the name of the menu as given on the 'menu label' line. - * kernel - the path to the kernel file to use for this label. - * append - kernel command line to use when booting this label - * initrd - path to the initrd to use for this label. - * attempted - 0 if we haven't tried to boot this label, 1 if we have. - * localboot - 1 if this label specified 'localboot', 0 otherwise. - * list - lets these form a list, which a pxe_menu struct will hold. - */ -struct pxe_label { - char num[4]; - char *name; - char *menu; - char *kernel; - char *config; - char *append; - char *initrd; - char *fdt; - char *fdtdir; - int ipappend; - int attempted; - int localboot; - int localboot_val; - struct list_head list; -}; - -/* - * Describes a pxe menu as given via pxe files. - * - * title - the name of the menu as given by a 'menu title' line. - * default_label - the name of the default label, if any. - * bmp - the bmp file name which is displayed in background - * timeout - time in tenths of a second to wait for a user key-press before - * booting the default label. - * prompt - if 0, don't prompt for a choice unless the timeout period is - * interrupted. If 1, always prompt for a choice regardless of - * timeout. - * labels - a list of labels defined for the menu. - */ -struct pxe_menu { - char *title; - char *default_label; - char *bmp; - int timeout; - int prompt; - struct list_head labels; -}; - -/* - * Allocates memory for and initializes a pxe_label. This uses malloc, so the - * result must be free()'d to reclaim the memory. - * - * Returns NULL if malloc fails. - */ -static struct pxe_label *label_create(void) -{ - struct pxe_label *label; - - label = malloc(sizeof(struct pxe_label)); - - if (!label) - return NULL; - - memset(label, 0, sizeof(struct pxe_label)); - - return label; -} - -/* - * Free the memory used by a pxe_label, including that used by its name, - * kernel, append and initrd members, if they're non NULL. - * - * So - be sure to only use dynamically allocated memory for the members of - * the pxe_label struct, unless you want to clean it up first. These are - * currently only created by the pxe file parsing code. - */ -static void label_destroy(struct pxe_label *label) -{ - if (label->name) - free(label->name); - - if (label->kernel) - free(label->kernel); - - if (label->config) - free(label->config); - - if (label->append) - free(label->append); - - if (label->initrd) - free(label->initrd); - - if (label->fdt) - free(label->fdt); - - if (label->fdtdir) - free(label->fdtdir); - - free(label); -} - -/* - * Print a label and its string members if they're defined. - * - * This is passed as a callback to the menu code for displaying each - * menu entry. - */ -static void label_print(void *data) -{ - struct pxe_label *label = data; - const char *c = label->menu ? label->menu : label->name; - - printf("%s:\t%s\n", label->num, c); -} - -/* - * Boot a label that specified 'localboot'. This requires that the 'localcmd' - * environment variable is defined. Its contents will be executed as U-Boot - * command. If the label specified an 'append' line, its contents will be - * used to overwrite the contents of the 'bootargs' environment variable prior - * to running 'localcmd'. - * - * Returns 1 on success or < 0 on error. - */ -static int label_localboot(struct pxe_label *label) -{ - char *localcmd; - - localcmd = from_env("localcmd"); - - if (!localcmd) - return -ENOENT; - - if (label->append) { - char bootargs[CONFIG_SYS_CBSIZE]; - - cli_simple_process_macros(label->append, bootargs); - env_set("bootargs", bootargs); - } - - debug("running: %s\n", localcmd); - - return run_command_list(localcmd, strlen(localcmd), 0); -} - -/* - * Boot according to the contents of a pxe_label. - * - * If we can't boot for any reason, we return. A successful boot never - * returns. - * - * The kernel will be stored in the location given by the 'kernel_addr_r' - * environment variable. - * - * If the label specifies an initrd file, it will be stored in the location - * given by the 'ramdisk_addr_r' environment variable. - * - * If the label specifies an 'append' line, its contents will overwrite that - * of the 'bootargs' environment variable. - */ -static int label_boot(cmd_tbl_t *cmdtp, struct pxe_label *label) -{ - char *bootm_argv[] = { "bootm", NULL, NULL, NULL, NULL }; - char initrd_str[28]; - char mac_str[29] = ""; - char ip_str[68] = ""; - char *fit_addr = NULL; - int bootm_argc = 2; - int len = 0; - ulong kernel_addr; - void *buf; - - label_print(label); - - label->attempted = 1; - - if (label->localboot) { - if (label->localboot_val >= 0) - label_localboot(label); - return 0; - } - - if (label->kernel == NULL) { - printf("No kernel given, skipping %s\n", - label->name); - return 1; - } - - if (label->initrd) { - if (get_relfile_envaddr(cmdtp, label->initrd, "ramdisk_addr_r") < 0) { - printf("Skipping %s for failure retrieving initrd\n", - label->name); - return 1; - } - - bootm_argv[2] = initrd_str; - strncpy(bootm_argv[2], env_get("ramdisk_addr_r"), 18); - strcat(bootm_argv[2], ":"); - strncat(bootm_argv[2], env_get("filesize"), 9); - bootm_argc = 3; - } - - if (get_relfile_envaddr(cmdtp, label->kernel, "kernel_addr_r") < 0) { - printf("Skipping %s for failure retrieving kernel\n", - label->name); - return 1; - } - - if (label->ipappend & 0x1) { - sprintf(ip_str, " ip=%s:%s:%s:%s", - env_get("ipaddr"), env_get("serverip"), - env_get("gatewayip"), env_get("netmask")); - } - -#ifdef CONFIG_CMD_NET - if (label->ipappend & 0x2) { - int err; - strcpy(mac_str, " BOOTIF="); - err = format_mac_pxe(mac_str + 8, sizeof(mac_str) - 8); - if (err < 0) - mac_str[0] = '\0'; - } -#endif - - if ((label->ipappend & 0x3) || label->append) { - char bootargs[CONFIG_SYS_CBSIZE] = ""; - char finalbootargs[CONFIG_SYS_CBSIZE]; - - if (strlen(label->append ?: "") + - strlen(ip_str) + strlen(mac_str) + 1 > sizeof(bootargs)) { - printf("bootarg overflow %zd+%zd+%zd+1 > %zd\n", - strlen(label->append ?: ""), - strlen(ip_str), strlen(mac_str), - sizeof(bootargs)); - return 1; - } else { - if (label->append) - strncpy(bootargs, label->append, - sizeof(bootargs)); - strcat(bootargs, ip_str); - strcat(bootargs, mac_str); - - cli_simple_process_macros(bootargs, finalbootargs); - env_set("bootargs", finalbootargs); - printf("append: %s\n", finalbootargs); - } - } - - bootm_argv[1] = env_get("kernel_addr_r"); - /* for FIT, append the configuration identifier */ - if (label->config) { - int len = strlen(bootm_argv[1]) + strlen(label->config) + 1; - - fit_addr = malloc(len); - if (!fit_addr) { - printf("malloc fail (FIT address)\n"); - return 1; - } - snprintf(fit_addr, len, "%s%s", bootm_argv[1], label->config); - bootm_argv[1] = fit_addr; - } - - /* - * fdt usage is optional: - * It handles the following scenarios. All scenarios are exclusive - * - * Scenario 1: If fdt_addr_r specified and "fdt" label is defined in - * pxe file, retrieve fdt blob from server. Pass fdt_addr_r to bootm, - * and adjust argc appropriately. - * - * Scenario 2: If there is an fdt_addr specified, pass it along to - * bootm, and adjust argc appropriately. - * - * Scenario 3: fdt blob is not available. - */ - bootm_argv[3] = env_get("fdt_addr_r"); - - /* if fdt label is defined then get fdt from server */ - if (bootm_argv[3]) { - char *fdtfile = NULL; - char *fdtfilefree = NULL; - - if (label->fdt) { - fdtfile = label->fdt; - } else if (label->fdtdir) { - char *f1, *f2, *f3, *f4, *slash; - - f1 = env_get("fdtfile"); - if (f1) { - f2 = ""; - f3 = ""; - f4 = ""; - } else { - /* - * For complex cases where this code doesn't - * generate the correct filename, the board - * code should set $fdtfile during early boot, - * or the boot scripts should set $fdtfile - * before invoking "pxe" or "sysboot". - */ - f1 = env_get("soc"); - f2 = "-"; - f3 = env_get("board"); - f4 = ".dtb"; - } - - len = strlen(label->fdtdir); - if (!len) - slash = "./"; - else if (label->fdtdir[len - 1] != '/') - slash = "/"; - else - slash = ""; - - len = strlen(label->fdtdir) + strlen(slash) + - strlen(f1) + strlen(f2) + strlen(f3) + - strlen(f4) + 1; - fdtfilefree = malloc(len); - if (!fdtfilefree) { - printf("malloc fail (FDT filename)\n"); - goto cleanup; - } - - snprintf(fdtfilefree, len, "%s%s%s%s%s%s", - label->fdtdir, slash, f1, f2, f3, f4); - fdtfile = fdtfilefree; - } - - if (fdtfile) { - int err = get_relfile_envaddr(cmdtp, fdtfile, "fdt_addr_r"); - free(fdtfilefree); - if (err < 0) { - printf("Skipping %s for failure retrieving fdt\n", - label->name); - goto cleanup; - } - } else { - bootm_argv[3] = NULL; - } - } - - if (!bootm_argv[3]) - bootm_argv[3] = env_get("fdt_addr"); - - if (bootm_argv[3]) { - if (!bootm_argv[2]) - bootm_argv[2] = "-"; - bootm_argc = 4; - } - - kernel_addr = genimg_get_kernel_addr(bootm_argv[1]); - buf = map_sysmem(kernel_addr, 0); - /* Try bootm for legacy and FIT format image */ - if (genimg_get_format(buf) != IMAGE_FORMAT_INVALID) - do_bootm(cmdtp, 0, bootm_argc, bootm_argv); -#ifdef CONFIG_CMD_BOOTI - /* Try booting an AArch64 Linux kernel image */ - else - do_booti(cmdtp, 0, bootm_argc, bootm_argv); -#elif defined(CONFIG_CMD_BOOTZ) - /* Try booting a Image */ - else - do_bootz(cmdtp, 0, bootm_argc, bootm_argv); -#endif - unmap_sysmem(buf); - -cleanup: - if (fit_addr) - free(fit_addr); - return 1; -} - -/* - * Tokens for the pxe file parser. - */ -enum token_type { - T_EOL, - T_STRING, - T_EOF, - T_MENU, - T_TITLE, - T_TIMEOUT, - T_LABEL, - T_KERNEL, - T_LINUX, - T_APPEND, - T_INITRD, - T_LOCALBOOT, - T_DEFAULT, - T_PROMPT, - T_INCLUDE, - T_FDT, - T_FDTDIR, - T_ONTIMEOUT, - T_IPAPPEND, - T_BACKGROUND, - T_INVALID -}; - -/* - * A token - given by a value and a type. - */ -struct token { - char *val; - enum token_type type; -}; - -/* - * Keywords recognized. - */ -static const struct token keywords[] = { - {"menu", T_MENU}, - {"title", T_TITLE}, - {"timeout", T_TIMEOUT}, - {"default", T_DEFAULT}, - {"prompt", T_PROMPT}, - {"label", T_LABEL}, - {"kernel", T_KERNEL}, - {"linux", T_LINUX}, - {"localboot", T_LOCALBOOT}, - {"append", T_APPEND}, - {"initrd", T_INITRD}, - {"include", T_INCLUDE}, - {"devicetree", T_FDT}, - {"fdt", T_FDT}, - {"devicetreedir", T_FDTDIR}, - {"fdtdir", T_FDTDIR}, - {"ontimeout", T_ONTIMEOUT,}, - {"ipappend", T_IPAPPEND,}, - {"background", T_BACKGROUND,}, - {NULL, T_INVALID} -}; - -/* - * Since pxe(linux) files don't have a token to identify the start of a - * literal, we have to keep track of when we're in a state where a literal is - * expected vs when we're in a state a keyword is expected. - */ -enum lex_state { - L_NORMAL = 0, - L_KEYWORD, - L_SLITERAL -}; - -/* - * get_string retrieves a string from *p and stores it as a token in - * *t. - * - * get_string used for scanning both string literals and keywords. - * - * Characters from *p are copied into t-val until a character equal to - * delim is found, or a NUL byte is reached. If delim has the special value of - * ' ', any whitespace character will be used as a delimiter. - * - * If lower is unequal to 0, uppercase characters will be converted to - * lowercase in the result. This is useful to make keywords case - * insensitive. - * - * The location of *p is updated to point to the first character after the end - * of the token - the ending delimiter. - * - * On success, the new value of t->val is returned. Memory for t->val is - * allocated using malloc and must be free()'d to reclaim it. If insufficient - * memory is available, NULL is returned. - */ -static char *get_string(char **p, struct token *t, char delim, int lower) -{ - char *b, *e; - size_t len, i; - - /* - * b and e both start at the beginning of the input stream. - * - * e is incremented until we find the ending delimiter, or a NUL byte - * is reached. Then, we take e - b to find the length of the token. - */ - b = e = *p; - - while (*e) { - if ((delim == ' ' && isspace(*e)) || delim == *e) - break; - e++; - } - - len = e - b; - - /* - * Allocate memory to hold the string, and copy it in, converting - * characters to lowercase if lower is != 0. - */ - t->val = malloc(len + 1); - if (!t->val) - return NULL; - - for (i = 0; i < len; i++, b++) { - if (lower) - t->val[i] = tolower(*b); - else - t->val[i] = *b; - } - - t->val[len] = '\0'; - - /* - * Update *p so the caller knows where to continue scanning. - */ - *p = e; - - t->type = T_STRING; - - return t->val; -} - -/* - * Populate a keyword token with a type and value. - */ -static void get_keyword(struct token *t) -{ - int i; - - for (i = 0; keywords[i].val; i++) { - if (!strcmp(t->val, keywords[i].val)) { - t->type = keywords[i].type; - break; - } - } -} - -/* - * Get the next token. We have to keep track of which state we're in to know - * if we're looking to get a string literal or a keyword. - * - * *p is updated to point at the first character after the current token. - */ -static void get_token(char **p, struct token *t, enum lex_state state) -{ - char *c = *p; - - t->type = T_INVALID; - - /* eat non EOL whitespace */ - while (isblank(*c)) - c++; - - /* - * eat comments. note that string literals can't begin with #, but - * can contain a # after their first character. - */ - if (*c == '#') { - while (*c && *c != '\n') - c++; - } - - if (*c == '\n') { - t->type = T_EOL; - c++; - } else if (*c == '\0') { - t->type = T_EOF; - c++; - } else if (state == L_SLITERAL) { - get_string(&c, t, '\n', 0); - } else if (state == L_KEYWORD) { - /* - * when we expect a keyword, we first get the next string - * token delimited by whitespace, and then check if it - * matches a keyword in our keyword list. if it does, it's - * converted to a keyword token of the appropriate type, and - * if not, it remains a string token. - */ - get_string(&c, t, ' ', 1); - get_keyword(t); - } - - *p = c; -} - -/* - * Increment *c until we get to the end of the current line, or EOF. - */ -static void eol_or_eof(char **c) -{ - while (**c && **c != '\n') - (*c)++; -} - -/* - * All of these parse_* functions share some common behavior. - * - * They finish with *c pointing after the token they parse, and return 1 on - * success, or < 0 on error. - */ - -/* - * Parse a string literal and store a pointer it at *dst. String literals - * terminate at the end of the line. - */ -static int parse_sliteral(char **c, char **dst) -{ - struct token t; - char *s = *c; - - get_token(c, &t, L_SLITERAL); - - if (t.type != T_STRING) { - printf("Expected string literal: %.*s\n", (int)(*c - s), s); - return -EINVAL; - } - - *dst = t.val; - - return 1; -} - -/* - * Parse a base 10 (unsigned) integer and store it at *dst. - */ -static int parse_integer(char **c, int *dst) -{ - struct token t; - char *s = *c; - - get_token(c, &t, L_SLITERAL); - - if (t.type != T_STRING) { - printf("Expected string: %.*s\n", (int)(*c - s), s); - return -EINVAL; - } - - *dst = simple_strtol(t.val, NULL, 10); - - free(t.val); - - return 1; -} - -static int parse_pxefile_top(cmd_tbl_t *cmdtp, char *p, unsigned long base, - struct pxe_menu *cfg, int nest_level); - -/* - * Parse an include statement, and retrieve and parse the file it mentions. - * - * base should point to a location where it's safe to store the file, and - * nest_level should indicate how many nested includes have occurred. For this - * include, nest_level has already been incremented and doesn't need to be - * incremented here. - */ -static int handle_include(cmd_tbl_t *cmdtp, char **c, unsigned long base, - struct pxe_menu *cfg, int nest_level) -{ - char *include_path; - char *s = *c; - int err; - char *buf; - int ret; - - err = parse_sliteral(c, &include_path); - - if (err < 0) { - printf("Expected include path: %.*s\n", - (int)(*c - s), s); - return err; - } - - err = get_pxe_file(cmdtp, include_path, base); - - if (err < 0) { - printf("Couldn't retrieve %s\n", include_path); - return err; - } - - buf = map_sysmem(base, 0); - ret = parse_pxefile_top(cmdtp, buf, base, cfg, nest_level); - unmap_sysmem(buf); - - return ret; -} - -/* - * Parse lines that begin with 'menu'. - * - * base and nest are provided to handle the 'menu include' case. - * - * base should point to a location where it's safe to store the included file. - * - * nest_level should be 1 when parsing the top level pxe file, 2 when parsing - * a file it includes, 3 when parsing a file included by that file, and so on. - */ -static int parse_menu(cmd_tbl_t *cmdtp, char **c, struct pxe_menu *cfg, - unsigned long base, int nest_level) -{ - struct token t; - char *s = *c; - int err = 0; - - get_token(c, &t, L_KEYWORD); - - switch (t.type) { - case T_TITLE: - err = parse_sliteral(c, &cfg->title); - - break; - - case T_INCLUDE: - err = handle_include(cmdtp, c, base, cfg, - nest_level + 1); - break; - - case T_BACKGROUND: - err = parse_sliteral(c, &cfg->bmp); - break; - - default: - printf("Ignoring malformed menu command: %.*s\n", - (int)(*c - s), s); - } - - if (err < 0) - return err; - - eol_or_eof(c); - - return 1; -} /* - * Handles parsing a 'menu line' when we're parsing a label. - */ -static int parse_label_menu(char **c, struct pxe_menu *cfg, - struct pxe_label *label) -{ - struct token t; - char *s; - - s = *c; - - get_token(c, &t, L_KEYWORD); - - switch (t.type) { - case T_DEFAULT: - if (!cfg->default_label) - cfg->default_label = strdup(label->name); - - if (!cfg->default_label) - return -ENOMEM; - - break; - case T_LABEL: - parse_sliteral(c, &label->menu); - break; - default: - printf("Ignoring malformed menu command: %.*s\n", - (int)(*c - s), s); - } - - eol_or_eof(c); - - return 0; -} - -/* - * Handles parsing a 'kernel' label. - * expecting "filename" or "<fit_filename>#cfg" - */ -static int parse_label_kernel(char **c, struct pxe_label *label) -{ - char *s; - int err; - - err = parse_sliteral(c, &label->kernel); - if (err < 0) - return err; - - s = strstr(label->kernel, "#"); - if (!s) - return 1; - - label->config = malloc(strlen(s) + 1); - if (!label->config) - return -ENOMEM; - - strcpy(label->config, s); - *s = 0; - - return 1; -} - -/* - * Parses a label and adds it to the list of labels for a menu. - * - * A label ends when we either get to the end of a file, or - * get some input we otherwise don't have a handler defined - * for. - * - */ -static int parse_label(char **c, struct pxe_menu *cfg) -{ - struct token t; - int len; - char *s = *c; - struct pxe_label *label; - int err; - - label = label_create(); - if (!label) - return -ENOMEM; - - err = parse_sliteral(c, &label->name); - if (err < 0) { - printf("Expected label name: %.*s\n", (int)(*c - s), s); - label_destroy(label); - return -EINVAL; - } - - list_add_tail(&label->list, &cfg->labels); - - while (1) { - s = *c; - get_token(c, &t, L_KEYWORD); - - err = 0; - switch (t.type) { - case T_MENU: - err = parse_label_menu(c, cfg, label); - break; - - case T_KERNEL: - case T_LINUX: - err = parse_label_kernel(c, label); - break; - - case T_APPEND: - err = parse_sliteral(c, &label->append); - if (label->initrd) - break; - s = strstr(label->append, "initrd="); - if (!s) - break; - s += 7; - len = (int)(strchr(s, ' ') - s); - label->initrd = malloc(len + 1); - strncpy(label->initrd, s, len); - label->initrd[len] = '\0'; - - break; - - case T_INITRD: - if (!label->initrd) - err = parse_sliteral(c, &label->initrd); - break; - - case T_FDT: - if (!label->fdt) - err = parse_sliteral(c, &label->fdt); - break; - - case T_FDTDIR: - if (!label->fdtdir) - err = parse_sliteral(c, &label->fdtdir); - break; - - case T_LOCALBOOT: - label->localboot = 1; - err = parse_integer(c, &label->localboot_val); - break; - - case T_IPAPPEND: - err = parse_integer(c, &label->ipappend); - break; - - case T_EOL: - break; - - default: - /* - * put the token back! we don't want it - it's the end - * of a label and whatever token this is, it's - * something for the menu level context to handle. - */ - *c = s; - return 1; - } - - if (err < 0) - return err; - } -} - -/* - * This 16 comes from the limit pxelinux imposes on nested includes. - * - * There is no reason at all we couldn't do more, but some limit helps prevent - * infinite (until crash occurs) recursion if a file tries to include itself. - */ -#define MAX_NEST_LEVEL 16 - -/* - * Entry point for parsing a menu file. nest_level indicates how many times - * we've nested in includes. It will be 1 for the top level menu file. - * - * Returns 1 on success, < 0 on error. - */ -static int parse_pxefile_top(cmd_tbl_t *cmdtp, char *p, unsigned long base, - struct pxe_menu *cfg, int nest_level) -{ - struct token t; - char *s, *b, *label_name; - int err; - - b = p; - - if (nest_level > MAX_NEST_LEVEL) { - printf("Maximum nesting (%d) exceeded\n", MAX_NEST_LEVEL); - return -EMLINK; - } - - while (1) { - s = p; - - get_token(&p, &t, L_KEYWORD); - - err = 0; - switch (t.type) { - case T_MENU: - cfg->prompt = 1; - err = parse_menu(cmdtp, &p, cfg, - base + ALIGN(strlen(b) + 1, 4), - nest_level); - break; - - case T_TIMEOUT: - err = parse_integer(&p, &cfg->timeout); - break; - - case T_LABEL: - err = parse_label(&p, cfg); - break; - - case T_DEFAULT: - case T_ONTIMEOUT: - err = parse_sliteral(&p, &label_name); - - if (label_name) { - if (cfg->default_label) - free(cfg->default_label); - - cfg->default_label = label_name; - } - - break; - - case T_INCLUDE: - err = handle_include(cmdtp, &p, - base + ALIGN(strlen(b), 4), cfg, - nest_level + 1); - break; - - case T_PROMPT: - eol_or_eof(&p); - break; - - case T_EOL: - break; - - case T_EOF: - return 1; - - default: - printf("Ignoring unknown command: %.*s\n", - (int)(p - s), s); - eol_or_eof(&p); - } - - if (err < 0) - return err; - } -} - -/* - * Free the memory used by a pxe_menu and its labels. - */ -static void destroy_pxe_menu(struct pxe_menu *cfg) -{ - struct list_head *pos, *n; - struct pxe_label *label; - - if (cfg->title) - free(cfg->title); - - if (cfg->default_label) - free(cfg->default_label); - - list_for_each_safe(pos, n, &cfg->labels) { - label = list_entry(pos, struct pxe_label, list); - - label_destroy(label); - } - - free(cfg); -} - -/* - * Entry point for parsing a pxe file. This is only used for the top level - * file. - * - * Returns NULL if there is an error, otherwise, returns a pointer to a - * pxe_menu struct populated with the results of parsing the pxe file (and any - * files it includes). The resulting pxe_menu struct can be free()'d by using - * the destroy_pxe_menu() function. - */ -static struct pxe_menu *parse_pxefile(cmd_tbl_t *cmdtp, unsigned long menucfg) -{ - struct pxe_menu *cfg; - char *buf; - int r; - - cfg = malloc(sizeof(struct pxe_menu)); - - if (!cfg) - return NULL; - - memset(cfg, 0, sizeof(struct pxe_menu)); - - INIT_LIST_HEAD(&cfg->labels); - - buf = map_sysmem(menucfg, 0); - r = parse_pxefile_top(cmdtp, buf, menucfg, cfg, 1); - unmap_sysmem(buf); - - if (r < 0) { - destroy_pxe_menu(cfg); - return NULL; - } - - return cfg; -} - -/* - * Converts a pxe_menu struct into a menu struct for use with U-Boot's generic - * menu code. - */ -static struct menu *pxe_menu_to_menu(struct pxe_menu *cfg) -{ - struct pxe_label *label; - struct list_head *pos; - struct menu *m; - int err; - int i = 1; - char *default_num = NULL; - - /* - * Create a menu and add items for all the labels. - */ - m = menu_create(cfg->title, DIV_ROUND_UP(cfg->timeout, 10), - cfg->prompt, label_print, NULL, NULL); - - if (!m) - return NULL; - - list_for_each(pos, &cfg->labels) { - label = list_entry(pos, struct pxe_label, list); - - sprintf(label->num, "%d", i++); - if (menu_item_add(m, label->num, label) != 1) { - menu_destroy(m); - return NULL; - } - if (cfg->default_label && - (strcmp(label->name, cfg->default_label) == 0)) - default_num = label->num; - - } - - /* - * After we've created items for each label in the menu, set the - * menu's default label if one was specified. - */ - if (default_num) { - err = menu_default_set(m, default_num); - if (err != 1) { - if (err != -ENOENT) { - menu_destroy(m); - return NULL; - } - - printf("Missing default: %s\n", cfg->default_label); - } - } - - return m; -} - -/* - * Try to boot any labels we have yet to attempt to boot. - */ -static void boot_unattempted_labels(cmd_tbl_t *cmdtp, struct pxe_menu *cfg) -{ - struct list_head *pos; - struct pxe_label *label; - - list_for_each(pos, &cfg->labels) { - label = list_entry(pos, struct pxe_label, list); - - if (!label->attempted) - label_boot(cmdtp, label); - } -} - -/* - * Boot the system as prescribed by a pxe_menu. - * - * Use the menu system to either get the user's choice or the default, based - * on config or user input. If there is no default or user's choice, - * attempted to boot labels in the order they were given in pxe files. - * If the default or user's choice fails to boot, attempt to boot other - * labels in the order they were given in pxe files. - * - * If this function returns, there weren't any labels that successfully - * booted, or the user interrupted the menu selection via ctrl+c. - */ -static void handle_pxe_menu(cmd_tbl_t *cmdtp, struct pxe_menu *cfg) -{ - void *choice; - struct menu *m; - int err; - -#ifdef CONFIG_CMD_BMP - /* display BMP if available */ - if (cfg->bmp) { - if (get_relfile(cmdtp, cfg->bmp, load_addr)) { - run_command("cls", 0); - bmp_display(load_addr, - BMP_ALIGN_CENTER, BMP_ALIGN_CENTER); - } else { - printf("Skipping background bmp %s for failure\n", - cfg->bmp); - } - } -#endif - - m = pxe_menu_to_menu(cfg); - if (!m) - return; - - err = menu_get_choice(m, &choice); - - menu_destroy(m); - - /* - * err == 1 means we got a choice back from menu_get_choice. - * - * err == -ENOENT if the menu was setup to select the default but no - * default was set. in that case, we should continue trying to boot - * labels that haven't been attempted yet. - * - * otherwise, the user interrupted or there was some other error and - * we give up. - */ - - if (err == 1) { - err = label_boot(cmdtp, choice); - if (!err) - return; - } else if (err != -ENOENT) { - return; - } - - boot_unattempted_labels(cmdtp, cfg); -} - -#ifdef CONFIG_CMD_NET -/* * Boots a system using a pxe file * * Returns 0 on success, 1 on error. @@ -1666,7 +190,7 @@ do_pxe_boot(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) cfg = parse_pxefile(cmdtp, pxefile_addr_r); - if (cfg == NULL) { + if (!cfg) { printf("Error parsing config file\n"); return 1; } @@ -1706,97 +230,9 @@ static int do_pxe(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) return CMD_RET_USAGE; } -U_BOOT_CMD( - pxe, 3, 1, do_pxe, - "commands to get and boot from pxe files", - "get - try to retrieve a pxe file using tftp\npxe " - "boot [pxefile_addr_r] - boot from the pxe file at pxefile_addr_r\n" +U_BOOT_CMD(pxe, 3, 1, do_pxe, + "commands to get and boot from pxe files", + "get - try to retrieve a pxe file using tftp\n" + "pxe boot [pxefile_addr_r] - boot from the pxe file at pxefile_addr_r\n" ); #endif - -/* - * Boots a system using a local disk syslinux/extlinux file - * - * Returns 0 on success, 1 on error. - */ -static int do_sysboot(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) -{ - unsigned long pxefile_addr_r; - struct pxe_menu *cfg; - char *pxefile_addr_str; - char *filename; - int prompt = 0; - - is_pxe = false; - - if (argc > 1 && strstr(argv[1], "-p")) { - prompt = 1; - argc--; - argv++; - } - - if (argc < 4) - return cmd_usage(cmdtp); - - if (argc < 5) { - pxefile_addr_str = from_env("pxefile_addr_r"); - if (!pxefile_addr_str) - return 1; - } else { - pxefile_addr_str = argv[4]; - } - - if (argc < 6) - filename = env_get("bootfile"); - else { - filename = argv[5]; - env_set("bootfile", filename); - } - - if (strstr(argv[3], "ext2")) - do_getfile = do_get_ext2; - else if (strstr(argv[3], "fat")) - do_getfile = do_get_fat; - else if (strstr(argv[3], "any")) - do_getfile = do_get_any; - else { - printf("Invalid filesystem: %s\n", argv[3]); - return 1; - } - fs_argv[1] = argv[1]; - fs_argv[2] = argv[2]; - - if (strict_strtoul(pxefile_addr_str, 16, &pxefile_addr_r) < 0) { - printf("Invalid pxefile address: %s\n", pxefile_addr_str); - return 1; - } - - if (get_pxe_file(cmdtp, filename, pxefile_addr_r) < 0) { - printf("Error reading config file\n"); - return 1; - } - - cfg = parse_pxefile(cmdtp, pxefile_addr_r); - - if (cfg == NULL) { - printf("Error parsing config file\n"); - return 1; - } - - if (prompt) - cfg->prompt = 1; - - handle_pxe_menu(cmdtp, cfg); - - destroy_pxe_menu(cfg); - - return 0; -} - -U_BOOT_CMD( - sysboot, 7, 1, do_sysboot, - "command to get and boot from syslinux files", - "[-p] <interface> <dev[:part]> <ext2|fat|any> [addr] [filename]\n" - " - load and parse syslinux menu file 'filename' from ext2, fat\n" - " or any filesystem on 'dev' on 'interface' to address 'addr'" -); diff --git a/cmd/pxe_utils.c b/cmd/pxe_utils.c new file mode 100644 index 0000000000..42b584ead3 --- /dev/null +++ b/cmd/pxe_utils.c @@ -0,0 +1,1352 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2010-2011 Calxeda, Inc. + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + */ + +#include <common.h> +#include <env.h> +#include <malloc.h> +#include <mapmem.h> +#include <lcd.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <errno.h> +#include <linux/list.h> + +#include <splash.h> +#include <asm/io.h> + +#include "menu.h" +#include "cli.h" + +#include "pxe_utils.h" + +#define MAX_TFTP_PATH_LEN 127 + +bool is_pxe; + +/* + * Convert an ethaddr from the environment to the format used by pxelinux + * filenames based on mac addresses. Convert's ':' to '-', and adds "01-" to + * the beginning of the ethernet address to indicate a hardware type of + * Ethernet. Also converts uppercase hex characters into lowercase, to match + * pxelinux's behavior. + * + * Returns 1 for success, -ENOENT if 'ethaddr' is undefined in the + * environment, or some other value < 0 on error. + */ +int format_mac_pxe(char *outbuf, size_t outbuf_len) +{ + uchar ethaddr[6]; + + if (outbuf_len < 21) { + printf("outbuf is too small (%zd < 21)\n", outbuf_len); + + return -EINVAL; + } + + if (!eth_env_get_enetaddr_by_index("eth", eth_get_dev_index(), ethaddr)) + return -ENOENT; + + sprintf(outbuf, "01-%02x-%02x-%02x-%02x-%02x-%02x", + ethaddr[0], ethaddr[1], ethaddr[2], + ethaddr[3], ethaddr[4], ethaddr[5]); + + return 1; +} + +/* + * Returns the directory the file specified in the bootfile env variable is + * in. If bootfile isn't defined in the environment, return NULL, which should + * be interpreted as "don't prepend anything to paths". + */ +static int get_bootfile_path(const char *file_path, char *bootfile_path, + size_t bootfile_path_size) +{ + char *bootfile, *last_slash; + size_t path_len = 0; + + /* Only syslinux allows absolute paths */ + if (file_path[0] == '/' && !is_pxe) + goto ret; + + bootfile = from_env("bootfile"); + + if (!bootfile) + goto ret; + + last_slash = strrchr(bootfile, '/'); + + if (!last_slash) + goto ret; + + path_len = (last_slash - bootfile) + 1; + + if (bootfile_path_size < path_len) { + printf("bootfile_path too small. (%zd < %zd)\n", + bootfile_path_size, path_len); + + return -1; + } + + strncpy(bootfile_path, bootfile, path_len); + + ret: + bootfile_path[path_len] = '\0'; + + return 1; +} + +int (*do_getfile)(cmd_tbl_t *cmdtp, const char *file_path, char *file_addr); + +/* + * As in pxelinux, paths to files referenced from files we retrieve are + * relative to the location of bootfile. get_relfile takes such a path and + * joins it with the bootfile path to get the full path to the target file. If + * the bootfile path is NULL, we use file_path as is. + * + * Returns 1 for success, or < 0 on error. + */ +static int get_relfile(cmd_tbl_t *cmdtp, const char *file_path, + unsigned long file_addr) +{ + size_t path_len; + char relfile[MAX_TFTP_PATH_LEN + 1]; + char addr_buf[18]; + int err; + + err = get_bootfile_path(file_path, relfile, sizeof(relfile)); + + if (err < 0) + return err; + + path_len = strlen(file_path); + path_len += strlen(relfile); + + if (path_len > MAX_TFTP_PATH_LEN) { + printf("Base path too long (%s%s)\n", relfile, file_path); + + return -ENAMETOOLONG; + } + + strcat(relfile, file_path); + + printf("Retrieving file: %s\n", relfile); + + sprintf(addr_buf, "%lx", file_addr); + + return do_getfile(cmdtp, relfile, addr_buf); +} + +/* + * Retrieve the file at 'file_path' to the locate given by 'file_addr'. If + * 'bootfile' was specified in the environment, the path to bootfile will be + * prepended to 'file_path' and the resulting path will be used. + * + * Returns 1 on success, or < 0 for error. + */ +int get_pxe_file(cmd_tbl_t *cmdtp, const char *file_path, + unsigned long file_addr) +{ + unsigned long config_file_size; + char *tftp_filesize; + int err; + char *buf; + + err = get_relfile(cmdtp, file_path, file_addr); + + if (err < 0) + return err; + + /* + * the file comes without a NUL byte at the end, so find out its size + * and add the NUL byte. + */ + tftp_filesize = from_env("filesize"); + + if (!tftp_filesize) + return -ENOENT; + + if (strict_strtoul(tftp_filesize, 16, &config_file_size) < 0) + return -EINVAL; + + buf = map_sysmem(file_addr + config_file_size, 1); + *buf = '\0'; + unmap_sysmem(buf); + + return 1; +} + +#define PXELINUX_DIR "pxelinux.cfg/" + +/* + * Retrieves a file in the 'pxelinux.cfg' folder. Since this uses get_pxe_file + * to do the hard work, the location of the 'pxelinux.cfg' folder is generated + * from the bootfile path, as described above. + * + * Returns 1 on success or < 0 on error. + */ +int get_pxelinux_path(cmd_tbl_t *cmdtp, const char *file, + unsigned long pxefile_addr_r) +{ + size_t base_len = strlen(PXELINUX_DIR); + char path[MAX_TFTP_PATH_LEN + 1]; + + if (base_len + strlen(file) > MAX_TFTP_PATH_LEN) { + printf("path (%s%s) too long, skipping\n", + PXELINUX_DIR, file); + return -ENAMETOOLONG; + } + + sprintf(path, PXELINUX_DIR "%s", file); + + return get_pxe_file(cmdtp, path, pxefile_addr_r); +} + +/* + * Wrapper to make it easier to store the file at file_path in the location + * specified by envaddr_name. file_path will be joined to the bootfile path, + * if any is specified. + * + * Returns 1 on success or < 0 on error. + */ +static int get_relfile_envaddr(cmd_tbl_t *cmdtp, const char *file_path, + const char *envaddr_name) +{ + unsigned long file_addr; + char *envaddr; + + envaddr = from_env(envaddr_name); + + if (!envaddr) + return -ENOENT; + + if (strict_strtoul(envaddr, 16, &file_addr) < 0) + return -EINVAL; + + return get_relfile(cmdtp, file_path, file_addr); +} + +/* + * Allocates memory for and initializes a pxe_label. This uses malloc, so the + * result must be free()'d to reclaim the memory. + * + * Returns NULL if malloc fails. + */ +static struct pxe_label *label_create(void) +{ + struct pxe_label *label; + + label = malloc(sizeof(struct pxe_label)); + + if (!label) + return NULL; + + memset(label, 0, sizeof(struct pxe_label)); + + return label; +} + +/* + * Free the memory used by a pxe_label, including that used by its name, + * kernel, append and initrd members, if they're non NULL. + * + * So - be sure to only use dynamically allocated memory for the members of + * the pxe_label struct, unless you want to clean it up first. These are + * currently only created by the pxe file parsing code. + */ +static void label_destroy(struct pxe_label *label) +{ + if (label->name) + free(label->name); + + if (label->kernel) + free(label->kernel); + + if (label->config) + free(label->config); + + if (label->append) + free(label->append); + + if (label->initrd) + free(label->initrd); + + if (label->fdt) + free(label->fdt); + + if (label->fdtdir) + free(label->fdtdir); + + free(label); +} + +/* + * Print a label and its string members if they're defined. + * + * This is passed as a callback to the menu code for displaying each + * menu entry. + */ +static void label_print(void *data) +{ + struct pxe_label *label = data; + const char *c = label->menu ? label->menu : label->name; + + printf("%s:\t%s\n", label->num, c); +} + +/* + * Boot a label that specified 'localboot'. This requires that the 'localcmd' + * environment variable is defined. Its contents will be executed as U-Boot + * command. If the label specified an 'append' line, its contents will be + * used to overwrite the contents of the 'bootargs' environment variable prior + * to running 'localcmd'. + * + * Returns 1 on success or < 0 on error. + */ +static int label_localboot(struct pxe_label *label) +{ + char *localcmd; + + localcmd = from_env("localcmd"); + + if (!localcmd) + return -ENOENT; + + if (label->append) { + char bootargs[CONFIG_SYS_CBSIZE]; + + cli_simple_process_macros(label->append, bootargs); + env_set("bootargs", bootargs); + } + + debug("running: %s\n", localcmd); + + return run_command_list(localcmd, strlen(localcmd), 0); +} + +/* + * Boot according to the contents of a pxe_label. + * + * If we can't boot for any reason, we return. A successful boot never + * returns. + * + * The kernel will be stored in the location given by the 'kernel_addr_r' + * environment variable. + * + * If the label specifies an initrd file, it will be stored in the location + * given by the 'ramdisk_addr_r' environment variable. + * + * If the label specifies an 'append' line, its contents will overwrite that + * of the 'bootargs' environment variable. + */ +static int label_boot(cmd_tbl_t *cmdtp, struct pxe_label *label) +{ + char *bootm_argv[] = { "bootm", NULL, NULL, NULL, NULL }; + char initrd_str[28]; + char mac_str[29] = ""; + char ip_str[68] = ""; + char *fit_addr = NULL; + int bootm_argc = 2; + int len = 0; + ulong kernel_addr; + void *buf; + + label_print(label); + + label->attempted = 1; + + if (label->localboot) { + if (label->localboot_val >= 0) + label_localboot(label); + return 0; + } + + if (!label->kernel) { + printf("No kernel given, skipping %s\n", + label->name); + return 1; + } + + if (label->initrd) { + if (get_relfile_envaddr(cmdtp, label->initrd, "ramdisk_addr_r") < 0) { + printf("Skipping %s for failure retrieving initrd\n", + label->name); + return 1; + } + + bootm_argv[2] = initrd_str; + strncpy(bootm_argv[2], env_get("ramdisk_addr_r"), 18); + strcat(bootm_argv[2], ":"); + strncat(bootm_argv[2], env_get("filesize"), 9); + bootm_argc = 3; + } + + if (get_relfile_envaddr(cmdtp, label->kernel, "kernel_addr_r") < 0) { + printf("Skipping %s for failure retrieving kernel\n", + label->name); + return 1; + } + + if (label->ipappend & 0x1) { + sprintf(ip_str, " ip=%s:%s:%s:%s", + env_get("ipaddr"), env_get("serverip"), + env_get("gatewayip"), env_get("netmask")); + } + +#ifdef CONFIG_CMD_NET + if (label->ipappend & 0x2) { + int err; + + strcpy(mac_str, " BOOTIF="); + err = format_mac_pxe(mac_str + 8, sizeof(mac_str) - 8); + if (err < 0) + mac_str[0] = '\0'; + } +#endif + + if ((label->ipappend & 0x3) || label->append) { + char bootargs[CONFIG_SYS_CBSIZE] = ""; + char finalbootargs[CONFIG_SYS_CBSIZE]; + + if (strlen(label->append ?: "") + + strlen(ip_str) + strlen(mac_str) + 1 > sizeof(bootargs)) { + printf("bootarg overflow %zd+%zd+%zd+1 > %zd\n", + strlen(label->append ?: ""), + strlen(ip_str), strlen(mac_str), + sizeof(bootargs)); + return 1; + } + + if (label->append) + strncpy(bootargs, label->append, sizeof(bootargs)); + + strcat(bootargs, ip_str); + strcat(bootargs, mac_str); + + cli_simple_process_macros(bootargs, finalbootargs); + env_set("bootargs", finalbootargs); + printf("append: %s\n", finalbootargs); + } + + bootm_argv[1] = env_get("kernel_addr_r"); + /* for FIT, append the configuration identifier */ + if (label->config) { + int len = strlen(bootm_argv[1]) + strlen(label->config) + 1; + + fit_addr = malloc(len); + if (!fit_addr) { + printf("malloc fail (FIT address)\n"); + return 1; + } + snprintf(fit_addr, len, "%s%s", bootm_argv[1], label->config); + bootm_argv[1] = fit_addr; + } + + /* + * fdt usage is optional: + * It handles the following scenarios. All scenarios are exclusive + * + * Scenario 1: If fdt_addr_r specified and "fdt" label is defined in + * pxe file, retrieve fdt blob from server. Pass fdt_addr_r to bootm, + * and adjust argc appropriately. + * + * Scenario 2: If there is an fdt_addr specified, pass it along to + * bootm, and adjust argc appropriately. + * + * Scenario 3: fdt blob is not available. + */ + bootm_argv[3] = env_get("fdt_addr_r"); + + /* if fdt label is defined then get fdt from server */ + if (bootm_argv[3]) { + char *fdtfile = NULL; + char *fdtfilefree = NULL; + + if (label->fdt) { + fdtfile = label->fdt; + } else if (label->fdtdir) { + char *f1, *f2, *f3, *f4, *slash; + + f1 = env_get("fdtfile"); + if (f1) { + f2 = ""; + f3 = ""; + f4 = ""; + } else { + /* + * For complex cases where this code doesn't + * generate the correct filename, the board + * code should set $fdtfile during early boot, + * or the boot scripts should set $fdtfile + * before invoking "pxe" or "sysboot". + */ + f1 = env_get("soc"); + f2 = "-"; + f3 = env_get("board"); + f4 = ".dtb"; + } + + len = strlen(label->fdtdir); + if (!len) + slash = "./"; + else if (label->fdtdir[len - 1] != '/') + slash = "/"; + else + slash = ""; + + len = strlen(label->fdtdir) + strlen(slash) + + strlen(f1) + strlen(f2) + strlen(f3) + + strlen(f4) + 1; + fdtfilefree = malloc(len); + if (!fdtfilefree) { + printf("malloc fail (FDT filename)\n"); + goto cleanup; + } + + snprintf(fdtfilefree, len, "%s%s%s%s%s%s", + label->fdtdir, slash, f1, f2, f3, f4); + fdtfile = fdtfilefree; + } + + if (fdtfile) { + int err = get_relfile_envaddr(cmdtp, fdtfile, + "fdt_addr_r"); + + free(fdtfilefree); + if (err < 0) { + printf("Skipping %s for failure retrieving fdt\n", + label->name); + goto cleanup; + } + } else { + bootm_argv[3] = NULL; + } + } + + if (!bootm_argv[3]) + bootm_argv[3] = env_get("fdt_addr"); + + if (bootm_argv[3]) { + if (!bootm_argv[2]) + bootm_argv[2] = "-"; + bootm_argc = 4; + } + + kernel_addr = genimg_get_kernel_addr(bootm_argv[1]); + buf = map_sysmem(kernel_addr, 0); + /* Try bootm for legacy and FIT format image */ + if (genimg_get_format(buf) != IMAGE_FORMAT_INVALID) + do_bootm(cmdtp, 0, bootm_argc, bootm_argv); +#ifdef CONFIG_CMD_BOOTI + /* Try booting an AArch64 Linux kernel image */ + else + do_booti(cmdtp, 0, bootm_argc, bootm_argv); +#elif defined(CONFIG_CMD_BOOTZ) + /* Try booting a Image */ + else + do_bootz(cmdtp, 0, bootm_argc, bootm_argv); +#endif + unmap_sysmem(buf); + +cleanup: + if (fit_addr) + free(fit_addr); + return 1; +} + +/* + * Tokens for the pxe file parser. + */ +enum token_type { + T_EOL, + T_STRING, + T_EOF, + T_MENU, + T_TITLE, + T_TIMEOUT, + T_LABEL, + T_KERNEL, + T_LINUX, + T_APPEND, + T_INITRD, + T_LOCALBOOT, + T_DEFAULT, + T_PROMPT, + T_INCLUDE, + T_FDT, + T_FDTDIR, + T_ONTIMEOUT, + T_IPAPPEND, + T_BACKGROUND, + T_INVALID +}; + +/* + * A token - given by a value and a type. + */ +struct token { + char *val; + enum token_type type; +}; + +/* + * Keywords recognized. + */ +static const struct token keywords[] = { + {"menu", T_MENU}, + {"title", T_TITLE}, + {"timeout", T_TIMEOUT}, + {"default", T_DEFAULT}, + {"prompt", T_PROMPT}, + {"label", T_LABEL}, + {"kernel", T_KERNEL}, + {"linux", T_LINUX}, + {"localboot", T_LOCALBOOT}, + {"append", T_APPEND}, + {"initrd", T_INITRD}, + {"include", T_INCLUDE}, + {"devicetree", T_FDT}, + {"fdt", T_FDT}, + {"devicetreedir", T_FDTDIR}, + {"fdtdir", T_FDTDIR}, + {"ontimeout", T_ONTIMEOUT,}, + {"ipappend", T_IPAPPEND,}, + {"background", T_BACKGROUND,}, + {NULL, T_INVALID} +}; + +/* + * Since pxe(linux) files don't have a token to identify the start of a + * literal, we have to keep track of when we're in a state where a literal is + * expected vs when we're in a state a keyword is expected. + */ +enum lex_state { + L_NORMAL = 0, + L_KEYWORD, + L_SLITERAL +}; + +/* + * get_string retrieves a string from *p and stores it as a token in + * *t. + * + * get_string used for scanning both string literals and keywords. + * + * Characters from *p are copied into t-val until a character equal to + * delim is found, or a NUL byte is reached. If delim has the special value of + * ' ', any whitespace character will be used as a delimiter. + * + * If lower is unequal to 0, uppercase characters will be converted to + * lowercase in the result. This is useful to make keywords case + * insensitive. + * + * The location of *p is updated to point to the first character after the end + * of the token - the ending delimiter. + * + * On success, the new value of t->val is returned. Memory for t->val is + * allocated using malloc and must be free()'d to reclaim it. If insufficient + * memory is available, NULL is returned. + */ +static char *get_string(char **p, struct token *t, char delim, int lower) +{ + char *b, *e; + size_t len, i; + + /* + * b and e both start at the beginning of the input stream. + * + * e is incremented until we find the ending delimiter, or a NUL byte + * is reached. Then, we take e - b to find the length of the token. + */ + b = *p; + e = *p; + + while (*e) { + if ((delim == ' ' && isspace(*e)) || delim == *e) + break; + e++; + } + + len = e - b; + + /* + * Allocate memory to hold the string, and copy it in, converting + * characters to lowercase if lower is != 0. + */ + t->val = malloc(len + 1); + if (!t->val) + return NULL; + + for (i = 0; i < len; i++, b++) { + if (lower) + t->val[i] = tolower(*b); + else + t->val[i] = *b; + } + + t->val[len] = '\0'; + + /* + * Update *p so the caller knows where to continue scanning. + */ + *p = e; + + t->type = T_STRING; + + return t->val; +} + +/* + * Populate a keyword token with a type and value. + */ +static void get_keyword(struct token *t) +{ + int i; + + for (i = 0; keywords[i].val; i++) { + if (!strcmp(t->val, keywords[i].val)) { + t->type = keywords[i].type; + break; + } + } +} + +/* + * Get the next token. We have to keep track of which state we're in to know + * if we're looking to get a string literal or a keyword. + * + * *p is updated to point at the first character after the current token. + */ +static void get_token(char **p, struct token *t, enum lex_state state) +{ + char *c = *p; + + t->type = T_INVALID; + + /* eat non EOL whitespace */ + while (isblank(*c)) + c++; + + /* + * eat comments. note that string literals can't begin with #, but + * can contain a # after their first character. + */ + if (*c == '#') { + while (*c && *c != '\n') + c++; + } + + if (*c == '\n') { + t->type = T_EOL; + c++; + } else if (*c == '\0') { + t->type = T_EOF; + c++; + } else if (state == L_SLITERAL) { + get_string(&c, t, '\n', 0); + } else if (state == L_KEYWORD) { + /* + * when we expect a keyword, we first get the next string + * token delimited by whitespace, and then check if it + * matches a keyword in our keyword list. if it does, it's + * converted to a keyword token of the appropriate type, and + * if not, it remains a string token. + */ + get_string(&c, t, ' ', 1); + get_keyword(t); + } + + *p = c; +} + +/* + * Increment *c until we get to the end of the current line, or EOF. + */ +static void eol_or_eof(char **c) +{ + while (**c && **c != '\n') + (*c)++; +} + +/* + * All of these parse_* functions share some common behavior. + * + * They finish with *c pointing after the token they parse, and return 1 on + * success, or < 0 on error. + */ + +/* + * Parse a string literal and store a pointer it at *dst. String literals + * terminate at the end of the line. + */ +static int parse_sliteral(char **c, char **dst) +{ + struct token t; + char *s = *c; + + get_token(c, &t, L_SLITERAL); + + if (t.type != T_STRING) { + printf("Expected string literal: %.*s\n", (int)(*c - s), s); + return -EINVAL; + } + + *dst = t.val; + + return 1; +} + +/* + * Parse a base 10 (unsigned) integer and store it at *dst. + */ +static int parse_integer(char **c, int *dst) +{ + struct token t; + char *s = *c; + + get_token(c, &t, L_SLITERAL); + + if (t.type != T_STRING) { + printf("Expected string: %.*s\n", (int)(*c - s), s); + return -EINVAL; + } + + *dst = simple_strtol(t.val, NULL, 10); + + free(t.val); + + return 1; +} + +static int parse_pxefile_top(cmd_tbl_t *cmdtp, char *p, unsigned long base, + struct pxe_menu *cfg, int nest_level); + +/* + * Parse an include statement, and retrieve and parse the file it mentions. + * + * base should point to a location where it's safe to store the file, and + * nest_level should indicate how many nested includes have occurred. For this + * include, nest_level has already been incremented and doesn't need to be + * incremented here. + */ +static int handle_include(cmd_tbl_t *cmdtp, char **c, unsigned long base, + struct pxe_menu *cfg, int nest_level) +{ + char *include_path; + char *s = *c; + int err; + char *buf; + int ret; + + err = parse_sliteral(c, &include_path); + + if (err < 0) { + printf("Expected include path: %.*s\n", (int)(*c - s), s); + return err; + } + + err = get_pxe_file(cmdtp, include_path, base); + + if (err < 0) { + printf("Couldn't retrieve %s\n", include_path); + return err; + } + + buf = map_sysmem(base, 0); + ret = parse_pxefile_top(cmdtp, buf, base, cfg, nest_level); + unmap_sysmem(buf); + + return ret; +} + +/* + * Parse lines that begin with 'menu'. + * + * base and nest are provided to handle the 'menu include' case. + * + * base should point to a location where it's safe to store the included file. + * + * nest_level should be 1 when parsing the top level pxe file, 2 when parsing + * a file it includes, 3 when parsing a file included by that file, and so on. + */ +static int parse_menu(cmd_tbl_t *cmdtp, char **c, struct pxe_menu *cfg, + unsigned long base, int nest_level) +{ + struct token t; + char *s = *c; + int err = 0; + + get_token(c, &t, L_KEYWORD); + + switch (t.type) { + case T_TITLE: + err = parse_sliteral(c, &cfg->title); + + break; + + case T_INCLUDE: + err = handle_include(cmdtp, c, base, cfg, nest_level + 1); + break; + + case T_BACKGROUND: + err = parse_sliteral(c, &cfg->bmp); + break; + + default: + printf("Ignoring malformed menu command: %.*s\n", + (int)(*c - s), s); + } + + if (err < 0) + return err; + + eol_or_eof(c); + + return 1; +} + +/* + * Handles parsing a 'menu line' when we're parsing a label. + */ +static int parse_label_menu(char **c, struct pxe_menu *cfg, + struct pxe_label *label) +{ + struct token t; + char *s; + + s = *c; + + get_token(c, &t, L_KEYWORD); + + switch (t.type) { + case T_DEFAULT: + if (!cfg->default_label) + cfg->default_label = strdup(label->name); + + if (!cfg->default_label) + return -ENOMEM; + + break; + case T_LABEL: + parse_sliteral(c, &label->menu); + break; + default: + printf("Ignoring malformed menu command: %.*s\n", + (int)(*c - s), s); + } + + eol_or_eof(c); + + return 0; +} + +/* + * Handles parsing a 'kernel' label. + * expecting "filename" or "<fit_filename>#cfg" + */ +static int parse_label_kernel(char **c, struct pxe_label *label) +{ + char *s; + int err; + + err = parse_sliteral(c, &label->kernel); + if (err < 0) + return err; + + s = strstr(label->kernel, "#"); + if (!s) + return 1; + + label->config = malloc(strlen(s) + 1); + if (!label->config) + return -ENOMEM; + + strcpy(label->config, s); + *s = 0; + + return 1; +} + +/* + * Parses a label and adds it to the list of labels for a menu. + * + * A label ends when we either get to the end of a file, or + * get some input we otherwise don't have a handler defined + * for. + * + */ +static int parse_label(char **c, struct pxe_menu *cfg) +{ + struct token t; + int len; + char *s = *c; + struct pxe_label *label; + int err; + + label = label_create(); + if (!label) + return -ENOMEM; + + err = parse_sliteral(c, &label->name); + if (err < 0) { + printf("Expected label name: %.*s\n", (int)(*c - s), s); + label_destroy(label); + return -EINVAL; + } + + list_add_tail(&label->list, &cfg->labels); + + while (1) { + s = *c; + get_token(c, &t, L_KEYWORD); + + err = 0; + switch (t.type) { + case T_MENU: + err = parse_label_menu(c, cfg, label); + break; + + case T_KERNEL: + case T_LINUX: + err = parse_label_kernel(c, label); + break; + + case T_APPEND: + err = parse_sliteral(c, &label->append); + if (label->initrd) + break; + s = strstr(label->append, "initrd="); + if (!s) + break; + s += 7; + len = (int)(strchr(s, ' ') - s); + label->initrd = malloc(len + 1); + strncpy(label->initrd, s, len); + label->initrd[len] = '\0'; + + break; + + case T_INITRD: + if (!label->initrd) + err = parse_sliteral(c, &label->initrd); + break; + + case T_FDT: + if (!label->fdt) + err = parse_sliteral(c, &label->fdt); + break; + + case T_FDTDIR: + if (!label->fdtdir) + err = parse_sliteral(c, &label->fdtdir); + break; + + case T_LOCALBOOT: + label->localboot = 1; + err = parse_integer(c, &label->localboot_val); + break; + + case T_IPAPPEND: + err = parse_integer(c, &label->ipappend); + break; + + case T_EOL: + break; + + default: + /* + * put the token back! we don't want it - it's the end + * of a label and whatever token this is, it's + * something for the menu level context to handle. + */ + *c = s; + return 1; + } + + if (err < 0) + return err; + } +} + +/* + * This 16 comes from the limit pxelinux imposes on nested includes. + * + * There is no reason at all we couldn't do more, but some limit helps prevent + * infinite (until crash occurs) recursion if a file tries to include itself. + */ +#define MAX_NEST_LEVEL 16 + +/* + * Entry point for parsing a menu file. nest_level indicates how many times + * we've nested in includes. It will be 1 for the top level menu file. + * + * Returns 1 on success, < 0 on error. + */ +static int parse_pxefile_top(cmd_tbl_t *cmdtp, char *p, unsigned long base, + struct pxe_menu *cfg, int nest_level) +{ + struct token t; + char *s, *b, *label_name; + int err; + + b = p; + + if (nest_level > MAX_NEST_LEVEL) { + printf("Maximum nesting (%d) exceeded\n", MAX_NEST_LEVEL); + return -EMLINK; + } + + while (1) { + s = p; + + get_token(&p, &t, L_KEYWORD); + + err = 0; + switch (t.type) { + case T_MENU: + cfg->prompt = 1; + err = parse_menu(cmdtp, &p, cfg, + base + ALIGN(strlen(b) + 1, 4), + nest_level); + break; + + case T_TIMEOUT: + err = parse_integer(&p, &cfg->timeout); + break; + + case T_LABEL: + err = parse_label(&p, cfg); + break; + + case T_DEFAULT: + case T_ONTIMEOUT: + err = parse_sliteral(&p, &label_name); + + if (label_name) { + if (cfg->default_label) + free(cfg->default_label); + + cfg->default_label = label_name; + } + + break; + + case T_INCLUDE: + err = handle_include(cmdtp, &p, + base + ALIGN(strlen(b), 4), cfg, + nest_level + 1); + break; + + case T_PROMPT: + eol_or_eof(&p); + break; + + case T_EOL: + break; + + case T_EOF: + return 1; + + default: + printf("Ignoring unknown command: %.*s\n", + (int)(p - s), s); + eol_or_eof(&p); + } + + if (err < 0) + return err; + } +} + +/* + * Free the memory used by a pxe_menu and its labels. + */ +void destroy_pxe_menu(struct pxe_menu *cfg) +{ + struct list_head *pos, *n; + struct pxe_label *label; + + if (cfg->title) + free(cfg->title); + + if (cfg->default_label) + free(cfg->default_label); + + list_for_each_safe(pos, n, &cfg->labels) { + label = list_entry(pos, struct pxe_label, list); + + label_destroy(label); + } + + free(cfg); +} + +/* + * Entry point for parsing a pxe file. This is only used for the top level + * file. + * + * Returns NULL if there is an error, otherwise, returns a pointer to a + * pxe_menu struct populated with the results of parsing the pxe file (and any + * files it includes). The resulting pxe_menu struct can be free()'d by using + * the destroy_pxe_menu() function. + */ +struct pxe_menu *parse_pxefile(cmd_tbl_t *cmdtp, unsigned long menucfg) +{ + struct pxe_menu *cfg; + char *buf; + int r; + + cfg = malloc(sizeof(struct pxe_menu)); + + if (!cfg) + return NULL; + + memset(cfg, 0, sizeof(struct pxe_menu)); + + INIT_LIST_HEAD(&cfg->labels); + + buf = map_sysmem(menucfg, 0); + r = parse_pxefile_top(cmdtp, buf, menucfg, cfg, 1); + unmap_sysmem(buf); + + if (r < 0) { + destroy_pxe_menu(cfg); + return NULL; + } + + return cfg; +} + +/* + * Converts a pxe_menu struct into a menu struct for use with U-Boot's generic + * menu code. + */ +static struct menu *pxe_menu_to_menu(struct pxe_menu *cfg) +{ + struct pxe_label *label; + struct list_head *pos; + struct menu *m; + int err; + int i = 1; + char *default_num = NULL; + + /* + * Create a menu and add items for all the labels. + */ + m = menu_create(cfg->title, DIV_ROUND_UP(cfg->timeout, 10), + cfg->prompt, label_print, NULL, NULL); + + if (!m) + return NULL; + + list_for_each(pos, &cfg->labels) { + label = list_entry(pos, struct pxe_label, list); + + sprintf(label->num, "%d", i++); + if (menu_item_add(m, label->num, label) != 1) { + menu_destroy(m); + return NULL; + } + if (cfg->default_label && + (strcmp(label->name, cfg->default_label) == 0)) + default_num = label->num; + } + + /* + * After we've created items for each label in the menu, set the + * menu's default label if one was specified. + */ + if (default_num) { + err = menu_default_set(m, default_num); + if (err != 1) { + if (err != -ENOENT) { + menu_destroy(m); + return NULL; + } + + printf("Missing default: %s\n", cfg->default_label); + } + } + + return m; +} + +/* + * Try to boot any labels we have yet to attempt to boot. + */ +static void boot_unattempted_labels(cmd_tbl_t *cmdtp, struct pxe_menu *cfg) +{ + struct list_head *pos; + struct pxe_label *label; + + list_for_each(pos, &cfg->labels) { + label = list_entry(pos, struct pxe_label, list); + + if (!label->attempted) + label_boot(cmdtp, label); + } +} + +/* + * Boot the system as prescribed by a pxe_menu. + * + * Use the menu system to either get the user's choice or the default, based + * on config or user input. If there is no default or user's choice, + * attempted to boot labels in the order they were given in pxe files. + * If the default or user's choice fails to boot, attempt to boot other + * labels in the order they were given in pxe files. + * + * If this function returns, there weren't any labels that successfully + * booted, or the user interrupted the menu selection via ctrl+c. + */ +void handle_pxe_menu(cmd_tbl_t *cmdtp, struct pxe_menu *cfg) +{ + void *choice; + struct menu *m; + int err; + +#ifdef CONFIG_CMD_BMP + /* display BMP if available */ + if (cfg->bmp) { + if (get_relfile(cmdtp, cfg->bmp, load_addr)) { + run_command("cls", 0); + bmp_display(load_addr, + BMP_ALIGN_CENTER, BMP_ALIGN_CENTER); + } else { + printf("Skipping background bmp %s for failure\n", + cfg->bmp); + } + } +#endif + + m = pxe_menu_to_menu(cfg); + if (!m) + return; + + err = menu_get_choice(m, &choice); + + menu_destroy(m); + + /* + * err == 1 means we got a choice back from menu_get_choice. + * + * err == -ENOENT if the menu was setup to select the default but no + * default was set. in that case, we should continue trying to boot + * labels that haven't been attempted yet. + * + * otherwise, the user interrupted or there was some other error and + * we give up. + */ + + if (err == 1) { + err = label_boot(cmdtp, choice); + if (!err) + return; + } else if (err != -ENOENT) { + return; + } + + boot_unattempted_labels(cmdtp, cfg); +} diff --git a/cmd/pxe_utils.h b/cmd/pxe_utils.h new file mode 100644 index 0000000000..a38ac81a47 --- /dev/null +++ b/cmd/pxe_utils.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef __PXE_UTILS_H +#define __PXE_UTILS_H + +/* + * A note on the pxe file parser. + * + * We're parsing files that use syslinux grammar, which has a few quirks. + * String literals must be recognized based on context - there is no + * quoting or escaping support. There's also nothing to explicitly indicate + * when a label section completes. We deal with that by ending a label + * section whenever we see a line that doesn't include. + * + * As with the syslinux family, this same file format could be reused in the + * future for non pxe purposes. The only action it takes during parsing that + * would throw this off is handling of include files. It assumes we're using + * pxe, and does a tftp download of a file listed as an include file in the + * middle of the parsing operation. That could be handled by refactoring it to + * take a 'include file getter' function. + */ + +/* + * Describes a single label given in a pxe file. + * + * Create these with the 'label_create' function given below. + * + * name - the name of the menu as given on the 'menu label' line. + * kernel - the path to the kernel file to use for this label. + * append - kernel command line to use when booting this label + * initrd - path to the initrd to use for this label. + * attempted - 0 if we haven't tried to boot this label, 1 if we have. + * localboot - 1 if this label specified 'localboot', 0 otherwise. + * list - lets these form a list, which a pxe_menu struct will hold. + */ +struct pxe_label { + char num[4]; + char *name; + char *menu; + char *kernel; + char *config; + char *append; + char *initrd; + char *fdt; + char *fdtdir; + int ipappend; + int attempted; + int localboot; + int localboot_val; + struct list_head list; +}; + +/* + * Describes a pxe menu as given via pxe files. + * + * title - the name of the menu as given by a 'menu title' line. + * default_label - the name of the default label, if any. + * bmp - the bmp file name which is displayed in background + * timeout - time in tenths of a second to wait for a user key-press before + * booting the default label. + * prompt - if 0, don't prompt for a choice unless the timeout period is + * interrupted. If 1, always prompt for a choice regardless of + * timeout. + * labels - a list of labels defined for the menu. + */ +struct pxe_menu { + char *title; + char *default_label; + char *bmp; + int timeout; + int prompt; + struct list_head labels; +}; + +extern bool is_pxe; + +extern int (*do_getfile)(cmd_tbl_t *cmdtp, const char *file_path, + char *file_addr); +void destroy_pxe_menu(struct pxe_menu *cfg); +int get_pxe_file(cmd_tbl_t *cmdtp, const char *file_path, + unsigned long file_addr); +int get_pxelinux_path(cmd_tbl_t *cmdtp, const char *file, + unsigned long pxefile_addr_r); +void handle_pxe_menu(cmd_tbl_t *cmdtp, struct pxe_menu *cfg); +struct pxe_menu *parse_pxefile(cmd_tbl_t *cmdtp, unsigned long menucfg); +int format_mac_pxe(char *outbuf, size_t outbuf_len); + +#endif /* __PXE_UTILS_H */ diff --git a/cmd/sysboot.c b/cmd/sysboot.c new file mode 100644 index 0000000000..793d67d2f5 --- /dev/null +++ b/cmd/sysboot.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <common.h> +#include <command.h> +#include <env.h> +#include <fs.h> +#include "pxe_utils.h" + +static char *fs_argv[5]; + +static int do_get_ext2(cmd_tbl_t *cmdtp, const char *file_path, char *file_addr) +{ +#ifdef CONFIG_CMD_EXT2 + fs_argv[0] = "ext2load"; + fs_argv[3] = file_addr; + fs_argv[4] = (void *)file_path; + + if (!do_ext2load(cmdtp, 0, 5, fs_argv)) + return 1; +#endif + return -ENOENT; +} + +static int do_get_fat(cmd_tbl_t *cmdtp, const char *file_path, char *file_addr) +{ +#ifdef CONFIG_CMD_FAT + fs_argv[0] = "fatload"; + fs_argv[3] = file_addr; + fs_argv[4] = (void *)file_path; + + if (!do_fat_fsload(cmdtp, 0, 5, fs_argv)) + return 1; +#endif + return -ENOENT; +} + +static int do_get_any(cmd_tbl_t *cmdtp, const char *file_path, char *file_addr) +{ +#ifdef CONFIG_CMD_FS_GENERIC + fs_argv[0] = "load"; + fs_argv[3] = file_addr; + fs_argv[4] = (void *)file_path; + + if (!do_load(cmdtp, 0, 5, fs_argv, FS_TYPE_ANY)) + return 1; +#endif + return -ENOENT; +} + +/* + * Boots a system using a local disk syslinux/extlinux file + * + * Returns 0 on success, 1 on error. + */ +static int do_sysboot(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) +{ + unsigned long pxefile_addr_r; + struct pxe_menu *cfg; + char *pxefile_addr_str; + char *filename; + int prompt = 0; + + is_pxe = false; + + if (argc > 1 && strstr(argv[1], "-p")) { + prompt = 1; + argc--; + argv++; + } + + if (argc < 4) + return cmd_usage(cmdtp); + + if (argc < 5) { + pxefile_addr_str = from_env("pxefile_addr_r"); + if (!pxefile_addr_str) + return 1; + } else { + pxefile_addr_str = argv[4]; + } + + if (argc < 6) { + filename = env_get("bootfile"); + } else { + filename = argv[5]; + env_set("bootfile", filename); + } + + if (strstr(argv[3], "ext2")) { + do_getfile = do_get_ext2; + } else if (strstr(argv[3], "fat")) { + do_getfile = do_get_fat; + } else if (strstr(argv[3], "any")) { + do_getfile = do_get_any; + } else { + printf("Invalid filesystem: %s\n", argv[3]); + return 1; + } + fs_argv[1] = argv[1]; + fs_argv[2] = argv[2]; + + if (strict_strtoul(pxefile_addr_str, 16, &pxefile_addr_r) < 0) { + printf("Invalid pxefile address: %s\n", pxefile_addr_str); + return 1; + } + + if (get_pxe_file(cmdtp, filename, pxefile_addr_r) < 0) { + printf("Error reading config file\n"); + return 1; + } + + cfg = parse_pxefile(cmdtp, pxefile_addr_r); + + if (!cfg) { + printf("Error parsing config file\n"); + return 1; + } + + if (prompt) + cfg->prompt = 1; + + handle_pxe_menu(cmdtp, cfg); + + destroy_pxe_menu(cfg); + + return 0; +} + +U_BOOT_CMD(sysboot, 7, 1, do_sysboot, + "command to get and boot from syslinux files", + "[-p] <interface> <dev[:part]> <ext2|fat|any> [addr] [filename]\n" + " - load and parse syslinux menu file 'filename' from ext2, fat\n" + " or any filesystem on 'dev' on 'interface' to address 'addr'" +); diff --git a/doc/README.asn1 b/doc/README.asn1 new file mode 100644 index 0000000000..1359b93aef --- /dev/null +++ b/doc/README.asn1 @@ -0,0 +1,40 @@ +ASN1 +==== + +Abstract Syntax Notation One (or ASN1) is a standard by ITU-T and ISO/IEC +and used as a description language for defining data structure in +an independent manner. +Any data described in ASN1 notation can be serialized (or encoded) and +de-serialized (or decoded) with well-defined encoding rules. + +A combination of ASN1 compiler and ASN1 decoder library function will +provide a function interface for parsing encoded binary into specific +data structure: +1) define data structure in a text file (*.asn1) +2) define "action" routines for specific "tags" defined in (1) +3) generate bytecode as a C file (*.asn1.[ch]) from *.asn1 file + with ASN1 compiler (tools/asn1_compiler) +4) call a ASN1 decoder (asn1_ber_decoder()) with bytecode and data + +Usage of ASN1 compiler +---------------------- + asn1_compiler [-v] [-d] <grammar-file> <c-file> <hdr-file> + + <grammar-file>: ASN1 input file + <c-file>: generated C file + <hdr-file>: generated include file + +Usage of ASN1 decoder +--------------------- + int asn1_ber_decoder(const struct asn1_decoder *decoder, void *context, + const unsigned char *data, size_t datalen); + + @decoder: bytecode binary + @context: context for decoder + @data: data to be parsed + @datalen: size of data + + +As of writing this, ASN1 compiler and decoder are used to implement +X509 certificate parser, pcks7 message parser and RSA public key parser +for UEFI secure boot. diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 8778cc7b26..89e71cc7eb 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -7,6 +7,7 @@ menu "Real Time Clock" config DM_RTC bool "Enable Driver Model for RTC drivers" depends on DM + select LIB_DATE help Enable drver model for real-time-clock drivers. The RTC uclass then provides the rtc_get()/rtc_set() interface, delegating to diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index f97a669982..e8875ce10f 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -7,7 +7,6 @@ obj-$(CONFIG_$(SPL_TPL_)DM_RTC) += rtc-uclass.o obj-$(CONFIG_RTC_AT91SAM9_RTT) += at91sam9_rtt.o -obj-y += date.o obj-y += rtc-lib.o obj-$(CONFIG_RTC_DAVINCI) += davinci.o obj-$(CONFIG_RTC_DS1302) += ds1302.o diff --git a/fs/ubifs/ubifs.c b/fs/ubifs/ubifs.c index 67a0e8caae..1ffdfe0d90 100644 --- a/fs/ubifs/ubifs.c +++ b/fs/ubifs/ubifs.c @@ -18,6 +18,7 @@ #include "ubifs.h" #include <u-boot/zlib.h> +#include <linux/compat.h> #include <linux/err.h> #include <linux/lzo.h> @@ -70,24 +71,6 @@ struct ubifs_compressor *ubifs_compressors[UBIFS_COMPR_TYPES_CNT]; #ifdef __UBOOT__ -/* from mm/util.c */ - -/** - * kmemdup - duplicate region of memory - * - * @src: memory region to duplicate - * @len: memory region length - * @gfp: GFP mask to use - */ -void *kmemdup(const void *src, size_t len, gfp_t gfp) -{ - void *p; - - p = kmalloc(len, gfp); - if (p) - memcpy(p, src, len); - return p; -} struct crypto_comp { int compressor; diff --git a/include/crypto/internal/rsa.h b/include/crypto/internal/rsa.h new file mode 100644 index 0000000000..e870133f4b --- /dev/null +++ b/include/crypto/internal/rsa.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * RSA internal helpers + * + * Copyright (c) 2015, Intel Corporation + * Authors: Tadeusz Struk <tadeusz.struk@intel.com> + */ +#ifndef _RSA_HELPER_ +#define _RSA_HELPER_ +#include <linux/types.h> + +/** + * rsa_key - RSA key structure + * @n : RSA modulus raw byte stream + * @e : RSA public exponent raw byte stream + * @d : RSA private exponent raw byte stream + * @p : RSA prime factor p of n raw byte stream + * @q : RSA prime factor q of n raw byte stream + * @dp : RSA exponent d mod (p - 1) raw byte stream + * @dq : RSA exponent d mod (q - 1) raw byte stream + * @qinv : RSA CRT coefficient q^(-1) mod p raw byte stream + * @n_sz : length in bytes of RSA modulus n + * @e_sz : length in bytes of RSA public exponent + * @d_sz : length in bytes of RSA private exponent + * @p_sz : length in bytes of p field + * @q_sz : length in bytes of q field + * @dp_sz : length in bytes of dp field + * @dq_sz : length in bytes of dq field + * @qinv_sz : length in bytes of qinv field + */ +struct rsa_key { + const u8 *n; + const u8 *e; + const u8 *d; + const u8 *p; + const u8 *q; + const u8 *dp; + const u8 *dq; + const u8 *qinv; + size_t n_sz; + size_t e_sz; + size_t d_sz; + size_t p_sz; + size_t q_sz; + size_t dp_sz; + size_t dq_sz; + size_t qinv_sz; +}; + +int rsa_parse_pub_key(struct rsa_key *rsa_key, const void *key, + unsigned int key_len); + +int rsa_parse_priv_key(struct rsa_key *rsa_key, const void *key, + unsigned int key_len); + +extern struct crypto_template rsa_pkcs1pad_tmpl; +#endif diff --git a/include/crypto/pkcs7.h b/include/crypto/pkcs7.h new file mode 100644 index 0000000000..8f5c8a7ee3 --- /dev/null +++ b/include/crypto/pkcs7.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* PKCS#7 crypto data parser + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#ifndef _CRYPTO_PKCS7_H +#define _CRYPTO_PKCS7_H + +#ifndef __UBOOT__ +#include <linux/verification.h> +#include <crypto/public_key.h> +#endif + +struct key; +struct pkcs7_message; + +/* + * pkcs7_parser.c + */ +extern struct pkcs7_message *pkcs7_parse_message(const void *data, + size_t datalen); +extern void pkcs7_free_message(struct pkcs7_message *pkcs7); + +extern int pkcs7_get_content_data(const struct pkcs7_message *pkcs7, + const void **_data, size_t *_datalen, + size_t *_headerlen); + +#ifndef __UBOOT__ +/* + * pkcs7_trust.c + */ +extern int pkcs7_validate_trust(struct pkcs7_message *pkcs7, + struct key *trust_keyring); + +/* + * pkcs7_verify.c + */ +extern int pkcs7_verify(struct pkcs7_message *pkcs7, + enum key_being_used_for usage); + +extern int pkcs7_supply_detached_data(struct pkcs7_message *pkcs7, + const void *data, size_t datalen); +#endif + +#endif /* _CRYPTO_PKCS7_H */ diff --git a/include/crypto/public_key.h b/include/crypto/public_key.h new file mode 100644 index 0000000000..436a1ee1ee --- /dev/null +++ b/include/crypto/public_key.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Asymmetric public-key algorithm definitions + * + * See Documentation/crypto/asymmetric-keys.txt + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#ifndef _LINUX_PUBLIC_KEY_H +#define _LINUX_PUBLIC_KEY_H + +#ifdef __UBOOT__ +#include <linux/types.h> +#else +#include <linux/keyctl.h> +#endif +#include <linux/oid_registry.h> + +/* + * Cryptographic data for the public-key subtype of the asymmetric key type. + * + * Note that this may include private part of the key as well as the public + * part. + */ +struct public_key { + void *key; + u32 keylen; + enum OID algo; + void *params; + u32 paramlen; + bool key_is_private; + const char *id_type; + const char *pkey_algo; +}; + +extern void public_key_free(struct public_key *key); + +/* + * Public key cryptography signature data + */ +struct public_key_signature { + struct asymmetric_key_id *auth_ids[2]; + u8 *s; /* Signature */ + u32 s_size; /* Number of bytes in signature */ + u8 *digest; + u8 digest_size; /* Number of bytes in digest */ + const char *pkey_algo; + const char *hash_algo; + const char *encoding; +}; + +extern void public_key_signature_free(struct public_key_signature *sig); + +#ifndef __UBOOT__ +extern struct asymmetric_key_subtype public_key_subtype; + +struct key; +struct key_type; +union key_payload; + +extern int restrict_link_by_signature(struct key *dest_keyring, + const struct key_type *type, + const union key_payload *payload, + struct key *trust_keyring); + +extern int restrict_link_by_key_or_keyring(struct key *dest_keyring, + const struct key_type *type, + const union key_payload *payload, + struct key *trusted); + +extern int restrict_link_by_key_or_keyring_chain(struct key *trust_keyring, + const struct key_type *type, + const union key_payload *payload, + struct key *trusted); + +extern int query_asymmetric_key(const struct kernel_pkey_params *, + struct kernel_pkey_query *); + +extern int encrypt_blob(struct kernel_pkey_params *, const void *, void *); +extern int decrypt_blob(struct kernel_pkey_params *, const void *, void *); +extern int create_signature(struct kernel_pkey_params *, const void *, void *); +extern int verify_signature(const struct key *, + const struct public_key_signature *); + +int public_key_verify_signature(const struct public_key *pkey, + const struct public_key_signature *sig); +#endif /* !__UBOOT__ */ + +#endif /* _LINUX_PUBLIC_KEY_H */ diff --git a/include/env.h b/include/env.h index b72239f6a5..d6c2d751d6 100644 --- a/include/env.h +++ b/include/env.h @@ -113,6 +113,16 @@ int env_match(unsigned char *name, int index); */ char *env_get(const char *varname); +/* + * Like env_get, but prints an error if envvar isn't defined in the + * environment. It always returns what env_get does, so it can be used in + * place of env_get without changing error handling otherwise. + * + * @varname: Variable to look up + * @return value of variable, or NULL if not found + */ +char *from_env(const char *envvar); + /** * env_get_f() - Look up the value of an environment variable (early) * diff --git a/include/keys/asymmetric-type.h b/include/keys/asymmetric-type.h new file mode 100644 index 0000000000..47d83917df --- /dev/null +++ b/include/keys/asymmetric-type.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Asymmetric Public-key cryptography key type interface + * + * See Documentation/crypto/asymmetric-keys.txt + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#ifndef _KEYS_ASYMMETRIC_TYPE_H +#define _KEYS_ASYMMETRIC_TYPE_H + +#ifndef __UBOOT__ +#include <linux/key-type.h> +#include <linux/verification.h> + +extern struct key_type key_type_asymmetric; + +/* + * The key payload is four words. The asymmetric-type key uses them as + * follows: + */ +enum asymmetric_payload_bits { + asym_crypto, /* The data representing the key */ + asym_subtype, /* Pointer to an asymmetric_key_subtype struct */ + asym_key_ids, /* Pointer to an asymmetric_key_ids struct */ + asym_auth /* The key's authorisation (signature, parent key ID) */ +}; +#endif /* !__UBOOT__ */ + +/* + * Identifiers for an asymmetric key ID. We have three ways of looking up a + * key derived from an X.509 certificate: + * + * (1) Serial Number & Issuer. Non-optional. This is the only valid way to + * map a PKCS#7 signature to an X.509 certificate. + * + * (2) Issuer & Subject Unique IDs. Optional. These were the original way to + * match X.509 certificates, but have fallen into disuse in favour of (3). + * + * (3) Auth & Subject Key Identifiers. Optional. SKIDs are only provided on + * CA keys that are intended to sign other keys, so don't appear in end + * user certificates unless forced. + * + * We could also support an PGP key identifier, which is just a SHA1 sum of the + * public key and certain parameters, but since we don't support PGP keys at + * the moment, we shall ignore those. + * + * What we actually do is provide a place where binary identifiers can be + * stashed and then compare against them when checking for an id match. + */ +struct asymmetric_key_id { + unsigned short len; + unsigned char data[]; +}; + +struct asymmetric_key_ids { + void *id[2]; +}; + +extern bool asymmetric_key_id_same(const struct asymmetric_key_id *kid1, + const struct asymmetric_key_id *kid2); + +extern bool asymmetric_key_id_partial(const struct asymmetric_key_id *kid1, + const struct asymmetric_key_id *kid2); + +extern struct asymmetric_key_id *asymmetric_key_generate_id(const void *val_1, + size_t len_1, + const void *val_2, + size_t len_2); +#ifndef __UBOOT__ +static inline +const struct asymmetric_key_ids *asymmetric_key_ids(const struct key *key) +{ + return key->payload.data[asym_key_ids]; +} + +extern struct key *find_asymmetric_key(struct key *keyring, + const struct asymmetric_key_id *id_0, + const struct asymmetric_key_id *id_1, + bool partial); +#endif + +/* + * The payload is at the discretion of the subtype. + */ + +#endif /* _KEYS_ASYMMETRIC_TYPE_H */ diff --git a/include/linux/asn1.h b/include/linux/asn1.h new file mode 100644 index 0000000000..a4d0bdd107 --- /dev/null +++ b/include/linux/asn1.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* ASN.1 BER/DER/CER encoding definitions + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#ifndef _LINUX_ASN1_H +#define _LINUX_ASN1_H + +/* Class */ +enum asn1_class { + ASN1_UNIV = 0, /* Universal */ + ASN1_APPL = 1, /* Application */ + ASN1_CONT = 2, /* Context */ + ASN1_PRIV = 3 /* Private */ +}; +#define ASN1_CLASS_BITS 0xc0 + + +enum asn1_method { + ASN1_PRIM = 0, /* Primitive */ + ASN1_CONS = 1 /* Constructed */ +}; +#define ASN1_CONS_BIT 0x20 + +/* Tag */ +enum asn1_tag { + ASN1_EOC = 0, /* End Of Contents or N/A */ + ASN1_BOOL = 1, /* Boolean */ + ASN1_INT = 2, /* Integer */ + ASN1_BTS = 3, /* Bit String */ + ASN1_OTS = 4, /* Octet String */ + ASN1_NULL = 5, /* Null */ + ASN1_OID = 6, /* Object Identifier */ + ASN1_ODE = 7, /* Object Description */ + ASN1_EXT = 8, /* External */ + ASN1_REAL = 9, /* Real float */ + ASN1_ENUM = 10, /* Enumerated */ + ASN1_EPDV = 11, /* Embedded PDV */ + ASN1_UTF8STR = 12, /* UTF8 String */ + ASN1_RELOID = 13, /* Relative OID */ + /* 14 - Reserved */ + /* 15 - Reserved */ + ASN1_SEQ = 16, /* Sequence and Sequence of */ + ASN1_SET = 17, /* Set and Set of */ + ASN1_NUMSTR = 18, /* Numerical String */ + ASN1_PRNSTR = 19, /* Printable String */ + ASN1_TEXSTR = 20, /* T61 String / Teletext String */ + ASN1_VIDSTR = 21, /* Videotex String */ + ASN1_IA5STR = 22, /* IA5 String */ + ASN1_UNITIM = 23, /* Universal Time */ + ASN1_GENTIM = 24, /* General Time */ + ASN1_GRASTR = 25, /* Graphic String */ + ASN1_VISSTR = 26, /* Visible String */ + ASN1_GENSTR = 27, /* General String */ + ASN1_UNISTR = 28, /* Universal String */ + ASN1_CHRSTR = 29, /* Character String */ + ASN1_BMPSTR = 30, /* BMP String */ + ASN1_LONG_TAG = 31 /* Long form tag */ +}; + +#define ASN1_INDEFINITE_LENGTH 0x80 + +#endif /* _LINUX_ASN1_H */ diff --git a/include/linux/asn1_ber_bytecode.h b/include/linux/asn1_ber_bytecode.h new file mode 100644 index 0000000000..b38361953a --- /dev/null +++ b/include/linux/asn1_ber_bytecode.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* ASN.1 BER/DER/CER parsing state machine internal definitions + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#ifndef _LINUX_ASN1_BER_BYTECODE_H +#define _LINUX_ASN1_BER_BYTECODE_H + +#ifdef __KERNEL__ +#include <linux/types.h> +#endif +#include <linux/asn1.h> + +typedef int (*asn1_action_t)(void *context, + size_t hdrlen, /* In case of ANY type */ + unsigned char tag, /* In case of ANY type */ + const void *value, size_t vlen); + +struct asn1_decoder { + const unsigned char *machine; + size_t machlen; + const asn1_action_t *actions; +}; + +enum asn1_opcode { + /* The tag-matching ops come first and the odd-numbered slots + * are for OR_SKIP ops. + */ +#define ASN1_OP_MATCH__SKIP 0x01 +#define ASN1_OP_MATCH__ACT 0x02 +#define ASN1_OP_MATCH__JUMP 0x04 +#define ASN1_OP_MATCH__ANY 0x08 +#define ASN1_OP_MATCH__COND 0x10 + + ASN1_OP_MATCH = 0x00, + ASN1_OP_MATCH_OR_SKIP = 0x01, + ASN1_OP_MATCH_ACT = 0x02, + ASN1_OP_MATCH_ACT_OR_SKIP = 0x03, + ASN1_OP_MATCH_JUMP = 0x04, + ASN1_OP_MATCH_JUMP_OR_SKIP = 0x05, + ASN1_OP_MATCH_ANY = 0x08, + ASN1_OP_MATCH_ANY_OR_SKIP = 0x09, + ASN1_OP_MATCH_ANY_ACT = 0x0a, + ASN1_OP_MATCH_ANY_ACT_OR_SKIP = 0x0b, + /* Everything before here matches unconditionally */ + + ASN1_OP_COND_MATCH_OR_SKIP = 0x11, + ASN1_OP_COND_MATCH_ACT_OR_SKIP = 0x13, + ASN1_OP_COND_MATCH_JUMP_OR_SKIP = 0x15, + ASN1_OP_COND_MATCH_ANY = 0x18, + ASN1_OP_COND_MATCH_ANY_OR_SKIP = 0x19, + ASN1_OP_COND_MATCH_ANY_ACT = 0x1a, + ASN1_OP_COND_MATCH_ANY_ACT_OR_SKIP = 0x1b, + + /* Everything before here will want a tag from the data */ +#define ASN1_OP__MATCHES_TAG ASN1_OP_COND_MATCH_ANY_ACT_OR_SKIP + + /* These are here to help fill up space */ + ASN1_OP_COND_FAIL = 0x1c, + ASN1_OP_COMPLETE = 0x1d, + ASN1_OP_ACT = 0x1e, + ASN1_OP_MAYBE_ACT = 0x1f, + + /* The following eight have bit 0 -> SET, 1 -> OF, 2 -> ACT */ + ASN1_OP_END_SEQ = 0x20, + ASN1_OP_END_SET = 0x21, + ASN1_OP_END_SEQ_OF = 0x22, + ASN1_OP_END_SET_OF = 0x23, + ASN1_OP_END_SEQ_ACT = 0x24, + ASN1_OP_END_SET_ACT = 0x25, + ASN1_OP_END_SEQ_OF_ACT = 0x26, + ASN1_OP_END_SET_OF_ACT = 0x27, +#define ASN1_OP_END__SET 0x01 +#define ASN1_OP_END__OF 0x02 +#define ASN1_OP_END__ACT 0x04 + + ASN1_OP_RETURN = 0x28, + + ASN1_OP__NR +}; + +#define _tag(CLASS, CP, TAG) ((ASN1_##CLASS << 6) | (ASN1_##CP << 5) | ASN1_##TAG) +#define _tagn(CLASS, CP, TAG) ((ASN1_##CLASS << 6) | (ASN1_##CP << 5) | TAG) +#define _jump_target(N) (N) +#define _action(N) (N) + +#endif /* _LINUX_ASN1_BER_BYTECODE_H */ diff --git a/include/linux/asn1_decoder.h b/include/linux/asn1_decoder.h new file mode 100644 index 0000000000..83f9c6e1e5 --- /dev/null +++ b/include/linux/asn1_decoder.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* ASN.1 decoder + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#ifndef _LINUX_ASN1_DECODER_H +#define _LINUX_ASN1_DECODER_H + +#include <linux/asn1.h> + +struct asn1_decoder; + +extern int asn1_ber_decoder(const struct asn1_decoder *decoder, + void *context, + const unsigned char *data, + size_t datalen); + +#endif /* _LINUX_ASN1_DECODER_H */ diff --git a/include/linux/kernel.h b/include/linux/kernel.h index 5c7e5f635b..564819a1c0 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -1,8 +1,8 @@ #ifndef _LINUX_KERNEL_H #define _LINUX_KERNEL_H - #include <linux/types.h> +#include <linux/printk.h> /* for printf/pr_* utilities */ #define USHRT_MAX ((u16)(~0U)) #define SHRT_MAX ((s16)(USHRT_MAX>>1)) diff --git a/include/linux/oid_registry.h b/include/linux/oid_registry.h new file mode 100644 index 0000000000..657d6bf2c0 --- /dev/null +++ b/include/linux/oid_registry.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* ASN.1 Object identifier (OID) registry + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#ifndef _LINUX_OID_REGISTRY_H +#define _LINUX_OID_REGISTRY_H + +#include <linux/types.h> + +/* + * OIDs are turned into these values if possible, or OID__NR if not held here. + * + * NOTE! Do not mess with the format of each line as this is read by + * build_OID_registry.pl to generate the data for look_up_OID(). + */ +enum OID { + OID_id_dsa_with_sha1, /* 1.2.840.10030.4.3 */ + OID_id_dsa, /* 1.2.840.10040.4.1 */ + OID_id_ecdsa_with_sha1, /* 1.2.840.10045.4.1 */ + OID_id_ecPublicKey, /* 1.2.840.10045.2.1 */ + + /* PKCS#1 {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1)} */ + OID_rsaEncryption, /* 1.2.840.113549.1.1.1 */ + OID_md2WithRSAEncryption, /* 1.2.840.113549.1.1.2 */ + OID_md3WithRSAEncryption, /* 1.2.840.113549.1.1.3 */ + OID_md4WithRSAEncryption, /* 1.2.840.113549.1.1.4 */ + OID_sha1WithRSAEncryption, /* 1.2.840.113549.1.1.5 */ + OID_sha256WithRSAEncryption, /* 1.2.840.113549.1.1.11 */ + OID_sha384WithRSAEncryption, /* 1.2.840.113549.1.1.12 */ + OID_sha512WithRSAEncryption, /* 1.2.840.113549.1.1.13 */ + OID_sha224WithRSAEncryption, /* 1.2.840.113549.1.1.14 */ + /* PKCS#7 {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-7(7)} */ + OID_data, /* 1.2.840.113549.1.7.1 */ + OID_signed_data, /* 1.2.840.113549.1.7.2 */ + /* PKCS#9 {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9)} */ + OID_email_address, /* 1.2.840.113549.1.9.1 */ + OID_contentType, /* 1.2.840.113549.1.9.3 */ + OID_messageDigest, /* 1.2.840.113549.1.9.4 */ + OID_signingTime, /* 1.2.840.113549.1.9.5 */ + OID_smimeCapabilites, /* 1.2.840.113549.1.9.15 */ + OID_smimeAuthenticatedAttrs, /* 1.2.840.113549.1.9.16.2.11 */ + + /* {iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2)} */ + OID_md2, /* 1.2.840.113549.2.2 */ + OID_md4, /* 1.2.840.113549.2.4 */ + OID_md5, /* 1.2.840.113549.2.5 */ + + /* Microsoft Authenticode & Software Publishing */ + OID_msIndirectData, /* 1.3.6.1.4.1.311.2.1.4 */ + OID_msStatementType, /* 1.3.6.1.4.1.311.2.1.11 */ + OID_msSpOpusInfo, /* 1.3.6.1.4.1.311.2.1.12 */ + OID_msPeImageDataObjId, /* 1.3.6.1.4.1.311.2.1.15 */ + OID_msIndividualSPKeyPurpose, /* 1.3.6.1.4.1.311.2.1.21 */ + OID_msOutlookExpress, /* 1.3.6.1.4.1.311.16.4 */ + + OID_certAuthInfoAccess, /* 1.3.6.1.5.5.7.1.1 */ + OID_sha1, /* 1.3.14.3.2.26 */ + OID_sha256, /* 2.16.840.1.101.3.4.2.1 */ + OID_sha384, /* 2.16.840.1.101.3.4.2.2 */ + OID_sha512, /* 2.16.840.1.101.3.4.2.3 */ + OID_sha224, /* 2.16.840.1.101.3.4.2.4 */ + + /* Distinguished Name attribute IDs [RFC 2256] */ + OID_commonName, /* 2.5.4.3 */ + OID_surname, /* 2.5.4.4 */ + OID_countryName, /* 2.5.4.6 */ + OID_locality, /* 2.5.4.7 */ + OID_stateOrProvinceName, /* 2.5.4.8 */ + OID_organizationName, /* 2.5.4.10 */ + OID_organizationUnitName, /* 2.5.4.11 */ + OID_title, /* 2.5.4.12 */ + OID_description, /* 2.5.4.13 */ + OID_name, /* 2.5.4.41 */ + OID_givenName, /* 2.5.4.42 */ + OID_initials, /* 2.5.4.43 */ + OID_generationalQualifier, /* 2.5.4.44 */ + + /* Certificate extension IDs */ + OID_subjectKeyIdentifier, /* 2.5.29.14 */ + OID_keyUsage, /* 2.5.29.15 */ + OID_subjectAltName, /* 2.5.29.17 */ + OID_issuerAltName, /* 2.5.29.18 */ + OID_basicConstraints, /* 2.5.29.19 */ + OID_crlDistributionPoints, /* 2.5.29.31 */ + OID_certPolicies, /* 2.5.29.32 */ + OID_authorityKeyIdentifier, /* 2.5.29.35 */ + OID_extKeyUsage, /* 2.5.29.37 */ + + /* EC-RDSA */ + OID_gostCPSignA, /* 1.2.643.2.2.35.1 */ + OID_gostCPSignB, /* 1.2.643.2.2.35.2 */ + OID_gostCPSignC, /* 1.2.643.2.2.35.3 */ + OID_gost2012PKey256, /* 1.2.643.7.1.1.1.1 */ + OID_gost2012PKey512, /* 1.2.643.7.1.1.1.2 */ + OID_gost2012Digest256, /* 1.2.643.7.1.1.2.2 */ + OID_gost2012Digest512, /* 1.2.643.7.1.1.2.3 */ + OID_gost2012Signature256, /* 1.2.643.7.1.1.3.2 */ + OID_gost2012Signature512, /* 1.2.643.7.1.1.3.3 */ + OID_gostTC26Sign256A, /* 1.2.643.7.1.2.1.1.1 */ + OID_gostTC26Sign256B, /* 1.2.643.7.1.2.1.1.2 */ + OID_gostTC26Sign256C, /* 1.2.643.7.1.2.1.1.3 */ + OID_gostTC26Sign256D, /* 1.2.643.7.1.2.1.1.4 */ + OID_gostTC26Sign512A, /* 1.2.643.7.1.2.1.2.1 */ + OID_gostTC26Sign512B, /* 1.2.643.7.1.2.1.2.2 */ + OID_gostTC26Sign512C, /* 1.2.643.7.1.2.1.2.3 */ + + OID__NR +}; + +extern enum OID look_up_OID(const void *data, size_t datasize); +extern int sprint_oid(const void *, size_t, char *, size_t); +extern int sprint_OID(enum OID, char *, size_t); + +#endif /* _LINUX_OID_REGISTRY_H */ diff --git a/include/linux/time.h b/include/linux/time.h index b8d298eb4d..702dd276ae 100644 --- a/include/linux/time.h +++ b/include/linux/time.h @@ -1,6 +1,8 @@ #ifndef _LINUX_TIME_H #define _LINUX_TIME_H +#include <rtc.h> +#include <vsprintf.h> #include <linux/types.h> #define _DEFUN(a,b,c) a(c) @@ -150,4 +152,13 @@ _DEFUN (ctime_r, (tim_p, result), return asctime_r (localtime_r (tim_p, &tm), result); } +/* for compatibility with linux code */ +typedef __s64 time64_t; + +#ifdef CONFIG_LIB_DATE +time64_t mktime64(const unsigned int year, const unsigned int mon, + const unsigned int day, const unsigned int hour, + const unsigned int min, const unsigned int sec); +#endif + #endif diff --git a/include/rtc.h b/include/rtc.h index 7386d52db1..8aabfc1162 100644 --- a/include/rtc.h +++ b/include/rtc.h @@ -18,6 +18,8 @@ #ifdef CONFIG_DM_RTC +struct udevice; + struct rtc_ops { /** * get() - get the current time diff --git a/lib/Kconfig b/lib/Kconfig index 55dedcbcdd..965cf7bc03 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -265,6 +265,7 @@ config AES present. source lib/rsa/Kconfig +source lib/crypto/Kconfig config TPM bool "Trusted Platform Module (TPM) Support" @@ -566,6 +567,19 @@ config SMBIOS_PRODUCT_NAME endmenu +config ASN1_COMPILER + bool + +config ASN1_DECODER + bool + help + Enable asn1 decoder library. + +config OID_REGISTRY + bool + help + Enable fast lookup object identifier registry. + source lib/efi/Kconfig source lib/efi_loader/Kconfig source lib/optee/Kconfig @@ -574,4 +588,7 @@ config TEST_FDTDEC bool "enable fdtdec test" depends on OF_LIBFDT +config LIB_DATE + bool + endmenu diff --git a/lib/Makefile b/lib/Makefile index e15a189f65..1fb650cd90 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -17,6 +17,8 @@ obj-$(CONFIG_OF_LIVE) += of_live.o obj-$(CONFIG_CMD_DHRYSTONE) += dhry/ obj-$(CONFIG_ARCH_AT91) += at91/ obj-$(CONFIG_OPTEE) += optee/ +obj-$(CONFIG_ASN1_DECODER) += asn1_decoder.o +obj-y += crypto/ obj-$(CONFIG_AES) += aes.o @@ -117,4 +119,22 @@ else obj-y += vsprintf.o strto.o endif +obj-y += date.o + +# +# Build a fast OID lookup registry from include/linux/oid_registry.h +# +obj-$(CONFIG_OID_REGISTRY) += oid_registry.o + +$(obj)/oid_registry.o: $(obj)/oid_registry_data.c + +$(obj)/oid_registry_data.c: $(srctree)/include/linux/oid_registry.h \ + $(srctree)/scripts/build_OID_registry + $(call cmd,build_OID_registry) + +quiet_cmd_build_OID_registry = GEN $@ + cmd_build_OID_registry = perl $(srctree)/scripts/build_OID_registry $< $@ + +clean-files += oid_registry_data.c + subdir-ccflags-$(CONFIG_CC_OPTIMIZE_LIBS_FOR_SPEED) += -O2 diff --git a/lib/asn1_decoder.c b/lib/asn1_decoder.c new file mode 100644 index 0000000000..db222625dd --- /dev/null +++ b/lib/asn1_decoder.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Decoder for ASN.1 BER/DER/CER encoded bytestream + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#ifdef __UBOOT__ +#include <linux/compat.h> +#else +#include <linux/export.h> +#endif +#include <linux/kernel.h> +#include <linux/errno.h> +#ifndef __UBOOT__ +#include <linux/module.h> +#endif +#include <linux/asn1_decoder.h> +#include <linux/asn1_ber_bytecode.h> + +static const unsigned char asn1_op_lengths[ASN1_OP__NR] = { + /* OPC TAG JMP ACT */ + [ASN1_OP_MATCH] = 1 + 1, + [ASN1_OP_MATCH_OR_SKIP] = 1 + 1, + [ASN1_OP_MATCH_ACT] = 1 + 1 + 1, + [ASN1_OP_MATCH_ACT_OR_SKIP] = 1 + 1 + 1, + [ASN1_OP_MATCH_JUMP] = 1 + 1 + 1, + [ASN1_OP_MATCH_JUMP_OR_SKIP] = 1 + 1 + 1, + [ASN1_OP_MATCH_ANY] = 1, + [ASN1_OP_MATCH_ANY_OR_SKIP] = 1, + [ASN1_OP_MATCH_ANY_ACT] = 1 + 1, + [ASN1_OP_MATCH_ANY_ACT_OR_SKIP] = 1 + 1, + [ASN1_OP_COND_MATCH_OR_SKIP] = 1 + 1, + [ASN1_OP_COND_MATCH_ACT_OR_SKIP] = 1 + 1 + 1, + [ASN1_OP_COND_MATCH_JUMP_OR_SKIP] = 1 + 1 + 1, + [ASN1_OP_COND_MATCH_ANY] = 1, + [ASN1_OP_COND_MATCH_ANY_OR_SKIP] = 1, + [ASN1_OP_COND_MATCH_ANY_ACT] = 1 + 1, + [ASN1_OP_COND_MATCH_ANY_ACT_OR_SKIP] = 1 + 1, + [ASN1_OP_COND_FAIL] = 1, + [ASN1_OP_COMPLETE] = 1, + [ASN1_OP_ACT] = 1 + 1, + [ASN1_OP_MAYBE_ACT] = 1 + 1, + [ASN1_OP_RETURN] = 1, + [ASN1_OP_END_SEQ] = 1, + [ASN1_OP_END_SEQ_OF] = 1 + 1, + [ASN1_OP_END_SET] = 1, + [ASN1_OP_END_SET_OF] = 1 + 1, + [ASN1_OP_END_SEQ_ACT] = 1 + 1, + [ASN1_OP_END_SEQ_OF_ACT] = 1 + 1 + 1, + [ASN1_OP_END_SET_ACT] = 1 + 1, + [ASN1_OP_END_SET_OF_ACT] = 1 + 1 + 1, +}; + +/* + * Find the length of an indefinite length object + * @data: The data buffer + * @datalen: The end of the innermost containing element in the buffer + * @_dp: The data parse cursor (updated before returning) + * @_len: Where to return the size of the element. + * @_errmsg: Where to return a pointer to an error message on error + */ +static int asn1_find_indefinite_length(const unsigned char *data, size_t datalen, + size_t *_dp, size_t *_len, + const char **_errmsg) +{ + unsigned char tag, tmp; + size_t dp = *_dp, len, n; + int indef_level = 1; + +next_tag: + if (unlikely(datalen - dp < 2)) { + if (datalen == dp) + goto missing_eoc; + goto data_overrun_error; + } + + /* Extract a tag from the data */ + tag = data[dp++]; + if (tag == ASN1_EOC) { + /* It appears to be an EOC. */ + if (data[dp++] != 0) + goto invalid_eoc; + if (--indef_level <= 0) { + *_len = dp - *_dp; + *_dp = dp; + return 0; + } + goto next_tag; + } + + if (unlikely((tag & 0x1f) == ASN1_LONG_TAG)) { + do { + if (unlikely(datalen - dp < 2)) + goto data_overrun_error; + tmp = data[dp++]; + } while (tmp & 0x80); + } + + /* Extract the length */ + len = data[dp++]; + if (len <= 0x7f) + goto check_length; + + if (unlikely(len == ASN1_INDEFINITE_LENGTH)) { + /* Indefinite length */ + if (unlikely((tag & ASN1_CONS_BIT) == ASN1_PRIM << 5)) + goto indefinite_len_primitive; + indef_level++; + goto next_tag; + } + + n = len - 0x80; + if (unlikely(n > sizeof(len) - 1)) + goto length_too_long; + if (unlikely(n > datalen - dp)) + goto data_overrun_error; + len = 0; + for (; n > 0; n--) { + len <<= 8; + len |= data[dp++]; + } +check_length: + if (len > datalen - dp) + goto data_overrun_error; + dp += len; + goto next_tag; + +length_too_long: + *_errmsg = "Unsupported length"; + goto error; +indefinite_len_primitive: + *_errmsg = "Indefinite len primitive not permitted"; + goto error; +invalid_eoc: + *_errmsg = "Invalid length EOC"; + goto error; +data_overrun_error: + *_errmsg = "Data overrun error"; + goto error; +missing_eoc: + *_errmsg = "Missing EOC in indefinite len cons"; +error: + *_dp = dp; + return -1; +} + +/** + * asn1_ber_decoder - Decoder BER/DER/CER ASN.1 according to pattern + * @decoder: The decoder definition (produced by asn1_compiler) + * @context: The caller's context (to be passed to the action functions) + * @data: The encoded data + * @datalen: The size of the encoded data + * + * Decode BER/DER/CER encoded ASN.1 data according to a bytecode pattern + * produced by asn1_compiler. Action functions are called on marked tags to + * allow the caller to retrieve significant data. + * + * LIMITATIONS: + * + * To keep down the amount of stack used by this function, the following limits + * have been imposed: + * + * (1) This won't handle datalen > 65535 without increasing the size of the + * cons stack elements and length_too_long checking. + * + * (2) The stack of constructed types is 10 deep. If the depth of non-leaf + * constructed types exceeds this, the decode will fail. + * + * (3) The SET type (not the SET OF type) isn't really supported as tracking + * what members of the set have been seen is a pain. + */ +int asn1_ber_decoder(const struct asn1_decoder *decoder, + void *context, + const unsigned char *data, + size_t datalen) +{ + const unsigned char *machine = decoder->machine; + const asn1_action_t *actions = decoder->actions; + size_t machlen = decoder->machlen; + enum asn1_opcode op; + unsigned char tag = 0, csp = 0, jsp = 0, optag = 0, hdr = 0; + const char *errmsg; + size_t pc = 0, dp = 0, tdp = 0, len = 0; + int ret; + + unsigned char flags = 0; +#define FLAG_INDEFINITE_LENGTH 0x01 +#define FLAG_MATCHED 0x02 +#define FLAG_LAST_MATCHED 0x04 /* Last tag matched */ +#define FLAG_CONS 0x20 /* Corresponds to CONS bit in the opcode tag + * - ie. whether or not we are going to parse + * a compound type. + */ + +#define NR_CONS_STACK 10 + unsigned short cons_dp_stack[NR_CONS_STACK]; + unsigned short cons_datalen_stack[NR_CONS_STACK]; + unsigned char cons_hdrlen_stack[NR_CONS_STACK]; +#define NR_JUMP_STACK 10 + unsigned char jump_stack[NR_JUMP_STACK]; + + if (datalen > 65535) + return -EMSGSIZE; + +next_op: + pr_debug("next_op: pc=\e[32m%zu\e[m/%zu dp=\e[33m%zu\e[m/%zu C=%d J=%d\n", + pc, machlen, dp, datalen, csp, jsp); + if (unlikely(pc >= machlen)) + goto machine_overrun_error; + op = machine[pc]; + if (unlikely(pc + asn1_op_lengths[op] > machlen)) + goto machine_overrun_error; + + /* If this command is meant to match a tag, then do that before + * evaluating the command. + */ + if (op <= ASN1_OP__MATCHES_TAG) { + unsigned char tmp; + + /* Skip conditional matches if possible */ + if ((op & ASN1_OP_MATCH__COND && flags & FLAG_MATCHED) || + (op & ASN1_OP_MATCH__SKIP && dp == datalen)) { + flags &= ~FLAG_LAST_MATCHED; + pc += asn1_op_lengths[op]; + goto next_op; + } + + flags = 0; + hdr = 2; + + /* Extract a tag from the data */ + if (unlikely(datalen - dp < 2)) + goto data_overrun_error; + tag = data[dp++]; + if (unlikely((tag & 0x1f) == ASN1_LONG_TAG)) + goto long_tag_not_supported; + + if (op & ASN1_OP_MATCH__ANY) { + pr_debug("- any %02x\n", tag); + } else { + /* Extract the tag from the machine + * - Either CONS or PRIM are permitted in the data if + * CONS is not set in the op stream, otherwise CONS + * is mandatory. + */ + optag = machine[pc + 1]; + flags |= optag & FLAG_CONS; + + /* Determine whether the tag matched */ + tmp = optag ^ tag; + tmp &= ~(optag & ASN1_CONS_BIT); + pr_debug("- match? %02x %02x %02x\n", tag, optag, tmp); + if (tmp != 0) { + /* All odd-numbered tags are MATCH_OR_SKIP. */ + if (op & ASN1_OP_MATCH__SKIP) { + pc += asn1_op_lengths[op]; + dp--; + goto next_op; + } + goto tag_mismatch; + } + } + flags |= FLAG_MATCHED; + + len = data[dp++]; + if (len > 0x7f) { + if (unlikely(len == ASN1_INDEFINITE_LENGTH)) { + /* Indefinite length */ + if (unlikely(!(tag & ASN1_CONS_BIT))) + goto indefinite_len_primitive; + flags |= FLAG_INDEFINITE_LENGTH; + if (unlikely(2 > datalen - dp)) + goto data_overrun_error; + } else { + int n = len - 0x80; + if (unlikely(n > 2)) + goto length_too_long; + if (unlikely(n > datalen - dp)) + goto data_overrun_error; + hdr += n; + for (len = 0; n > 0; n--) { + len <<= 8; + len |= data[dp++]; + } + if (unlikely(len > datalen - dp)) + goto data_overrun_error; + } + } else { + if (unlikely(len > datalen - dp)) + goto data_overrun_error; + } + + if (flags & FLAG_CONS) { + /* For expected compound forms, we stack the positions + * of the start and end of the data. + */ + if (unlikely(csp >= NR_CONS_STACK)) + goto cons_stack_overflow; + cons_dp_stack[csp] = dp; + cons_hdrlen_stack[csp] = hdr; + if (!(flags & FLAG_INDEFINITE_LENGTH)) { + cons_datalen_stack[csp] = datalen; + datalen = dp + len; + } else { + cons_datalen_stack[csp] = 0; + } + csp++; + } + + pr_debug("- TAG: %02x %zu%s\n", + tag, len, flags & FLAG_CONS ? " CONS" : ""); + tdp = dp; + } + + /* Decide how to handle the operation */ + switch (op) { + case ASN1_OP_MATCH: + case ASN1_OP_MATCH_OR_SKIP: + case ASN1_OP_MATCH_ACT: + case ASN1_OP_MATCH_ACT_OR_SKIP: + case ASN1_OP_MATCH_ANY: + case ASN1_OP_MATCH_ANY_OR_SKIP: + case ASN1_OP_MATCH_ANY_ACT: + case ASN1_OP_MATCH_ANY_ACT_OR_SKIP: + case ASN1_OP_COND_MATCH_OR_SKIP: + case ASN1_OP_COND_MATCH_ACT_OR_SKIP: + case ASN1_OP_COND_MATCH_ANY: + case ASN1_OP_COND_MATCH_ANY_OR_SKIP: + case ASN1_OP_COND_MATCH_ANY_ACT: + case ASN1_OP_COND_MATCH_ANY_ACT_OR_SKIP: + + if (!(flags & FLAG_CONS)) { + if (flags & FLAG_INDEFINITE_LENGTH) { + size_t tmp = dp; + + ret = asn1_find_indefinite_length( + data, datalen, &tmp, &len, &errmsg); + if (ret < 0) + goto error; + } + pr_debug("- LEAF: %zu\n", len); + } + + if (op & ASN1_OP_MATCH__ACT) { + unsigned char act; + + if (op & ASN1_OP_MATCH__ANY) + act = machine[pc + 1]; + else + act = machine[pc + 2]; + ret = actions[act](context, hdr, tag, data + dp, len); + if (ret < 0) + return ret; + } + + if (!(flags & FLAG_CONS)) + dp += len; + pc += asn1_op_lengths[op]; + goto next_op; + + case ASN1_OP_MATCH_JUMP: + case ASN1_OP_MATCH_JUMP_OR_SKIP: + case ASN1_OP_COND_MATCH_JUMP_OR_SKIP: + pr_debug("- MATCH_JUMP\n"); + if (unlikely(jsp == NR_JUMP_STACK)) + goto jump_stack_overflow; + jump_stack[jsp++] = pc + asn1_op_lengths[op]; + pc = machine[pc + 2]; + goto next_op; + + case ASN1_OP_COND_FAIL: + if (unlikely(!(flags & FLAG_MATCHED))) + goto tag_mismatch; + pc += asn1_op_lengths[op]; + goto next_op; + + case ASN1_OP_COMPLETE: + if (unlikely(jsp != 0 || csp != 0)) { + pr_err("ASN.1 decoder error: Stacks not empty at completion (%u, %u)\n", + jsp, csp); + return -EBADMSG; + } + return 0; + + case ASN1_OP_END_SET: + case ASN1_OP_END_SET_ACT: + if (unlikely(!(flags & FLAG_MATCHED))) + goto tag_mismatch; + /* fall through */ + + case ASN1_OP_END_SEQ: + case ASN1_OP_END_SET_OF: + case ASN1_OP_END_SEQ_OF: + case ASN1_OP_END_SEQ_ACT: + case ASN1_OP_END_SET_OF_ACT: + case ASN1_OP_END_SEQ_OF_ACT: + if (unlikely(csp <= 0)) + goto cons_stack_underflow; + csp--; + tdp = cons_dp_stack[csp]; + hdr = cons_hdrlen_stack[csp]; + len = datalen; + datalen = cons_datalen_stack[csp]; + pr_debug("- end cons t=%zu dp=%zu l=%zu/%zu\n", + tdp, dp, len, datalen); + if (datalen == 0) { + /* Indefinite length - check for the EOC. */ + datalen = len; + if (unlikely(datalen - dp < 2)) + goto data_overrun_error; + if (data[dp++] != 0) { + if (op & ASN1_OP_END__OF) { + dp--; + csp++; + pc = machine[pc + 1]; + pr_debug("- continue\n"); + goto next_op; + } + goto missing_eoc; + } + if (data[dp++] != 0) + goto invalid_eoc; + len = dp - tdp - 2; + } else { + if (dp < len && (op & ASN1_OP_END__OF)) { + datalen = len; + csp++; + pc = machine[pc + 1]; + pr_debug("- continue\n"); + goto next_op; + } + if (dp != len) + goto cons_length_error; + len -= tdp; + pr_debug("- cons len l=%zu d=%zu\n", len, dp - tdp); + } + + if (op & ASN1_OP_END__ACT) { + unsigned char act; + if (op & ASN1_OP_END__OF) + act = machine[pc + 2]; + else + act = machine[pc + 1]; + ret = actions[act](context, hdr, 0, data + tdp, len); + if (ret < 0) + return ret; + } + pc += asn1_op_lengths[op]; + goto next_op; + + case ASN1_OP_MAYBE_ACT: + if (!(flags & FLAG_LAST_MATCHED)) { + pc += asn1_op_lengths[op]; + goto next_op; + } + /* fall through */ + + case ASN1_OP_ACT: + ret = actions[machine[pc + 1]](context, hdr, tag, data + tdp, len); + if (ret < 0) + return ret; + pc += asn1_op_lengths[op]; + goto next_op; + + case ASN1_OP_RETURN: + if (unlikely(jsp <= 0)) + goto jump_stack_underflow; + pc = jump_stack[--jsp]; + flags |= FLAG_MATCHED | FLAG_LAST_MATCHED; + goto next_op; + + default: + break; + } + + /* Shouldn't reach here */ + pr_err("ASN.1 decoder error: Found reserved opcode (%u) pc=%zu\n", + op, pc); + return -EBADMSG; + +data_overrun_error: + errmsg = "Data overrun error"; + goto error; +machine_overrun_error: + errmsg = "Machine overrun error"; + goto error; +jump_stack_underflow: + errmsg = "Jump stack underflow"; + goto error; +jump_stack_overflow: + errmsg = "Jump stack overflow"; + goto error; +cons_stack_underflow: + errmsg = "Cons stack underflow"; + goto error; +cons_stack_overflow: + errmsg = "Cons stack overflow"; + goto error; +cons_length_error: + errmsg = "Cons length error"; + goto error; +missing_eoc: + errmsg = "Missing EOC in indefinite len cons"; + goto error; +invalid_eoc: + errmsg = "Invalid length EOC"; + goto error; +length_too_long: + errmsg = "Unsupported length"; + goto error; +indefinite_len_primitive: + errmsg = "Indefinite len primitive not permitted"; + goto error; +tag_mismatch: + errmsg = "Unexpected tag"; + goto error; +long_tag_not_supported: + errmsg = "Long tag not supported"; +error: + pr_debug("\nASN1: %s [m=%zu d=%zu ot=%02x t=%02x l=%zu]\n", + errmsg, pc, dp, optag, tag, len); + return -EBADMSG; +} +EXPORT_SYMBOL_GPL(asn1_ber_decoder); + +MODULE_LICENSE("GPL"); diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig new file mode 100644 index 0000000000..2b221b915a --- /dev/null +++ b/lib/crypto/Kconfig @@ -0,0 +1,52 @@ +menuconfig ASYMMETRIC_KEY_TYPE + bool "Asymmetric (public-key cryptographic) key Support" + help + This option provides support for a key type that holds the data for + the asymmetric keys used for public key cryptographic operations such + as encryption, decryption, signature generation and signature + verification. + +if ASYMMETRIC_KEY_TYPE + +config ASYMMETRIC_PUBLIC_KEY_SUBTYPE + bool "Asymmetric public-key crypto algorithm subtype" + help + This option provides support for asymmetric public key type handling. + If signature generation and/or verification are to be used, + appropriate hash algorithms (such as SHA-1) must be available. + ENOPKG will be reported if the requisite algorithm is unavailable. + +config RSA_PUBLIC_KEY_PARSER + bool "RSA public key parser" + depends on ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select ASN1_DECODER + select ASN1_COMPILER + select OID_REGISTRY + help + This option provides support for parsing a blob containing RSA + public key data and provides the ability to instantiate a public + key. + +config X509_CERTIFICATE_PARSER + bool "X.509 certificate parser" + depends on ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select ASN1_DECODER + select ASN1_COMPILER + select OID_REGISTRY + select LIB_DATE + help + This option provides support for parsing X.509 format blobs for key + data and provides the ability to instantiate a crypto key from a + public key packet found inside the certificate. + +config PKCS7_MESSAGE_PARSER + bool "PKCS#7 message parser" + depends on X509_CERTIFICATE_PARSER + select ASN1_DECODER + select ASN1_COMPILER + select OID_REGISTRY + help + This option provides support for parsing PKCS#7 format messages for + signature data and provides the ability to verify the signature. + +endif # ASYMMETRIC_KEY_TYPE diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile new file mode 100644 index 0000000000..8267fee0a7 --- /dev/null +++ b/lib/crypto/Makefile @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Makefile for asymmetric cryptographic keys +# + +obj-$(CONFIG_ASYMMETRIC_KEY_TYPE) += asymmetric_keys.o + +asymmetric_keys-y := asymmetric_type.o + +obj-$(CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE) += public_key.o + +# +# RSA public key parser +# +obj-$(CONFIG_RSA_PUBLIC_KEY_PARSER) += rsa_public_key.o +rsa_public_key-y := \ + rsapubkey.asn1.o \ + rsa_helper.o + +$(obj)/rsapubkey.asn1.o: $(obj)/rsapubkey.asn1.c $(obj)/rsapubkey.asn1.h +$(obj)/rsa_helper.o: $(obj)/rsapubkey.asn1.h + +# +# X.509 Certificate handling +# +obj-$(CONFIG_X509_CERTIFICATE_PARSER) += x509_key_parser.o +x509_key_parser-y := \ + x509.asn1.o \ + x509_akid.asn1.o \ + x509_cert_parser.o \ + x509_public_key.o + +$(obj)/x509_cert_parser.o: \ + $(obj)/x509.asn1.h \ + $(obj)/x509_akid.asn1.h + +$(obj)/x509.asn1.o: $(obj)/x509.asn1.c $(obj)/x509.asn1.h +$(obj)/x509_akid.asn1.o: $(obj)/x509_akid.asn1.c $(obj)/x509_akid.asn1.h + +# +# PKCS#7 message handling +# +obj-$(CONFIG_PKCS7_MESSAGE_PARSER) += pkcs7_message.o +pkcs7_message-y := \ + pkcs7.asn1.o \ + pkcs7_parser.o + +$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h +$(obj)/pkcs7.asn1.o: $(obj)/pkcs7.asn1.c $(obj)/pkcs7.asn1.h diff --git a/lib/crypto/asymmetric_type.c b/lib/crypto/asymmetric_type.c new file mode 100644 index 0000000000..e04666c080 --- /dev/null +++ b/lib/crypto/asymmetric_type.c @@ -0,0 +1,668 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Asymmetric public-key cryptography key type + * + * See Documentation/crypto/asymmetric-keys.txt + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ +#ifndef __UBOOT__ +#include <keys/asymmetric-subtype.h> +#include <keys/asymmetric-parser.h> +#endif +#include <crypto/public_key.h> +#ifdef __UBOOT__ +#include <linux/compat.h> +#include <linux/ctype.h> +#include <linux/string.h> +#else +#include <linux/seq_file.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#endif +#ifdef __UBOOT__ +#include <keys/asymmetric-type.h> +#else +#include <keys/system_keyring.h> +#include <keys/user-type.h> +#include "asymmetric_keys.h" +#endif + +MODULE_LICENSE("GPL"); + +#ifndef __UBOOT__ +const char *const key_being_used_for[NR__KEY_BEING_USED_FOR] = { + [VERIFYING_MODULE_SIGNATURE] = "mod sig", + [VERIFYING_FIRMWARE_SIGNATURE] = "firmware sig", + [VERIFYING_KEXEC_PE_SIGNATURE] = "kexec PE sig", + [VERIFYING_KEY_SIGNATURE] = "key sig", + [VERIFYING_KEY_SELF_SIGNATURE] = "key self sig", + [VERIFYING_UNSPECIFIED_SIGNATURE] = "unspec sig", +}; +EXPORT_SYMBOL_GPL(key_being_used_for); + +static LIST_HEAD(asymmetric_key_parsers); +static DECLARE_RWSEM(asymmetric_key_parsers_sem); + +/** + * find_asymmetric_key - Find a key by ID. + * @keyring: The keys to search. + * @id_0: The first ID to look for or NULL. + * @id_1: The second ID to look for or NULL. + * @partial: Use partial match if true, exact if false. + * + * Find a key in the given keyring by identifier. The preferred identifier is + * the id_0 and the fallback identifier is the id_1. If both are given, the + * lookup is by the former, but the latter must also match. + */ +struct key *find_asymmetric_key(struct key *keyring, + const struct asymmetric_key_id *id_0, + const struct asymmetric_key_id *id_1, + bool partial) +{ + struct key *key; + key_ref_t ref; + const char *lookup; + char *req, *p; + int len; + + BUG_ON(!id_0 && !id_1); + + if (id_0) { + lookup = id_0->data; + len = id_0->len; + } else { + lookup = id_1->data; + len = id_1->len; + } + + /* Construct an identifier "id:<keyid>". */ + p = req = kmalloc(2 + 1 + len * 2 + 1, GFP_KERNEL); + if (!req) + return ERR_PTR(-ENOMEM); + + if (partial) { + *p++ = 'i'; + *p++ = 'd'; + } else { + *p++ = 'e'; + *p++ = 'x'; + } + *p++ = ':'; + p = bin2hex(p, lookup, len); + *p = 0; + + pr_debug("Look up: \"%s\"\n", req); + + ref = keyring_search(make_key_ref(keyring, 1), + &key_type_asymmetric, req, true); + if (IS_ERR(ref)) + pr_debug("Request for key '%s' err %ld\n", req, PTR_ERR(ref)); + kfree(req); + + if (IS_ERR(ref)) { + switch (PTR_ERR(ref)) { + /* Hide some search errors */ + case -EACCES: + case -ENOTDIR: + case -EAGAIN: + return ERR_PTR(-ENOKEY); + default: + return ERR_CAST(ref); + } + } + + key = key_ref_to_ptr(ref); + if (id_0 && id_1) { + const struct asymmetric_key_ids *kids = asymmetric_key_ids(key); + + if (!kids->id[1]) { + pr_debug("First ID matches, but second is missing\n"); + goto reject; + } + if (!asymmetric_key_id_same(id_1, kids->id[1])) { + pr_debug("First ID matches, but second does not\n"); + goto reject; + } + } + + pr_devel("<==%s() = 0 [%x]\n", __func__, key_serial(key)); + return key; + +reject: + key_put(key); + return ERR_PTR(-EKEYREJECTED); +} +EXPORT_SYMBOL_GPL(find_asymmetric_key); +#endif /* !__UBOOT__ */ + +/** + * asymmetric_key_generate_id: Construct an asymmetric key ID + * @val_1: First binary blob + * @len_1: Length of first binary blob + * @val_2: Second binary blob + * @len_2: Length of second binary blob + * + * Construct an asymmetric key ID from a pair of binary blobs. + */ +struct asymmetric_key_id *asymmetric_key_generate_id(const void *val_1, + size_t len_1, + const void *val_2, + size_t len_2) +{ + struct asymmetric_key_id *kid; + + kid = kmalloc(sizeof(struct asymmetric_key_id) + len_1 + len_2, + GFP_KERNEL); + if (!kid) + return ERR_PTR(-ENOMEM); + kid->len = len_1 + len_2; + memcpy(kid->data, val_1, len_1); + memcpy(kid->data + len_1, val_2, len_2); + return kid; +} +EXPORT_SYMBOL_GPL(asymmetric_key_generate_id); + +/** + * asymmetric_key_id_same - Return true if two asymmetric keys IDs are the same. + * @kid_1, @kid_2: The key IDs to compare + */ +bool asymmetric_key_id_same(const struct asymmetric_key_id *kid1, + const struct asymmetric_key_id *kid2) +{ + if (!kid1 || !kid2) + return false; + if (kid1->len != kid2->len) + return false; + return memcmp(kid1->data, kid2->data, kid1->len) == 0; +} +EXPORT_SYMBOL_GPL(asymmetric_key_id_same); + +/** + * asymmetric_key_id_partial - Return true if two asymmetric keys IDs + * partially match + * @kid_1, @kid_2: The key IDs to compare + */ +bool asymmetric_key_id_partial(const struct asymmetric_key_id *kid1, + const struct asymmetric_key_id *kid2) +{ + if (!kid1 || !kid2) + return false; + if (kid1->len < kid2->len) + return false; + return memcmp(kid1->data + (kid1->len - kid2->len), + kid2->data, kid2->len) == 0; +} +EXPORT_SYMBOL_GPL(asymmetric_key_id_partial); + +#ifndef __UBOOT__ +/** + * asymmetric_match_key_ids - Search asymmetric key IDs + * @kids: The list of key IDs to check + * @match_id: The key ID we're looking for + * @match: The match function to use + */ +static bool asymmetric_match_key_ids( + const struct asymmetric_key_ids *kids, + const struct asymmetric_key_id *match_id, + bool (*match)(const struct asymmetric_key_id *kid1, + const struct asymmetric_key_id *kid2)) +{ + int i; + + if (!kids || !match_id) + return false; + for (i = 0; i < ARRAY_SIZE(kids->id); i++) + if (match(kids->id[i], match_id)) + return true; + return false; +} + +/* helper function can be called directly with pre-allocated memory */ +inline int __asymmetric_key_hex_to_key_id(const char *id, + struct asymmetric_key_id *match_id, + size_t hexlen) +{ + match_id->len = hexlen; + return hex2bin(match_id->data, id, hexlen); +} + +/** + * asymmetric_key_hex_to_key_id - Convert a hex string into a key ID. + * @id: The ID as a hex string. + */ +struct asymmetric_key_id *asymmetric_key_hex_to_key_id(const char *id) +{ + struct asymmetric_key_id *match_id; + size_t asciihexlen; + int ret; + + if (!*id) + return ERR_PTR(-EINVAL); + asciihexlen = strlen(id); + if (asciihexlen & 1) + return ERR_PTR(-EINVAL); + + match_id = kmalloc(sizeof(struct asymmetric_key_id) + asciihexlen / 2, + GFP_KERNEL); + if (!match_id) + return ERR_PTR(-ENOMEM); + ret = __asymmetric_key_hex_to_key_id(id, match_id, asciihexlen / 2); + if (ret < 0) { + kfree(match_id); + return ERR_PTR(-EINVAL); + } + return match_id; +} + +/* + * Match asymmetric keys by an exact match on an ID. + */ +static bool asymmetric_key_cmp(const struct key *key, + const struct key_match_data *match_data) +{ + const struct asymmetric_key_ids *kids = asymmetric_key_ids(key); + const struct asymmetric_key_id *match_id = match_data->preparsed; + + return asymmetric_match_key_ids(kids, match_id, + asymmetric_key_id_same); +} + +/* + * Match asymmetric keys by a partial match on an IDs. + */ +static bool asymmetric_key_cmp_partial(const struct key *key, + const struct key_match_data *match_data) +{ + const struct asymmetric_key_ids *kids = asymmetric_key_ids(key); + const struct asymmetric_key_id *match_id = match_data->preparsed; + + return asymmetric_match_key_ids(kids, match_id, + asymmetric_key_id_partial); +} + +/* + * Preparse the match criterion. If we don't set lookup_type and cmp, + * the default will be an exact match on the key description. + * + * There are some specifiers for matching key IDs rather than by the key + * description: + * + * "id:<id>" - find a key by partial match on any available ID + * "ex:<id>" - find a key by exact match on any available ID + * + * These have to be searched by iteration rather than by direct lookup because + * the key is hashed according to its description. + */ +static int asymmetric_key_match_preparse(struct key_match_data *match_data) +{ + struct asymmetric_key_id *match_id; + const char *spec = match_data->raw_data; + const char *id; + bool (*cmp)(const struct key *, const struct key_match_data *) = + asymmetric_key_cmp; + + if (!spec || !*spec) + return -EINVAL; + if (spec[0] == 'i' && + spec[1] == 'd' && + spec[2] == ':') { + id = spec + 3; + cmp = asymmetric_key_cmp_partial; + } else if (spec[0] == 'e' && + spec[1] == 'x' && + spec[2] == ':') { + id = spec + 3; + } else { + goto default_match; + } + + match_id = asymmetric_key_hex_to_key_id(id); + if (IS_ERR(match_id)) + return PTR_ERR(match_id); + + match_data->preparsed = match_id; + match_data->cmp = cmp; + match_data->lookup_type = KEYRING_SEARCH_LOOKUP_ITERATE; + return 0; + +default_match: + return 0; +} + +/* + * Free the preparsed the match criterion. + */ +static void asymmetric_key_match_free(struct key_match_data *match_data) +{ + kfree(match_data->preparsed); +} + +/* + * Describe the asymmetric key + */ +static void asymmetric_key_describe(const struct key *key, struct seq_file *m) +{ + const struct asymmetric_key_subtype *subtype = asymmetric_key_subtype(key); + const struct asymmetric_key_ids *kids = asymmetric_key_ids(key); + const struct asymmetric_key_id *kid; + const unsigned char *p; + int n; + + seq_puts(m, key->description); + + if (subtype) { + seq_puts(m, ": "); + subtype->describe(key, m); + + if (kids && kids->id[1]) { + kid = kids->id[1]; + seq_putc(m, ' '); + n = kid->len; + p = kid->data; + if (n > 4) { + p += n - 4; + n = 4; + } + seq_printf(m, "%*phN", n, p); + } + + seq_puts(m, " ["); + /* put something here to indicate the key's capabilities */ + seq_putc(m, ']'); + } +} + +/* + * Preparse a asymmetric payload to get format the contents appropriately for the + * internal payload to cut down on the number of scans of the data performed. + * + * We also generate a proposed description from the contents of the key that + * can be used to name the key if the user doesn't want to provide one. + */ +static int asymmetric_key_preparse(struct key_preparsed_payload *prep) +{ + struct asymmetric_key_parser *parser; + int ret; + + pr_devel("==>%s()\n", __func__); + + if (prep->datalen == 0) + return -EINVAL; + + down_read(&asymmetric_key_parsers_sem); + + ret = -EBADMSG; + list_for_each_entry(parser, &asymmetric_key_parsers, link) { + pr_debug("Trying parser '%s'\n", parser->name); + + ret = parser->parse(prep); + if (ret != -EBADMSG) { + pr_debug("Parser recognised the format (ret %d)\n", + ret); + break; + } + } + + up_read(&asymmetric_key_parsers_sem); + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} + +/* + * Clean up the key ID list + */ +static void asymmetric_key_free_kids(struct asymmetric_key_ids *kids) +{ + int i; + + if (kids) { + for (i = 0; i < ARRAY_SIZE(kids->id); i++) + kfree(kids->id[i]); + kfree(kids); + } +} + +/* + * Clean up the preparse data + */ +static void asymmetric_key_free_preparse(struct key_preparsed_payload *prep) +{ + struct asymmetric_key_subtype *subtype = prep->payload.data[asym_subtype]; + struct asymmetric_key_ids *kids = prep->payload.data[asym_key_ids]; + + pr_devel("==>%s()\n", __func__); + + if (subtype) { + subtype->destroy(prep->payload.data[asym_crypto], + prep->payload.data[asym_auth]); + module_put(subtype->owner); + } + asymmetric_key_free_kids(kids); + kfree(prep->description); +} + +/* + * dispose of the data dangling from the corpse of a asymmetric key + */ +static void asymmetric_key_destroy(struct key *key) +{ + struct asymmetric_key_subtype *subtype = asymmetric_key_subtype(key); + struct asymmetric_key_ids *kids = key->payload.data[asym_key_ids]; + void *data = key->payload.data[asym_crypto]; + void *auth = key->payload.data[asym_auth]; + + key->payload.data[asym_crypto] = NULL; + key->payload.data[asym_subtype] = NULL; + key->payload.data[asym_key_ids] = NULL; + key->payload.data[asym_auth] = NULL; + + if (subtype) { + subtype->destroy(data, auth); + module_put(subtype->owner); + } + + asymmetric_key_free_kids(kids); +} + +static struct key_restriction *asymmetric_restriction_alloc( + key_restrict_link_func_t check, + struct key *key) +{ + struct key_restriction *keyres = + kzalloc(sizeof(struct key_restriction), GFP_KERNEL); + + if (!keyres) + return ERR_PTR(-ENOMEM); + + keyres->check = check; + keyres->key = key; + keyres->keytype = &key_type_asymmetric; + + return keyres; +} + +/* + * look up keyring restrict functions for asymmetric keys + */ +static struct key_restriction *asymmetric_lookup_restriction( + const char *restriction) +{ + char *restrict_method; + char *parse_buf; + char *next; + struct key_restriction *ret = ERR_PTR(-EINVAL); + + if (strcmp("builtin_trusted", restriction) == 0) + return asymmetric_restriction_alloc( + restrict_link_by_builtin_trusted, NULL); + + if (strcmp("builtin_and_secondary_trusted", restriction) == 0) + return asymmetric_restriction_alloc( + restrict_link_by_builtin_and_secondary_trusted, NULL); + + parse_buf = kstrndup(restriction, PAGE_SIZE, GFP_KERNEL); + if (!parse_buf) + return ERR_PTR(-ENOMEM); + + next = parse_buf; + restrict_method = strsep(&next, ":"); + + if ((strcmp(restrict_method, "key_or_keyring") == 0) && next) { + char *key_text; + key_serial_t serial; + struct key *key; + key_restrict_link_func_t link_fn = + restrict_link_by_key_or_keyring; + bool allow_null_key = false; + + key_text = strsep(&next, ":"); + + if (next) { + if (strcmp(next, "chain") != 0) + goto out; + + link_fn = restrict_link_by_key_or_keyring_chain; + allow_null_key = true; + } + + if (kstrtos32(key_text, 0, &serial) < 0) + goto out; + + if ((serial == 0) && allow_null_key) { + key = NULL; + } else { + key = key_lookup(serial); + if (IS_ERR(key)) { + ret = ERR_CAST(key); + goto out; + } + } + + ret = asymmetric_restriction_alloc(link_fn, key); + if (IS_ERR(ret)) + key_put(key); + } + +out: + kfree(parse_buf); + return ret; +} + +int asymmetric_key_eds_op(struct kernel_pkey_params *params, + const void *in, void *out) +{ + const struct asymmetric_key_subtype *subtype; + struct key *key = params->key; + int ret; + + pr_devel("==>%s()\n", __func__); + + if (key->type != &key_type_asymmetric) + return -EINVAL; + subtype = asymmetric_key_subtype(key); + if (!subtype || + !key->payload.data[0]) + return -EINVAL; + if (!subtype->eds_op) + return -ENOTSUPP; + + ret = subtype->eds_op(params, in, out); + + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} + +static int asymmetric_key_verify_signature(struct kernel_pkey_params *params, + const void *in, const void *in2) +{ + struct public_key_signature sig = { + .s_size = params->in2_len, + .digest_size = params->in_len, + .encoding = params->encoding, + .hash_algo = params->hash_algo, + .digest = (void *)in, + .s = (void *)in2, + }; + + return verify_signature(params->key, &sig); +} + +struct key_type key_type_asymmetric = { + .name = "asymmetric", + .preparse = asymmetric_key_preparse, + .free_preparse = asymmetric_key_free_preparse, + .instantiate = generic_key_instantiate, + .match_preparse = asymmetric_key_match_preparse, + .match_free = asymmetric_key_match_free, + .destroy = asymmetric_key_destroy, + .describe = asymmetric_key_describe, + .lookup_restriction = asymmetric_lookup_restriction, + .asym_query = query_asymmetric_key, + .asym_eds_op = asymmetric_key_eds_op, + .asym_verify_signature = asymmetric_key_verify_signature, +}; +EXPORT_SYMBOL_GPL(key_type_asymmetric); + +/** + * register_asymmetric_key_parser - Register a asymmetric key blob parser + * @parser: The parser to register + */ +int register_asymmetric_key_parser(struct asymmetric_key_parser *parser) +{ + struct asymmetric_key_parser *cursor; + int ret; + + down_write(&asymmetric_key_parsers_sem); + + list_for_each_entry(cursor, &asymmetric_key_parsers, link) { + if (strcmp(cursor->name, parser->name) == 0) { + pr_err("Asymmetric key parser '%s' already registered\n", + parser->name); + ret = -EEXIST; + goto out; + } + } + + list_add_tail(&parser->link, &asymmetric_key_parsers); + + pr_notice("Asymmetric key parser '%s' registered\n", parser->name); + ret = 0; + +out: + up_write(&asymmetric_key_parsers_sem); + return ret; +} +EXPORT_SYMBOL_GPL(register_asymmetric_key_parser); + +/** + * unregister_asymmetric_key_parser - Unregister a asymmetric key blob parser + * @parser: The parser to unregister + */ +void unregister_asymmetric_key_parser(struct asymmetric_key_parser *parser) +{ + down_write(&asymmetric_key_parsers_sem); + list_del(&parser->link); + up_write(&asymmetric_key_parsers_sem); + + pr_notice("Asymmetric key parser '%s' unregistered\n", parser->name); +} +EXPORT_SYMBOL_GPL(unregister_asymmetric_key_parser); + +/* + * Module stuff + */ +static int __init asymmetric_key_init(void) +{ + return register_key_type(&key_type_asymmetric); +} + +static void __exit asymmetric_key_cleanup(void) +{ + unregister_key_type(&key_type_asymmetric); +} + +module_init(asymmetric_key_init); +module_exit(asymmetric_key_cleanup); +#endif /* !__UBOOT__ */ diff --git a/lib/crypto/pkcs7.asn1 b/lib/crypto/pkcs7.asn1 new file mode 100644 index 0000000000..1eca740b81 --- /dev/null +++ b/lib/crypto/pkcs7.asn1 @@ -0,0 +1,135 @@ +PKCS7ContentInfo ::= SEQUENCE { + contentType ContentType ({ pkcs7_check_content_type }), + content [0] EXPLICIT SignedData OPTIONAL +} + +ContentType ::= OBJECT IDENTIFIER ({ pkcs7_note_OID }) + +SignedData ::= SEQUENCE { + version INTEGER ({ pkcs7_note_signeddata_version }), + digestAlgorithms DigestAlgorithmIdentifiers, + contentInfo ContentInfo ({ pkcs7_note_content }), + certificates CHOICE { + certSet [0] IMPLICIT ExtendedCertificatesAndCertificates, + certSequence [2] IMPLICIT Certificates + } OPTIONAL ({ pkcs7_note_certificate_list }), + crls CHOICE { + crlSet [1] IMPLICIT CertificateRevocationLists, + crlSequence [3] IMPLICIT CRLSequence + } OPTIONAL, + signerInfos SignerInfos +} + +ContentInfo ::= SEQUENCE { + contentType ContentType ({ pkcs7_note_OID }), + content [0] EXPLICIT Data OPTIONAL +} + +Data ::= ANY ({ pkcs7_note_data }) + +DigestAlgorithmIdentifiers ::= CHOICE { + daSet SET OF DigestAlgorithmIdentifier, + daSequence SEQUENCE OF DigestAlgorithmIdentifier +} + +DigestAlgorithmIdentifier ::= SEQUENCE { + algorithm OBJECT IDENTIFIER ({ pkcs7_note_OID }), + parameters ANY OPTIONAL +} + +-- +-- Certificates and certificate lists +-- +ExtendedCertificatesAndCertificates ::= SET OF ExtendedCertificateOrCertificate + +ExtendedCertificateOrCertificate ::= CHOICE { + certificate Certificate, -- X.509 + extendedCertificate [0] IMPLICIT ExtendedCertificate -- PKCS#6 +} + +ExtendedCertificate ::= Certificate -- cheating + +Certificates ::= SEQUENCE OF Certificate + +CertificateRevocationLists ::= SET OF CertificateList + +CertificateList ::= SEQUENCE OF Certificate -- This may be defined incorrectly + +CRLSequence ::= SEQUENCE OF CertificateList + +Certificate ::= ANY ({ pkcs7_extract_cert }) -- X.509 + +-- +-- Signer information +-- +SignerInfos ::= CHOICE { + siSet SET OF SignerInfo, + siSequence SEQUENCE OF SignerInfo +} + +SignerInfo ::= SEQUENCE { + version INTEGER ({ pkcs7_note_signerinfo_version }), + sid SignerIdentifier, -- CMS variant, not PKCS#7 + digestAlgorithm DigestAlgorithmIdentifier ({ pkcs7_sig_note_digest_algo }), + authenticatedAttributes CHOICE { + aaSet [0] IMPLICIT SetOfAuthenticatedAttribute + ({ pkcs7_sig_note_set_of_authattrs }), + aaSequence [2] EXPLICIT SEQUENCE OF AuthenticatedAttribute + -- Explicit because easier to compute digest on + -- sequence of attributes and then reuse encoded + -- sequence in aaSequence. + } OPTIONAL, + digestEncryptionAlgorithm + DigestEncryptionAlgorithmIdentifier ({ pkcs7_sig_note_pkey_algo }), + encryptedDigest EncryptedDigest, + unauthenticatedAttributes CHOICE { + uaSet [1] IMPLICIT SET OF UnauthenticatedAttribute, + uaSequence [3] IMPLICIT SEQUENCE OF UnauthenticatedAttribute + } OPTIONAL +} ({ pkcs7_note_signed_info }) + +SignerIdentifier ::= CHOICE { + -- RFC5652 sec 5.3 + issuerAndSerialNumber IssuerAndSerialNumber, + subjectKeyIdentifier [0] IMPLICIT SubjectKeyIdentifier +} + +IssuerAndSerialNumber ::= SEQUENCE { + issuer Name ({ pkcs7_sig_note_issuer }), + serialNumber CertificateSerialNumber ({ pkcs7_sig_note_serial }) +} + +CertificateSerialNumber ::= INTEGER + +SubjectKeyIdentifier ::= OCTET STRING ({ pkcs7_sig_note_skid }) + +SetOfAuthenticatedAttribute ::= SET OF AuthenticatedAttribute + +AuthenticatedAttribute ::= SEQUENCE { + type OBJECT IDENTIFIER ({ pkcs7_note_OID }), + values SET OF ANY ({ pkcs7_sig_note_authenticated_attr }) +} + +UnauthenticatedAttribute ::= SEQUENCE { + type OBJECT IDENTIFIER, + values SET OF ANY +} + +DigestEncryptionAlgorithmIdentifier ::= SEQUENCE { + algorithm OBJECT IDENTIFIER ({ pkcs7_note_OID }), + parameters ANY OPTIONAL +} + +EncryptedDigest ::= OCTET STRING ({ pkcs7_sig_note_signature }) + +--- +--- X.500 Name +--- +Name ::= SEQUENCE OF RelativeDistinguishedName + +RelativeDistinguishedName ::= SET OF AttributeValueAssertion + +AttributeValueAssertion ::= SEQUENCE { + attributeType OBJECT IDENTIFIER ({ pkcs7_note_OID }), + attributeValue ANY +} diff --git a/lib/crypto/pkcs7_parser.c b/lib/crypto/pkcs7_parser.c new file mode 100644 index 0000000000..bf9e7e888f --- /dev/null +++ b/lib/crypto/pkcs7_parser.c @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* PKCS#7 parser + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#define pr_fmt(fmt) "PKCS7: "fmt +#ifdef __UBOOT__ +#include <linux/bitops.h> +#include <linux/compat.h> +#endif +#include <linux/kernel.h> +#ifndef __UBOOT__ +#include <linux/module.h> +#include <linux/export.h> +#include <linux/slab.h> +#endif +#include <linux/err.h> +#include <linux/oid_registry.h> +#include <crypto/public_key.h> +#include "pkcs7_parser.h" +#include "pkcs7.asn1.h" + +MODULE_DESCRIPTION("PKCS#7 parser"); +MODULE_AUTHOR("Red Hat, Inc."); +MODULE_LICENSE("GPL"); + +struct pkcs7_parse_context { + struct pkcs7_message *msg; /* Message being constructed */ + struct pkcs7_signed_info *sinfo; /* SignedInfo being constructed */ + struct pkcs7_signed_info **ppsinfo; + struct x509_certificate *certs; /* Certificate cache */ + struct x509_certificate **ppcerts; + unsigned long data; /* Start of data */ + enum OID last_oid; /* Last OID encountered */ + unsigned x509_index; + unsigned sinfo_index; + const void *raw_serial; + unsigned raw_serial_size; + unsigned raw_issuer_size; + const void *raw_issuer; + const void *raw_skid; + unsigned raw_skid_size; + bool expect_skid; +}; + +/* + * Free a signed information block. + */ +static void pkcs7_free_signed_info(struct pkcs7_signed_info *sinfo) +{ + if (sinfo) { + public_key_signature_free(sinfo->sig); + kfree(sinfo); + } +} + +/** + * pkcs7_free_message - Free a PKCS#7 message + * @pkcs7: The PKCS#7 message to free + */ +void pkcs7_free_message(struct pkcs7_message *pkcs7) +{ + struct x509_certificate *cert; + struct pkcs7_signed_info *sinfo; + + if (pkcs7) { + while (pkcs7->certs) { + cert = pkcs7->certs; + pkcs7->certs = cert->next; + x509_free_certificate(cert); + } + while (pkcs7->crl) { + cert = pkcs7->crl; + pkcs7->crl = cert->next; + x509_free_certificate(cert); + } + while (pkcs7->signed_infos) { + sinfo = pkcs7->signed_infos; + pkcs7->signed_infos = sinfo->next; + pkcs7_free_signed_info(sinfo); + } + kfree(pkcs7); + } +} +EXPORT_SYMBOL_GPL(pkcs7_free_message); + +/* + * Check authenticatedAttributes are provided or not provided consistently. + */ +static int pkcs7_check_authattrs(struct pkcs7_message *msg) +{ + struct pkcs7_signed_info *sinfo; + bool want = false; + + sinfo = msg->signed_infos; + if (!sinfo) + goto inconsistent; + + if (sinfo->authattrs) { + want = true; + msg->have_authattrs = true; + } + + for (sinfo = sinfo->next; sinfo; sinfo = sinfo->next) + if (!!sinfo->authattrs != want) + goto inconsistent; + return 0; + +inconsistent: + pr_warn("Inconsistently supplied authAttrs\n"); + return -EINVAL; +} + +/** + * pkcs7_parse_message - Parse a PKCS#7 message + * @data: The raw binary ASN.1 encoded message to be parsed + * @datalen: The size of the encoded message + */ +struct pkcs7_message *pkcs7_parse_message(const void *data, size_t datalen) +{ + struct pkcs7_parse_context *ctx; + struct pkcs7_message *msg = ERR_PTR(-ENOMEM); + int ret; + + ctx = kzalloc(sizeof(struct pkcs7_parse_context), GFP_KERNEL); + if (!ctx) + goto out_no_ctx; + ctx->msg = kzalloc(sizeof(struct pkcs7_message), GFP_KERNEL); + if (!ctx->msg) + goto out_no_msg; + ctx->sinfo = kzalloc(sizeof(struct pkcs7_signed_info), GFP_KERNEL); + if (!ctx->sinfo) + goto out_no_sinfo; + ctx->sinfo->sig = kzalloc(sizeof(struct public_key_signature), + GFP_KERNEL); + if (!ctx->sinfo->sig) + goto out_no_sig; + + ctx->data = (unsigned long)data; + ctx->ppcerts = &ctx->certs; + ctx->ppsinfo = &ctx->msg->signed_infos; + + /* Attempt to decode the signature */ + ret = asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen); + if (ret < 0) { + msg = ERR_PTR(ret); + goto out; + } + + ret = pkcs7_check_authattrs(ctx->msg); + if (ret < 0) { + msg = ERR_PTR(ret); + goto out; + } + + msg = ctx->msg; + ctx->msg = NULL; + +out: + while (ctx->certs) { + struct x509_certificate *cert = ctx->certs; + ctx->certs = cert->next; + x509_free_certificate(cert); + } +out_no_sig: + pkcs7_free_signed_info(ctx->sinfo); +out_no_sinfo: + pkcs7_free_message(ctx->msg); +out_no_msg: + kfree(ctx); +out_no_ctx: + return msg; +} +EXPORT_SYMBOL_GPL(pkcs7_parse_message); + +/** + * pkcs7_get_content_data - Get access to the PKCS#7 content + * @pkcs7: The preparsed PKCS#7 message to access + * @_data: Place to return a pointer to the data + * @_data_len: Place to return the data length + * @_headerlen: Size of ASN.1 header not included in _data + * + * Get access to the data content of the PKCS#7 message. The size of the + * header of the ASN.1 object that contains it is also provided and can be used + * to adjust *_data and *_data_len to get the entire object. + * + * Returns -ENODATA if the data object was missing from the message. + */ +int pkcs7_get_content_data(const struct pkcs7_message *pkcs7, + const void **_data, size_t *_data_len, + size_t *_headerlen) +{ + if (!pkcs7->data) + return -ENODATA; + + *_data = pkcs7->data; + *_data_len = pkcs7->data_len; + if (_headerlen) + *_headerlen = pkcs7->data_hdrlen; + return 0; +} +EXPORT_SYMBOL_GPL(pkcs7_get_content_data); + +/* + * Note an OID when we find one for later processing when we know how + * to interpret it. + */ +int pkcs7_note_OID(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + ctx->last_oid = look_up_OID(value, vlen); + if (ctx->last_oid == OID__NR) { + char buffer[50]; + sprint_oid(value, vlen, buffer, sizeof(buffer)); + printk("PKCS7: Unknown OID: [%lu] %s\n", + (unsigned long)value - ctx->data, buffer); + } + return 0; +} + +/* + * Note the digest algorithm for the signature. + */ +int pkcs7_sig_note_digest_algo(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + switch (ctx->last_oid) { + case OID_md4: + ctx->sinfo->sig->hash_algo = "md4"; + break; + case OID_md5: + ctx->sinfo->sig->hash_algo = "md5"; + break; + case OID_sha1: + ctx->sinfo->sig->hash_algo = "sha1"; + break; + case OID_sha256: + ctx->sinfo->sig->hash_algo = "sha256"; + break; + case OID_sha384: + ctx->sinfo->sig->hash_algo = "sha384"; + break; + case OID_sha512: + ctx->sinfo->sig->hash_algo = "sha512"; + break; + case OID_sha224: + ctx->sinfo->sig->hash_algo = "sha224"; + break; + default: + printk("Unsupported digest algo: %u\n", ctx->last_oid); + return -ENOPKG; + } + return 0; +} + +/* + * Note the public key algorithm for the signature. + */ +int pkcs7_sig_note_pkey_algo(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + switch (ctx->last_oid) { + case OID_rsaEncryption: + ctx->sinfo->sig->pkey_algo = "rsa"; + ctx->sinfo->sig->encoding = "pkcs1"; + break; + default: + printk("Unsupported pkey algo: %u\n", ctx->last_oid); + return -ENOPKG; + } + return 0; +} + +/* + * We only support signed data [RFC2315 sec 9]. + */ +int pkcs7_check_content_type(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + if (ctx->last_oid != OID_signed_data) { + pr_warn("Only support pkcs7_signedData type\n"); + return -EINVAL; + } + + return 0; +} + +/* + * Note the SignedData version + */ +int pkcs7_note_signeddata_version(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + unsigned version; + + if (vlen != 1) + goto unsupported; + + ctx->msg->version = version = *(const u8 *)value; + switch (version) { + case 1: + /* PKCS#7 SignedData [RFC2315 sec 9.1] + * CMS ver 1 SignedData [RFC5652 sec 5.1] + */ + break; + case 3: + /* CMS ver 3 SignedData [RFC2315 sec 5.1] */ + break; + default: + goto unsupported; + } + + return 0; + +unsupported: + pr_warn("Unsupported SignedData version\n"); + return -EINVAL; +} + +/* + * Note the SignerInfo version + */ +int pkcs7_note_signerinfo_version(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + unsigned version; + + if (vlen != 1) + goto unsupported; + + version = *(const u8 *)value; + switch (version) { + case 1: + /* PKCS#7 SignerInfo [RFC2315 sec 9.2] + * CMS ver 1 SignerInfo [RFC5652 sec 5.3] + */ + if (ctx->msg->version != 1) + goto version_mismatch; + ctx->expect_skid = false; + break; + case 3: + /* CMS ver 3 SignerInfo [RFC2315 sec 5.3] */ + if (ctx->msg->version == 1) + goto version_mismatch; + ctx->expect_skid = true; + break; + default: + goto unsupported; + } + + return 0; + +unsupported: + pr_warn("Unsupported SignerInfo version\n"); + return -EINVAL; +version_mismatch: + pr_warn("SignedData-SignerInfo version mismatch\n"); + return -EBADMSG; +} + +/* + * Extract a certificate and store it in the context. + */ +int pkcs7_extract_cert(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + struct x509_certificate *x509; + + if (tag != ((ASN1_UNIV << 6) | ASN1_CONS_BIT | ASN1_SEQ)) { + pr_debug("Cert began with tag %02x at %lu\n", + tag, (unsigned long)ctx - ctx->data); + return -EBADMSG; + } + + /* We have to correct for the header so that the X.509 parser can start + * from the beginning. Note that since X.509 stipulates DER, there + * probably shouldn't be an EOC trailer - but it is in PKCS#7 (which + * stipulates BER). + */ + value -= hdrlen; + vlen += hdrlen; + + if (((u8*)value)[1] == 0x80) + vlen += 2; /* Indefinite length - there should be an EOC */ + + x509 = x509_cert_parse(value, vlen); + if (IS_ERR(x509)) + return PTR_ERR(x509); + + x509->index = ++ctx->x509_index; + pr_debug("Got cert %u for %s\n", x509->index, x509->subject); + pr_debug("- fingerprint %*phN\n", x509->id->len, x509->id->data); + + *ctx->ppcerts = x509; + ctx->ppcerts = &x509->next; + return 0; +} + +/* + * Save the certificate list + */ +int pkcs7_note_certificate_list(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + pr_devel("Got cert list (%02x)\n", tag); + + *ctx->ppcerts = ctx->msg->certs; + ctx->msg->certs = ctx->certs; + ctx->certs = NULL; + ctx->ppcerts = &ctx->certs; + return 0; +} + +/* + * Note the content type. + */ +int pkcs7_note_content(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + if (ctx->last_oid != OID_data && + ctx->last_oid != OID_msIndirectData) { + pr_warn("Unsupported data type %d\n", ctx->last_oid); + return -EINVAL; + } + + ctx->msg->data_type = ctx->last_oid; + return 0; +} + +/* + * Extract the data from the message and store that and its content type OID in + * the context. + */ +int pkcs7_note_data(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + pr_debug("Got data\n"); + + ctx->msg->data = value; + ctx->msg->data_len = vlen; + ctx->msg->data_hdrlen = hdrlen; + return 0; +} + +/* + * Parse authenticated attributes. + */ +int pkcs7_sig_note_authenticated_attr(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + struct pkcs7_signed_info *sinfo = ctx->sinfo; + enum OID content_type; + + pr_devel("AuthAttr: %02x %zu [%*ph]\n", tag, vlen, (unsigned)vlen, value); + + switch (ctx->last_oid) { + case OID_contentType: + if (__test_and_set_bit(sinfo_has_content_type, &sinfo->aa_set)) + goto repeated; + content_type = look_up_OID(value, vlen); + if (content_type != ctx->msg->data_type) { + pr_warn("Mismatch between global data type (%d) and sinfo %u (%d)\n", + ctx->msg->data_type, sinfo->index, + content_type); + return -EBADMSG; + } + return 0; + + case OID_signingTime: + if (__test_and_set_bit(sinfo_has_signing_time, &sinfo->aa_set)) + goto repeated; + /* Should we check that the signing time is consistent + * with the signer's X.509 cert? + */ + return x509_decode_time(&sinfo->signing_time, + hdrlen, tag, value, vlen); + + case OID_messageDigest: + if (__test_and_set_bit(sinfo_has_message_digest, &sinfo->aa_set)) + goto repeated; + if (tag != ASN1_OTS) + return -EBADMSG; + sinfo->msgdigest = value; + sinfo->msgdigest_len = vlen; + return 0; + + case OID_smimeCapabilites: + if (__test_and_set_bit(sinfo_has_smime_caps, &sinfo->aa_set)) + goto repeated; +#ifdef __UBOOT__ /* OID_data is needed for authenticated UEFI variables */ + if (ctx->msg->data_type != OID_msIndirectData && + ctx->msg->data_type != OID_data) { +#else + if (ctx->msg->data_type != OID_msIndirectData) { +#endif + pr_warn("S/MIME Caps only allowed with Authenticode\n"); + return -EKEYREJECTED; + } + return 0; + + /* Microsoft SpOpusInfo seems to be contain cont[0] 16-bit BE + * char URLs and cont[1] 8-bit char URLs. + * + * Microsoft StatementType seems to contain a list of OIDs that + * are also used as extendedKeyUsage types in X.509 certs. + */ + case OID_msSpOpusInfo: + if (__test_and_set_bit(sinfo_has_ms_opus_info, &sinfo->aa_set)) + goto repeated; + goto authenticode_check; + case OID_msStatementType: + if (__test_and_set_bit(sinfo_has_ms_statement_type, &sinfo->aa_set)) + goto repeated; + authenticode_check: + if (ctx->msg->data_type != OID_msIndirectData) { + pr_warn("Authenticode AuthAttrs only allowed with Authenticode\n"); + return -EKEYREJECTED; + } + /* I'm not sure how to validate these */ + return 0; + default: + return 0; + } + +repeated: + /* We permit max one item per AuthenticatedAttribute and no repeats */ + pr_warn("Repeated/multivalue AuthAttrs not permitted\n"); + return -EKEYREJECTED; +} + +/* + * Note the set of auth attributes for digestion purposes [RFC2315 sec 9.3] + */ +int pkcs7_sig_note_set_of_authattrs(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + struct pkcs7_signed_info *sinfo = ctx->sinfo; + + if (!test_bit(sinfo_has_content_type, &sinfo->aa_set) || + !test_bit(sinfo_has_message_digest, &sinfo->aa_set)) { + pr_warn("Missing required AuthAttr\n"); + return -EBADMSG; + } + + if (ctx->msg->data_type != OID_msIndirectData && + test_bit(sinfo_has_ms_opus_info, &sinfo->aa_set)) { + pr_warn("Unexpected Authenticode AuthAttr\n"); + return -EBADMSG; + } + + /* We need to switch the 'CONT 0' to a 'SET OF' when we digest */ + sinfo->authattrs = value - (hdrlen - 1); + sinfo->authattrs_len = vlen + (hdrlen - 1); + return 0; +} + +/* + * Note the issuing certificate serial number + */ +int pkcs7_sig_note_serial(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + ctx->raw_serial = value; + ctx->raw_serial_size = vlen; + return 0; +} + +/* + * Note the issuer's name + */ +int pkcs7_sig_note_issuer(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + ctx->raw_issuer = value; + ctx->raw_issuer_size = vlen; + return 0; +} + +/* + * Note the issuing cert's subjectKeyIdentifier + */ +int pkcs7_sig_note_skid(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + pr_devel("SKID: %02x %zu [%*ph]\n", tag, vlen, (unsigned)vlen, value); + + ctx->raw_skid = value; + ctx->raw_skid_size = vlen; + return 0; +} + +/* + * Note the signature data + */ +int pkcs7_sig_note_signature(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + ctx->sinfo->sig->s = kmemdup(value, vlen, GFP_KERNEL); + if (!ctx->sinfo->sig->s) + return -ENOMEM; + + ctx->sinfo->sig->s_size = vlen; + return 0; +} + +/* + * Note a signature information block + */ +int pkcs7_note_signed_info(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + struct pkcs7_signed_info *sinfo = ctx->sinfo; + struct asymmetric_key_id *kid; + + if (ctx->msg->data_type == OID_msIndirectData && !sinfo->authattrs) { + pr_warn("Authenticode requires AuthAttrs\n"); + return -EBADMSG; + } + + /* Generate cert issuer + serial number key ID */ + if (!ctx->expect_skid) { + kid = asymmetric_key_generate_id(ctx->raw_serial, + ctx->raw_serial_size, + ctx->raw_issuer, + ctx->raw_issuer_size); + } else { + kid = asymmetric_key_generate_id(ctx->raw_skid, + ctx->raw_skid_size, + "", 0); + } + if (IS_ERR(kid)) + return PTR_ERR(kid); + + pr_devel("SINFO KID: %u [%*phN]\n", kid->len, kid->len, kid->data); + + sinfo->sig->auth_ids[0] = kid; + sinfo->index = ++ctx->sinfo_index; + *ctx->ppsinfo = sinfo; + ctx->ppsinfo = &sinfo->next; + ctx->sinfo = kzalloc(sizeof(struct pkcs7_signed_info), GFP_KERNEL); + if (!ctx->sinfo) + return -ENOMEM; + ctx->sinfo->sig = kzalloc(sizeof(struct public_key_signature), + GFP_KERNEL); + if (!ctx->sinfo->sig) + return -ENOMEM; + return 0; +} diff --git a/lib/crypto/pkcs7_parser.h b/lib/crypto/pkcs7_parser.h new file mode 100644 index 0000000000..6565fdc2d4 --- /dev/null +++ b/lib/crypto/pkcs7_parser.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* PKCS#7 crypto data parser internal definitions + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#include <linux/oid_registry.h> +#include <crypto/pkcs7.h> +#include "x509_parser.h" + +#define kenter(FMT, ...) \ + pr_devel("==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + pr_devel("<== %s()"FMT"\n", __func__, ##__VA_ARGS__) + +struct pkcs7_signed_info { + struct pkcs7_signed_info *next; + struct x509_certificate *signer; /* Signing certificate (in msg->certs) */ + unsigned index; + bool unsupported_crypto; /* T if not usable due to missing crypto */ + bool blacklisted; + + /* Message digest - the digest of the Content Data (or NULL) */ + const void *msgdigest; + unsigned msgdigest_len; + + /* Authenticated Attribute data (or NULL) */ + unsigned authattrs_len; + const void *authattrs; + unsigned long aa_set; +#define sinfo_has_content_type 0 +#define sinfo_has_signing_time 1 +#define sinfo_has_message_digest 2 +#define sinfo_has_smime_caps 3 +#define sinfo_has_ms_opus_info 4 +#define sinfo_has_ms_statement_type 5 + time64_t signing_time; + + /* Message signature. + * + * This contains the generated digest of _either_ the Content Data or + * the Authenticated Attributes [RFC2315 9.3]. If the latter, one of + * the attributes contains the digest of the the Content Data within + * it. + * + * THis also contains the issuing cert serial number and issuer's name + * [PKCS#7 or CMS ver 1] or issuing cert's SKID [CMS ver 3]. + */ + struct public_key_signature *sig; +}; + +struct pkcs7_message { + struct x509_certificate *certs; /* Certificate list */ + struct x509_certificate *crl; /* Revocation list */ + struct pkcs7_signed_info *signed_infos; + u8 version; /* Version of cert (1 -> PKCS#7 or CMS; 3 -> CMS) */ + bool have_authattrs; /* T if have authattrs */ + + /* Content Data (or NULL) */ + enum OID data_type; /* Type of Data */ + size_t data_len; /* Length of Data */ + size_t data_hdrlen; /* Length of Data ASN.1 header */ + const void *data; /* Content Data (or 0) */ +}; diff --git a/lib/crypto/public_key.c b/lib/crypto/public_key.c new file mode 100644 index 0000000000..634377472f --- /dev/null +++ b/lib/crypto/public_key.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* In-software asymmetric public-key crypto subtype + * + * See Documentation/crypto/asymmetric-keys.txt + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#define pr_fmt(fmt) "PKEY: "fmt +#ifdef __UBOOT__ +#include <linux/compat.h> +#else +#include <linux/module.h> +#include <linux/export.h> +#endif +#include <linux/kernel.h> +#ifndef __UBOOT__ +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/scatterlist.h> +#include <keys/asymmetric-subtype.h> +#endif +#include <crypto/public_key.h> +#ifndef __UBOOT__ +#include <crypto/akcipher.h> +#endif + +MODULE_DESCRIPTION("In-software asymmetric public-key subtype"); +MODULE_AUTHOR("Red Hat, Inc."); +MODULE_LICENSE("GPL"); + +#ifndef __UBOOT__ +/* + * Provide a part of a description of the key for /proc/keys. + */ +static void public_key_describe(const struct key *asymmetric_key, + struct seq_file *m) +{ + struct public_key *key = asymmetric_key->payload.data[asym_crypto]; + + if (key) + seq_printf(m, "%s.%s", key->id_type, key->pkey_algo); +} +#endif + +/* + * Destroy a public key algorithm key. + */ +void public_key_free(struct public_key *key) +{ + if (key) { + kfree(key->key); + kfree(key->params); + kfree(key); + } +} +EXPORT_SYMBOL_GPL(public_key_free); + +#ifdef __UBOOT__ +/* + * from <linux>/crypto/asymmetric_keys/signature.c + * + * Destroy a public key signature. + */ +void public_key_signature_free(struct public_key_signature *sig) +{ + int i; + + if (sig) { + for (i = 0; i < ARRAY_SIZE(sig->auth_ids); i++) + free(sig->auth_ids[i]); + free(sig->s); + free(sig->digest); + free(sig); + } +} +EXPORT_SYMBOL_GPL(public_key_signature_free); + +#else +/* + * Destroy a public key algorithm key. + */ +static void public_key_destroy(void *payload0, void *payload3) +{ + public_key_free(payload0); + public_key_signature_free(payload3); +} + +/* + * Determine the crypto algorithm name. + */ +static +int software_key_determine_akcipher(const char *encoding, + const char *hash_algo, + const struct public_key *pkey, + char alg_name[CRYPTO_MAX_ALG_NAME]) +{ + int n; + + if (strcmp(encoding, "pkcs1") == 0) { + /* The data wangled by the RSA algorithm is typically padded + * and encoded in some manner, such as EMSA-PKCS1-1_5 [RFC3447 + * sec 8.2]. + */ + if (!hash_algo) + n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME, + "pkcs1pad(%s)", + pkey->pkey_algo); + else + n = snprintf(alg_name, CRYPTO_MAX_ALG_NAME, + "pkcs1pad(%s,%s)", + pkey->pkey_algo, hash_algo); + return n >= CRYPTO_MAX_ALG_NAME ? -EINVAL : 0; + } + + if (strcmp(encoding, "raw") == 0) { + strcpy(alg_name, pkey->pkey_algo); + return 0; + } + + return -ENOPKG; +} + +static u8 *pkey_pack_u32(u8 *dst, u32 val) +{ + memcpy(dst, &val, sizeof(val)); + return dst + sizeof(val); +} + +/* + * Query information about a key. + */ +static int software_key_query(const struct kernel_pkey_params *params, + struct kernel_pkey_query *info) +{ + struct crypto_akcipher *tfm; + struct public_key *pkey = params->key->payload.data[asym_crypto]; + char alg_name[CRYPTO_MAX_ALG_NAME]; + u8 *key, *ptr; + int ret, len; + + ret = software_key_determine_akcipher(params->encoding, + params->hash_algo, + pkey, alg_name); + if (ret < 0) + return ret; + + tfm = crypto_alloc_akcipher(alg_name, 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen, + GFP_KERNEL); + if (!key) + goto error_free_tfm; + memcpy(key, pkey->key, pkey->keylen); + ptr = key + pkey->keylen; + ptr = pkey_pack_u32(ptr, pkey->algo); + ptr = pkey_pack_u32(ptr, pkey->paramlen); + memcpy(ptr, pkey->params, pkey->paramlen); + + if (pkey->key_is_private) + ret = crypto_akcipher_set_priv_key(tfm, key, pkey->keylen); + else + ret = crypto_akcipher_set_pub_key(tfm, key, pkey->keylen); + if (ret < 0) + goto error_free_key; + + len = crypto_akcipher_maxsize(tfm); + info->key_size = len * 8; + info->max_data_size = len; + info->max_sig_size = len; + info->max_enc_size = len; + info->max_dec_size = len; + info->supported_ops = (KEYCTL_SUPPORTS_ENCRYPT | + KEYCTL_SUPPORTS_VERIFY); + if (pkey->key_is_private) + info->supported_ops |= (KEYCTL_SUPPORTS_DECRYPT | + KEYCTL_SUPPORTS_SIGN); + ret = 0; + +error_free_key: + kfree(key); +error_free_tfm: + crypto_free_akcipher(tfm); + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} + +/* + * Do encryption, decryption and signing ops. + */ +static int software_key_eds_op(struct kernel_pkey_params *params, + const void *in, void *out) +{ + const struct public_key *pkey = params->key->payload.data[asym_crypto]; + struct akcipher_request *req; + struct crypto_akcipher *tfm; + struct crypto_wait cwait; + struct scatterlist in_sg, out_sg; + char alg_name[CRYPTO_MAX_ALG_NAME]; + char *key, *ptr; + int ret; + + pr_devel("==>%s()\n", __func__); + + ret = software_key_determine_akcipher(params->encoding, + params->hash_algo, + pkey, alg_name); + if (ret < 0) + return ret; + + tfm = crypto_alloc_akcipher(alg_name, 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + req = akcipher_request_alloc(tfm, GFP_KERNEL); + if (!req) + goto error_free_tfm; + + key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen, + GFP_KERNEL); + if (!key) + goto error_free_req; + + memcpy(key, pkey->key, pkey->keylen); + ptr = key + pkey->keylen; + ptr = pkey_pack_u32(ptr, pkey->algo); + ptr = pkey_pack_u32(ptr, pkey->paramlen); + memcpy(ptr, pkey->params, pkey->paramlen); + + if (pkey->key_is_private) + ret = crypto_akcipher_set_priv_key(tfm, key, pkey->keylen); + else + ret = crypto_akcipher_set_pub_key(tfm, key, pkey->keylen); + if (ret) + goto error_free_key; + + sg_init_one(&in_sg, in, params->in_len); + sg_init_one(&out_sg, out, params->out_len); + akcipher_request_set_crypt(req, &in_sg, &out_sg, params->in_len, + params->out_len); + crypto_init_wait(&cwait); + akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | + CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &cwait); + + /* Perform the encryption calculation. */ + switch (params->op) { + case kernel_pkey_encrypt: + ret = crypto_akcipher_encrypt(req); + break; + case kernel_pkey_decrypt: + ret = crypto_akcipher_decrypt(req); + break; + case kernel_pkey_sign: + ret = crypto_akcipher_sign(req); + break; + default: + BUG(); + } + + ret = crypto_wait_req(ret, &cwait); + if (ret == 0) + ret = req->dst_len; + +error_free_key: + kfree(key); +error_free_req: + akcipher_request_free(req); +error_free_tfm: + crypto_free_akcipher(tfm); + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} + +/* + * Verify a signature using a public key. + */ +int public_key_verify_signature(const struct public_key *pkey, + const struct public_key_signature *sig) +{ + struct crypto_wait cwait; + struct crypto_akcipher *tfm; + struct akcipher_request *req; + struct scatterlist src_sg[2]; + char alg_name[CRYPTO_MAX_ALG_NAME]; + char *key, *ptr; + int ret; + + pr_devel("==>%s()\n", __func__); + + BUG_ON(!pkey); + BUG_ON(!sig); + BUG_ON(!sig->s); + + ret = software_key_determine_akcipher(sig->encoding, + sig->hash_algo, + pkey, alg_name); + if (ret < 0) + return ret; + + tfm = crypto_alloc_akcipher(alg_name, 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + ret = -ENOMEM; + req = akcipher_request_alloc(tfm, GFP_KERNEL); + if (!req) + goto error_free_tfm; + + key = kmalloc(pkey->keylen + sizeof(u32) * 2 + pkey->paramlen, + GFP_KERNEL); + if (!key) + goto error_free_req; + + memcpy(key, pkey->key, pkey->keylen); + ptr = key + pkey->keylen; + ptr = pkey_pack_u32(ptr, pkey->algo); + ptr = pkey_pack_u32(ptr, pkey->paramlen); + memcpy(ptr, pkey->params, pkey->paramlen); + + if (pkey->key_is_private) + ret = crypto_akcipher_set_priv_key(tfm, key, pkey->keylen); + else + ret = crypto_akcipher_set_pub_key(tfm, key, pkey->keylen); + if (ret) + goto error_free_key; + + sg_init_table(src_sg, 2); + sg_set_buf(&src_sg[0], sig->s, sig->s_size); + sg_set_buf(&src_sg[1], sig->digest, sig->digest_size); + akcipher_request_set_crypt(req, src_sg, NULL, sig->s_size, + sig->digest_size); + crypto_init_wait(&cwait); + akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | + CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &cwait); + ret = crypto_wait_req(crypto_akcipher_verify(req), &cwait); + +error_free_key: + kfree(key); +error_free_req: + akcipher_request_free(req); +error_free_tfm: + crypto_free_akcipher(tfm); + pr_devel("<==%s() = %d\n", __func__, ret); + if (WARN_ON_ONCE(ret > 0)) + ret = -EINVAL; + return ret; +} +EXPORT_SYMBOL_GPL(public_key_verify_signature); + +static int public_key_verify_signature_2(const struct key *key, + const struct public_key_signature *sig) +{ + const struct public_key *pk = key->payload.data[asym_crypto]; + return public_key_verify_signature(pk, sig); +} + +/* + * Public key algorithm asymmetric key subtype + */ +struct asymmetric_key_subtype public_key_subtype = { + .owner = THIS_MODULE, + .name = "public_key", + .name_len = sizeof("public_key") - 1, + .describe = public_key_describe, + .destroy = public_key_destroy, + .query = software_key_query, + .eds_op = software_key_eds_op, + .verify_signature = public_key_verify_signature_2, +}; +EXPORT_SYMBOL_GPL(public_key_subtype); +#endif /* !__UBOOT__ */ diff --git a/lib/crypto/rsa_helper.c b/lib/crypto/rsa_helper.c new file mode 100644 index 0000000000..aca627a4a6 --- /dev/null +++ b/lib/crypto/rsa_helper.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RSA key extract helper + * + * Copyright (c) 2015, Intel Corporation + * Authors: Tadeusz Struk <tadeusz.struk@intel.com> + */ +#ifndef __UBOOT__ +#include <linux/kernel.h> +#include <linux/export.h> +#endif +#include <linux/err.h> +#ifndef __UBOOT__ +#include <linux/fips.h> +#endif +#include <crypto/internal/rsa.h> +#include "rsapubkey.asn1.h" +#ifndef __UBOOT__ +#include "rsaprivkey.asn1.h" +#endif + +int rsa_get_n(void *context, size_t hdrlen, unsigned char tag, + const void *value, size_t vlen) +{ + struct rsa_key *key = context; +#ifndef __UBOOT__ + const u8 *ptr = value; + size_t n_sz = vlen; +#endif + + /* invalid key provided */ + if (!value || !vlen) + return -EINVAL; + +#ifndef __UBOOT__ + if (fips_enabled) { + while (n_sz && !*ptr) { + ptr++; + n_sz--; + } + + /* In FIPS mode only allow key size 2K and higher */ + if (n_sz < 256) { + pr_err("RSA: key size not allowed in FIPS mode\n"); + return -EINVAL; + } + } +#endif + + key->n = value; + key->n_sz = vlen; + + return 0; +} + +int rsa_get_e(void *context, size_t hdrlen, unsigned char tag, + const void *value, size_t vlen) +{ + struct rsa_key *key = context; + + /* invalid key provided */ + if (!value || !key->n_sz || !vlen || vlen > key->n_sz) + return -EINVAL; + + key->e = value; + key->e_sz = vlen; + + return 0; +} + +int rsa_get_d(void *context, size_t hdrlen, unsigned char tag, + const void *value, size_t vlen) +{ + struct rsa_key *key = context; + + /* invalid key provided */ + if (!value || !key->n_sz || !vlen || vlen > key->n_sz) + return -EINVAL; + + key->d = value; + key->d_sz = vlen; + + return 0; +} + +int rsa_get_p(void *context, size_t hdrlen, unsigned char tag, + const void *value, size_t vlen) +{ + struct rsa_key *key = context; + + /* invalid key provided */ + if (!value || !vlen || vlen > key->n_sz) + return -EINVAL; + + key->p = value; + key->p_sz = vlen; + + return 0; +} + +int rsa_get_q(void *context, size_t hdrlen, unsigned char tag, + const void *value, size_t vlen) +{ + struct rsa_key *key = context; + + /* invalid key provided */ + if (!value || !vlen || vlen > key->n_sz) + return -EINVAL; + + key->q = value; + key->q_sz = vlen; + + return 0; +} + +int rsa_get_dp(void *context, size_t hdrlen, unsigned char tag, + const void *value, size_t vlen) +{ + struct rsa_key *key = context; + + /* invalid key provided */ + if (!value || !vlen || vlen > key->n_sz) + return -EINVAL; + + key->dp = value; + key->dp_sz = vlen; + + return 0; +} + +int rsa_get_dq(void *context, size_t hdrlen, unsigned char tag, + const void *value, size_t vlen) +{ + struct rsa_key *key = context; + + /* invalid key provided */ + if (!value || !vlen || vlen > key->n_sz) + return -EINVAL; + + key->dq = value; + key->dq_sz = vlen; + + return 0; +} + +int rsa_get_qinv(void *context, size_t hdrlen, unsigned char tag, + const void *value, size_t vlen) +{ + struct rsa_key *key = context; + + /* invalid key provided */ + if (!value || !vlen || vlen > key->n_sz) + return -EINVAL; + + key->qinv = value; + key->qinv_sz = vlen; + + return 0; +} + +/** + * rsa_parse_pub_key() - decodes the BER encoded buffer and stores in the + * provided struct rsa_key, pointers to the raw key as is, + * so that the caller can copy it or MPI parse it, etc. + * + * @rsa_key: struct rsa_key key representation + * @key: key in BER format + * @key_len: length of key + * + * Return: 0 on success or error code in case of error + */ +int rsa_parse_pub_key(struct rsa_key *rsa_key, const void *key, + unsigned int key_len) +{ + return asn1_ber_decoder(&rsapubkey_decoder, rsa_key, key, key_len); +} +EXPORT_SYMBOL_GPL(rsa_parse_pub_key); + +#ifndef __UBOOT__ +/** + * rsa_parse_priv_key() - decodes the BER encoded buffer and stores in the + * provided struct rsa_key, pointers to the raw key + * as is, so that the caller can copy it or MPI parse it, + * etc. + * + * @rsa_key: struct rsa_key key representation + * @key: key in BER format + * @key_len: length of key + * + * Return: 0 on success or error code in case of error + */ +int rsa_parse_priv_key(struct rsa_key *rsa_key, const void *key, + unsigned int key_len) +{ + return asn1_ber_decoder(&rsaprivkey_decoder, rsa_key, key, key_len); +} +EXPORT_SYMBOL_GPL(rsa_parse_priv_key); +#endif diff --git a/lib/crypto/rsapubkey.asn1 b/lib/crypto/rsapubkey.asn1 new file mode 100644 index 0000000000..725498e461 --- /dev/null +++ b/lib/crypto/rsapubkey.asn1 @@ -0,0 +1,4 @@ +RsaPubKey ::= SEQUENCE { + n INTEGER ({ rsa_get_n }), + e INTEGER ({ rsa_get_e }) +} diff --git a/lib/crypto/x509.asn1 b/lib/crypto/x509.asn1 new file mode 100644 index 0000000000..5c9f4e4a52 --- /dev/null +++ b/lib/crypto/x509.asn1 @@ -0,0 +1,60 @@ +Certificate ::= SEQUENCE { + tbsCertificate TBSCertificate ({ x509_note_tbs_certificate }), + signatureAlgorithm AlgorithmIdentifier, + signature BIT STRING ({ x509_note_signature }) + } + +TBSCertificate ::= SEQUENCE { + version [ 0 ] Version DEFAULT, + serialNumber CertificateSerialNumber ({ x509_note_serial }), + signature AlgorithmIdentifier ({ x509_note_pkey_algo }), + issuer Name ({ x509_note_issuer }), + validity Validity, + subject Name ({ x509_note_subject }), + subjectPublicKeyInfo SubjectPublicKeyInfo, + issuerUniqueID [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL, + subjectUniqueID [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL, + extensions [ 3 ] Extensions OPTIONAL + } + +Version ::= INTEGER +CertificateSerialNumber ::= INTEGER + +AlgorithmIdentifier ::= SEQUENCE { + algorithm OBJECT IDENTIFIER ({ x509_note_OID }), + parameters ANY OPTIONAL ({ x509_note_params }) +} + +Name ::= SEQUENCE OF RelativeDistinguishedName + +RelativeDistinguishedName ::= SET OF AttributeValueAssertion + +AttributeValueAssertion ::= SEQUENCE { + attributeType OBJECT IDENTIFIER ({ x509_note_OID }), + attributeValue ANY ({ x509_extract_name_segment }) + } + +Validity ::= SEQUENCE { + notBefore Time ({ x509_note_not_before }), + notAfter Time ({ x509_note_not_after }) + } + +Time ::= CHOICE { + utcTime UTCTime, + generalTime GeneralizedTime + } + +SubjectPublicKeyInfo ::= SEQUENCE { + algorithm AlgorithmIdentifier, + subjectPublicKey BIT STRING ({ x509_extract_key_data }) + } + +UniqueIdentifier ::= BIT STRING + +Extensions ::= SEQUENCE OF Extension + +Extension ::= SEQUENCE { + extnid OBJECT IDENTIFIER ({ x509_note_OID }), + critical BOOLEAN DEFAULT, + extnValue OCTET STRING ({ x509_process_extension }) + } diff --git a/lib/crypto/x509_akid.asn1 b/lib/crypto/x509_akid.asn1 new file mode 100644 index 0000000000..1a33231a75 --- /dev/null +++ b/lib/crypto/x509_akid.asn1 @@ -0,0 +1,35 @@ +-- X.509 AuthorityKeyIdentifier +-- rfc5280 section 4.2.1.1 + +AuthorityKeyIdentifier ::= SEQUENCE { + keyIdentifier [0] IMPLICIT KeyIdentifier OPTIONAL, + authorityCertIssuer [1] IMPLICIT GeneralNames OPTIONAL, + authorityCertSerialNumber [2] IMPLICIT CertificateSerialNumber OPTIONAL + } + +KeyIdentifier ::= OCTET STRING ({ x509_akid_note_kid }) + +CertificateSerialNumber ::= INTEGER ({ x509_akid_note_serial }) + +GeneralNames ::= SEQUENCE OF GeneralName + +GeneralName ::= CHOICE { + otherName [0] ANY, + rfc822Name [1] IA5String, + dNSName [2] IA5String, + x400Address [3] ANY, + directoryName [4] Name ({ x509_akid_note_name }), + ediPartyName [5] ANY, + uniformResourceIdentifier [6] IA5String, + iPAddress [7] OCTET STRING, + registeredID [8] OBJECT IDENTIFIER + } + +Name ::= SEQUENCE OF RelativeDistinguishedName + +RelativeDistinguishedName ::= SET OF AttributeValueAssertion + +AttributeValueAssertion ::= SEQUENCE { + attributeType OBJECT IDENTIFIER ({ x509_note_OID }), + attributeValue ANY ({ x509_extract_name_segment }) + } diff --git a/lib/crypto/x509_cert_parser.c b/lib/crypto/x509_cert_parser.c new file mode 100644 index 0000000000..e6d2a426a0 --- /dev/null +++ b/lib/crypto/x509_cert_parser.c @@ -0,0 +1,697 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* X.509 certificate parser + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#define pr_fmt(fmt) "X.509: "fmt +#include <linux/kernel.h> +#ifndef __UBOOT__ +#include <linux/export.h> +#include <linux/slab.h> +#endif +#include <linux/err.h> +#include <linux/oid_registry.h> +#ifdef __UBOOT__ +#include <linux/string.h> +#endif +#include <crypto/public_key.h> +#include "x509_parser.h" +#include "x509.asn1.h" +#include "x509_akid.asn1.h" + +struct x509_parse_context { + struct x509_certificate *cert; /* Certificate being constructed */ + unsigned long data; /* Start of data */ + const void *cert_start; /* Start of cert content */ + const void *key; /* Key data */ + size_t key_size; /* Size of key data */ + const void *params; /* Key parameters */ + size_t params_size; /* Size of key parameters */ + enum OID key_algo; /* Public key algorithm */ + enum OID last_oid; /* Last OID encountered */ + enum OID algo_oid; /* Algorithm OID */ + unsigned char nr_mpi; /* Number of MPIs stored */ + u8 o_size; /* Size of organizationName (O) */ + u8 cn_size; /* Size of commonName (CN) */ + u8 email_size; /* Size of emailAddress */ + u16 o_offset; /* Offset of organizationName (O) */ + u16 cn_offset; /* Offset of commonName (CN) */ + u16 email_offset; /* Offset of emailAddress */ + unsigned raw_akid_size; + const void *raw_akid; /* Raw authorityKeyId in ASN.1 */ + const void *akid_raw_issuer; /* Raw directoryName in authorityKeyId */ + unsigned akid_raw_issuer_size; +}; + +/* + * Free an X.509 certificate + */ +void x509_free_certificate(struct x509_certificate *cert) +{ + if (cert) { + public_key_free(cert->pub); + public_key_signature_free(cert->sig); + kfree(cert->issuer); + kfree(cert->subject); + kfree(cert->id); + kfree(cert->skid); + kfree(cert); + } +} +EXPORT_SYMBOL_GPL(x509_free_certificate); + +/* + * Parse an X.509 certificate + */ +struct x509_certificate *x509_cert_parse(const void *data, size_t datalen) +{ + struct x509_certificate *cert; + struct x509_parse_context *ctx; + struct asymmetric_key_id *kid; + long ret; + + ret = -ENOMEM; + cert = kzalloc(sizeof(struct x509_certificate), GFP_KERNEL); + if (!cert) + goto error_no_cert; + cert->pub = kzalloc(sizeof(struct public_key), GFP_KERNEL); + if (!cert->pub) + goto error_no_ctx; + cert->sig = kzalloc(sizeof(struct public_key_signature), GFP_KERNEL); + if (!cert->sig) + goto error_no_ctx; + ctx = kzalloc(sizeof(struct x509_parse_context), GFP_KERNEL); + if (!ctx) + goto error_no_ctx; + + ctx->cert = cert; + ctx->data = (unsigned long)data; + + /* Attempt to decode the certificate */ + ret = asn1_ber_decoder(&x509_decoder, ctx, data, datalen); + if (ret < 0) + goto error_decode; + + /* Decode the AuthorityKeyIdentifier */ + if (ctx->raw_akid) { + pr_devel("AKID: %u %*phN\n", + ctx->raw_akid_size, ctx->raw_akid_size, ctx->raw_akid); + ret = asn1_ber_decoder(&x509_akid_decoder, ctx, + ctx->raw_akid, ctx->raw_akid_size); + if (ret < 0) { + pr_warn("Couldn't decode AuthKeyIdentifier\n"); + goto error_decode; + } + } + + ret = -ENOMEM; + cert->pub->key = kmemdup(ctx->key, ctx->key_size, GFP_KERNEL); + if (!cert->pub->key) + goto error_decode; + + cert->pub->keylen = ctx->key_size; + + cert->pub->params = kmemdup(ctx->params, ctx->params_size, GFP_KERNEL); + if (!cert->pub->params) + goto error_decode; + + cert->pub->paramlen = ctx->params_size; + cert->pub->algo = ctx->key_algo; + + /* Grab the signature bits */ + ret = x509_get_sig_params(cert); + if (ret < 0) + goto error_decode; + + /* Generate cert issuer + serial number key ID */ + kid = asymmetric_key_generate_id(cert->raw_serial, + cert->raw_serial_size, + cert->raw_issuer, + cert->raw_issuer_size); + if (IS_ERR(kid)) { + ret = PTR_ERR(kid); + goto error_decode; + } + cert->id = kid; + +#ifndef __UBOOT__ + /* Detect self-signed certificates */ + ret = x509_check_for_self_signed(cert); + if (ret < 0) + goto error_decode; +#endif + + kfree(ctx); + return cert; + +error_decode: + kfree(ctx); +error_no_ctx: + x509_free_certificate(cert); +error_no_cert: + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(x509_cert_parse); + +/* + * Note an OID when we find one for later processing when we know how + * to interpret it. + */ +int x509_note_OID(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + ctx->last_oid = look_up_OID(value, vlen); + if (ctx->last_oid == OID__NR) { + char buffer[50]; + sprint_oid(value, vlen, buffer, sizeof(buffer)); + pr_debug("Unknown OID: [%lu] %s\n", + (unsigned long)value - ctx->data, buffer); + } + return 0; +} + +/* + * Save the position of the TBS data so that we can check the signature over it + * later. + */ +int x509_note_tbs_certificate(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + pr_debug("x509_note_tbs_certificate(,%zu,%02x,%ld,%zu)!\n", + hdrlen, tag, (unsigned long)value - ctx->data, vlen); + + ctx->cert->tbs = value - hdrlen; + ctx->cert->tbs_size = vlen + hdrlen; + return 0; +} + +/* + * Record the public key algorithm + */ +int x509_note_pkey_algo(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + pr_debug("PubKey Algo: %u\n", ctx->last_oid); + + switch (ctx->last_oid) { + case OID_md2WithRSAEncryption: + case OID_md3WithRSAEncryption: + default: + return -ENOPKG; /* Unsupported combination */ + + case OID_md4WithRSAEncryption: + ctx->cert->sig->hash_algo = "md4"; + goto rsa_pkcs1; + + case OID_sha1WithRSAEncryption: + ctx->cert->sig->hash_algo = "sha1"; + goto rsa_pkcs1; + + case OID_sha256WithRSAEncryption: + ctx->cert->sig->hash_algo = "sha256"; + goto rsa_pkcs1; + + case OID_sha384WithRSAEncryption: + ctx->cert->sig->hash_algo = "sha384"; + goto rsa_pkcs1; + + case OID_sha512WithRSAEncryption: + ctx->cert->sig->hash_algo = "sha512"; + goto rsa_pkcs1; + + case OID_sha224WithRSAEncryption: + ctx->cert->sig->hash_algo = "sha224"; + goto rsa_pkcs1; + + case OID_gost2012Signature256: + ctx->cert->sig->hash_algo = "streebog256"; + goto ecrdsa; + + case OID_gost2012Signature512: + ctx->cert->sig->hash_algo = "streebog512"; + goto ecrdsa; + } + +rsa_pkcs1: + ctx->cert->sig->pkey_algo = "rsa"; + ctx->cert->sig->encoding = "pkcs1"; + ctx->algo_oid = ctx->last_oid; + return 0; +ecrdsa: + ctx->cert->sig->pkey_algo = "ecrdsa"; + ctx->cert->sig->encoding = "raw"; + ctx->algo_oid = ctx->last_oid; + return 0; +} + +/* + * Note the whereabouts and type of the signature. + */ +int x509_note_signature(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + pr_debug("Signature type: %u size %zu\n", ctx->last_oid, vlen); + + if (ctx->last_oid != ctx->algo_oid) { + pr_warn("Got cert with pkey (%u) and sig (%u) algorithm OIDs\n", + ctx->algo_oid, ctx->last_oid); + return -EINVAL; + } + + if (strcmp(ctx->cert->sig->pkey_algo, "rsa") == 0 || + strcmp(ctx->cert->sig->pkey_algo, "ecrdsa") == 0) { + /* Discard the BIT STRING metadata */ + if (vlen < 1 || *(const u8 *)value != 0) + return -EBADMSG; + + value++; + vlen--; + } + + ctx->cert->raw_sig = value; + ctx->cert->raw_sig_size = vlen; + return 0; +} + +/* + * Note the certificate serial number + */ +int x509_note_serial(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + ctx->cert->raw_serial = value; + ctx->cert->raw_serial_size = vlen; + return 0; +} + +/* + * Note some of the name segments from which we'll fabricate a name. + */ +int x509_extract_name_segment(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + switch (ctx->last_oid) { + case OID_commonName: + ctx->cn_size = vlen; + ctx->cn_offset = (unsigned long)value - ctx->data; + break; + case OID_organizationName: + ctx->o_size = vlen; + ctx->o_offset = (unsigned long)value - ctx->data; + break; + case OID_email_address: + ctx->email_size = vlen; + ctx->email_offset = (unsigned long)value - ctx->data; + break; + default: + break; + } + + return 0; +} + +/* + * Fabricate and save the issuer and subject names + */ +static int x509_fabricate_name(struct x509_parse_context *ctx, size_t hdrlen, + unsigned char tag, + char **_name, size_t vlen) +{ + const void *name, *data = (const void *)ctx->data; + size_t namesize; + char *buffer; + + if (*_name) + return -EINVAL; + + /* Empty name string if no material */ + if (!ctx->cn_size && !ctx->o_size && !ctx->email_size) { + buffer = kmalloc(1, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + buffer[0] = 0; + goto done; + } + + if (ctx->cn_size && ctx->o_size) { + /* Consider combining O and CN, but use only the CN if it is + * prefixed by the O, or a significant portion thereof. + */ + namesize = ctx->cn_size; + name = data + ctx->cn_offset; + if (ctx->cn_size >= ctx->o_size && + memcmp(data + ctx->cn_offset, data + ctx->o_offset, + ctx->o_size) == 0) + goto single_component; + if (ctx->cn_size >= 7 && + ctx->o_size >= 7 && + memcmp(data + ctx->cn_offset, data + ctx->o_offset, 7) == 0) + goto single_component; + + buffer = kmalloc(ctx->o_size + 2 + ctx->cn_size + 1, + GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + memcpy(buffer, + data + ctx->o_offset, ctx->o_size); + buffer[ctx->o_size + 0] = ':'; + buffer[ctx->o_size + 1] = ' '; + memcpy(buffer + ctx->o_size + 2, + data + ctx->cn_offset, ctx->cn_size); + buffer[ctx->o_size + 2 + ctx->cn_size] = 0; + goto done; + + } else if (ctx->cn_size) { + namesize = ctx->cn_size; + name = data + ctx->cn_offset; + } else if (ctx->o_size) { + namesize = ctx->o_size; + name = data + ctx->o_offset; + } else { + namesize = ctx->email_size; + name = data + ctx->email_offset; + } + +single_component: + buffer = kmalloc(namesize + 1, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + memcpy(buffer, name, namesize); + buffer[namesize] = 0; + +done: + *_name = buffer; + ctx->cn_size = 0; + ctx->o_size = 0; + ctx->email_size = 0; + return 0; +} + +int x509_note_issuer(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + ctx->cert->raw_issuer = value; + ctx->cert->raw_issuer_size = vlen; + return x509_fabricate_name(ctx, hdrlen, tag, &ctx->cert->issuer, vlen); +} + +int x509_note_subject(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + ctx->cert->raw_subject = value; + ctx->cert->raw_subject_size = vlen; + return x509_fabricate_name(ctx, hdrlen, tag, &ctx->cert->subject, vlen); +} + +/* + * Extract the parameters for the public key + */ +int x509_note_params(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + /* + * AlgorithmIdentifier is used three times in the x509, we should skip + * first and ignore third, using second one which is after subject and + * before subjectPublicKey. + */ + if (!ctx->cert->raw_subject || ctx->key) + return 0; + ctx->params = value - hdrlen; + ctx->params_size = vlen + hdrlen; + return 0; +} + +/* + * Extract the data for the public key algorithm + */ +int x509_extract_key_data(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + ctx->key_algo = ctx->last_oid; + if (ctx->last_oid == OID_rsaEncryption) + ctx->cert->pub->pkey_algo = "rsa"; + else if (ctx->last_oid == OID_gost2012PKey256 || + ctx->last_oid == OID_gost2012PKey512) + ctx->cert->pub->pkey_algo = "ecrdsa"; + else + return -ENOPKG; + + /* Discard the BIT STRING metadata */ + if (vlen < 1 || *(const u8 *)value != 0) + return -EBADMSG; + ctx->key = value + 1; + ctx->key_size = vlen - 1; + return 0; +} + +/* The keyIdentifier in AuthorityKeyIdentifier SEQUENCE is tag(CONT,PRIM,0) */ +#define SEQ_TAG_KEYID (ASN1_CONT << 6) + +/* + * Process certificate extensions that are used to qualify the certificate. + */ +int x509_process_extension(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + struct asymmetric_key_id *kid; + const unsigned char *v = value; + + pr_debug("Extension: %u\n", ctx->last_oid); + + if (ctx->last_oid == OID_subjectKeyIdentifier) { + /* Get hold of the key fingerprint */ + if (ctx->cert->skid || vlen < 3) + return -EBADMSG; + if (v[0] != ASN1_OTS || v[1] != vlen - 2) + return -EBADMSG; + v += 2; + vlen -= 2; + + ctx->cert->raw_skid_size = vlen; + ctx->cert->raw_skid = v; + kid = asymmetric_key_generate_id(v, vlen, "", 0); + if (IS_ERR(kid)) + return PTR_ERR(kid); + ctx->cert->skid = kid; + pr_debug("subjkeyid %*phN\n", kid->len, kid->data); + return 0; + } + + if (ctx->last_oid == OID_authorityKeyIdentifier) { + /* Get hold of the CA key fingerprint */ + ctx->raw_akid = v; + ctx->raw_akid_size = vlen; + return 0; + } + + return 0; +} + +/** + * x509_decode_time - Decode an X.509 time ASN.1 object + * @_t: The time to fill in + * @hdrlen: The length of the object header + * @tag: The object tag + * @value: The object value + * @vlen: The size of the object value + * + * Decode an ASN.1 universal time or generalised time field into a struct the + * kernel can handle and check it for validity. The time is decoded thus: + * + * [RFC5280 §4.1.2.5] + * CAs conforming to this profile MUST always encode certificate validity + * dates through the year 2049 as UTCTime; certificate validity dates in + * 2050 or later MUST be encoded as GeneralizedTime. Conforming + * applications MUST be able to process validity dates that are encoded in + * either UTCTime or GeneralizedTime. + */ +int x509_decode_time(time64_t *_t, size_t hdrlen, + unsigned char tag, + const unsigned char *value, size_t vlen) +{ + static const unsigned char month_lengths[] = { 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 }; + const unsigned char *p = value; + unsigned year, mon, day, hour, min, sec, mon_len; + +#define dec2bin(X) ({ unsigned char x = (X) - '0'; if (x > 9) goto invalid_time; x; }) +#define DD2bin(P) ({ unsigned x = dec2bin(P[0]) * 10 + dec2bin(P[1]); P += 2; x; }) + + if (tag == ASN1_UNITIM) { + /* UTCTime: YYMMDDHHMMSSZ */ + if (vlen != 13) + goto unsupported_time; + year = DD2bin(p); + if (year >= 50) + year += 1900; + else + year += 2000; + } else if (tag == ASN1_GENTIM) { + /* GenTime: YYYYMMDDHHMMSSZ */ + if (vlen != 15) + goto unsupported_time; + year = DD2bin(p) * 100 + DD2bin(p); + if (year >= 1950 && year <= 2049) + goto invalid_time; + } else { + goto unsupported_time; + } + + mon = DD2bin(p); + day = DD2bin(p); + hour = DD2bin(p); + min = DD2bin(p); + sec = DD2bin(p); + + if (*p != 'Z') + goto unsupported_time; + + if (year < 1970 || + mon < 1 || mon > 12) + goto invalid_time; + + mon_len = month_lengths[mon - 1]; + if (mon == 2) { + if (year % 4 == 0) { + mon_len = 29; + if (year % 100 == 0) { + mon_len = 28; + if (year % 400 == 0) + mon_len = 29; + } + } + } + + if (day < 1 || day > mon_len || + hour > 24 || /* ISO 8601 permits 24:00:00 as midnight tomorrow */ + min > 59 || + sec > 60) /* ISO 8601 permits leap seconds [X.680 46.3] */ + goto invalid_time; + + *_t = mktime64(year, mon, day, hour, min, sec); + return 0; + +unsupported_time: + pr_debug("Got unsupported time [tag %02x]: '%*phN'\n", + tag, (int)vlen, value); + return -EBADMSG; +invalid_time: + pr_debug("Got invalid time [tag %02x]: '%*phN'\n", + tag, (int)vlen, value); + return -EBADMSG; +} +EXPORT_SYMBOL_GPL(x509_decode_time); + +int x509_note_not_before(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + return x509_decode_time(&ctx->cert->valid_from, hdrlen, tag, value, vlen); +} + +int x509_note_not_after(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + return x509_decode_time(&ctx->cert->valid_to, hdrlen, tag, value, vlen); +} + +/* + * Note a key identifier-based AuthorityKeyIdentifier + */ +int x509_akid_note_kid(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + struct asymmetric_key_id *kid; + + pr_debug("AKID: keyid: %*phN\n", (int)vlen, value); + + if (ctx->cert->sig->auth_ids[1]) + return 0; + + kid = asymmetric_key_generate_id(value, vlen, "", 0); + if (IS_ERR(kid)) + return PTR_ERR(kid); + pr_debug("authkeyid %*phN\n", kid->len, kid->data); + ctx->cert->sig->auth_ids[1] = kid; + return 0; +} + +/* + * Note a directoryName in an AuthorityKeyIdentifier + */ +int x509_akid_note_name(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + pr_debug("AKID: name: %*phN\n", (int)vlen, value); + + ctx->akid_raw_issuer = value; + ctx->akid_raw_issuer_size = vlen; + return 0; +} + +/* + * Note a serial number in an AuthorityKeyIdentifier + */ +int x509_akid_note_serial(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + struct asymmetric_key_id *kid; + + pr_debug("AKID: serial: %*phN\n", (int)vlen, value); + + if (!ctx->akid_raw_issuer || ctx->cert->sig->auth_ids[0]) + return 0; + + kid = asymmetric_key_generate_id(value, + vlen, + ctx->akid_raw_issuer, + ctx->akid_raw_issuer_size); + if (IS_ERR(kid)) + return PTR_ERR(kid); + + pr_debug("authkeyid %*phN\n", kid->len, kid->data); + ctx->cert->sig->auth_ids[0] = kid; + return 0; +} diff --git a/lib/crypto/x509_parser.h b/lib/crypto/x509_parser.h new file mode 100644 index 0000000000..c233f136fb --- /dev/null +++ b/lib/crypto/x509_parser.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* X.509 certificate parser internal definitions + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#include <linux/time.h> +#include <crypto/public_key.h> +#include <keys/asymmetric-type.h> + +struct x509_certificate { + struct x509_certificate *next; + struct x509_certificate *signer; /* Certificate that signed this one */ + struct public_key *pub; /* Public key details */ + struct public_key_signature *sig; /* Signature parameters */ + char *issuer; /* Name of certificate issuer */ + char *subject; /* Name of certificate subject */ + struct asymmetric_key_id *id; /* Issuer + Serial number */ + struct asymmetric_key_id *skid; /* Subject + subjectKeyId (optional) */ + time64_t valid_from; + time64_t valid_to; + const void *tbs; /* Signed data */ + unsigned tbs_size; /* Size of signed data */ + unsigned raw_sig_size; /* Size of sigature */ + const void *raw_sig; /* Signature data */ + const void *raw_serial; /* Raw serial number in ASN.1 */ + unsigned raw_serial_size; + unsigned raw_issuer_size; + const void *raw_issuer; /* Raw issuer name in ASN.1 */ + const void *raw_subject; /* Raw subject name in ASN.1 */ + unsigned raw_subject_size; + unsigned raw_skid_size; + const void *raw_skid; /* Raw subjectKeyId in ASN.1 */ + unsigned index; + bool seen; /* Infinite recursion prevention */ + bool verified; + bool self_signed; /* T if self-signed (check unsupported_sig too) */ + bool unsupported_key; /* T if key uses unsupported crypto */ + bool unsupported_sig; /* T if signature uses unsupported crypto */ + bool blacklisted; +}; + +/* + * x509_cert_parser.c + */ +extern void x509_free_certificate(struct x509_certificate *cert); +extern struct x509_certificate *x509_cert_parse(const void *data, size_t datalen); +extern int x509_decode_time(time64_t *_t, size_t hdrlen, + unsigned char tag, + const unsigned char *value, size_t vlen); + +/* + * x509_public_key.c + */ +extern int x509_get_sig_params(struct x509_certificate *cert); +extern int x509_check_for_self_signed(struct x509_certificate *cert); diff --git a/lib/crypto/x509_public_key.c b/lib/crypto/x509_public_key.c new file mode 100644 index 0000000000..04bdb672b4 --- /dev/null +++ b/lib/crypto/x509_public_key.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Instantiate a public key crypto key from an X.509 Certificate + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#define pr_fmt(fmt) "X.509: "fmt +#ifdef __UBOOT__ +#include <common.h> +#include <linux/compat.h> +#include <linux/errno.h> +#else +#include <linux/module.h> +#endif +#include <linux/kernel.h> +#ifndef __UBOOT__ +#include <linux/slab.h> +#include <keys/asymmetric-subtype.h> +#include <keys/asymmetric-parser.h> +#include <keys/system_keyring.h> +#include <crypto/hash.h> +#include "asymmetric_keys.h" +#endif +#include "x509_parser.h" + +/* + * Set up the signature parameters in an X.509 certificate. This involves + * digesting the signed data and extracting the signature. + */ +int x509_get_sig_params(struct x509_certificate *cert) +{ + struct public_key_signature *sig = cert->sig; +#ifndef __UBOOT__ + struct crypto_shash *tfm; + struct shash_desc *desc; + size_t desc_size; +#endif + int ret; + + pr_devel("==>%s()\n", __func__); + + if (!cert->pub->pkey_algo) + cert->unsupported_key = true; + + if (!sig->pkey_algo) + cert->unsupported_sig = true; + + /* We check the hash if we can - even if we can't then verify it */ + if (!sig->hash_algo) { + cert->unsupported_sig = true; + return 0; + } + + sig->s = kmemdup(cert->raw_sig, cert->raw_sig_size, GFP_KERNEL); + if (!sig->s) + return -ENOMEM; + + sig->s_size = cert->raw_sig_size; + +#ifdef __UBOOT__ + /* + * Note: + * This part (filling sig->digest) should be implemented if + * x509_check_for_self_signed() is enabled x509_cert_parse(). + * Currently, this check won't affect UEFI secure boot. + */ + ret = 0; +#else + /* Allocate the hashing algorithm we're going to need and find out how + * big the hash operational data will be. + */ + tfm = crypto_alloc_shash(sig->hash_algo, 0, 0); + if (IS_ERR(tfm)) { + if (PTR_ERR(tfm) == -ENOENT) { + cert->unsupported_sig = true; + return 0; + } + return PTR_ERR(tfm); + } + + desc_size = crypto_shash_descsize(tfm) + sizeof(*desc); + sig->digest_size = crypto_shash_digestsize(tfm); + + ret = -ENOMEM; + sig->digest = kmalloc(sig->digest_size, GFP_KERNEL); + if (!sig->digest) + goto error; + + desc = kzalloc(desc_size, GFP_KERNEL); + if (!desc) + goto error; + + desc->tfm = tfm; + + ret = crypto_shash_digest(desc, cert->tbs, cert->tbs_size, sig->digest); + if (ret < 0) + goto error_2; + + ret = is_hash_blacklisted(sig->digest, sig->digest_size, "tbs"); + if (ret == -EKEYREJECTED) { + pr_err("Cert %*phN is blacklisted\n", + sig->digest_size, sig->digest); + cert->blacklisted = true; + ret = 0; + } + +error_2: + kfree(desc); +error: + crypto_free_shash(tfm); +#endif /* __UBOOT__ */ + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} + +#ifndef __UBOOT__ +/* + * Check for self-signedness in an X.509 cert and if found, check the signature + * immediately if we can. + */ +int x509_check_for_self_signed(struct x509_certificate *cert) +{ + int ret = 0; + + pr_devel("==>%s()\n", __func__); + + if (cert->raw_subject_size != cert->raw_issuer_size || + memcmp(cert->raw_subject, cert->raw_issuer, + cert->raw_issuer_size) != 0) + goto not_self_signed; + + if (cert->sig->auth_ids[0] || cert->sig->auth_ids[1]) { + /* If the AKID is present it may have one or two parts. If + * both are supplied, both must match. + */ + bool a = asymmetric_key_id_same(cert->skid, cert->sig->auth_ids[1]); + bool b = asymmetric_key_id_same(cert->id, cert->sig->auth_ids[0]); + + if (!a && !b) + goto not_self_signed; + + ret = -EKEYREJECTED; + if (((a && !b) || (b && !a)) && + cert->sig->auth_ids[0] && cert->sig->auth_ids[1]) + goto out; + } + + ret = -EKEYREJECTED; + if (strcmp(cert->pub->pkey_algo, cert->sig->pkey_algo) != 0) + goto out; + + ret = public_key_verify_signature(cert->pub, cert->sig); + if (ret < 0) { + if (ret == -ENOPKG) { + cert->unsupported_sig = true; + ret = 0; + } + goto out; + } + + pr_devel("Cert Self-signature verified"); + cert->self_signed = true; + +out: + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; + +not_self_signed: + pr_devel("<==%s() = 0 [not]\n", __func__); + return 0; +} + +/* + * Attempt to parse a data blob for a key as an X509 certificate. + */ +static int x509_key_preparse(struct key_preparsed_payload *prep) +{ + struct asymmetric_key_ids *kids; + struct x509_certificate *cert; + const char *q; + size_t srlen, sulen; + char *desc = NULL, *p; + int ret; + + cert = x509_cert_parse(prep->data, prep->datalen); + if (IS_ERR(cert)) + return PTR_ERR(cert); + + pr_devel("Cert Issuer: %s\n", cert->issuer); + pr_devel("Cert Subject: %s\n", cert->subject); + + if (cert->unsupported_key) { + ret = -ENOPKG; + goto error_free_cert; + } + + pr_devel("Cert Key Algo: %s\n", cert->pub->pkey_algo); + pr_devel("Cert Valid period: %lld-%lld\n", cert->valid_from, cert->valid_to); + + cert->pub->id_type = "X509"; + + if (cert->unsupported_sig) { + public_key_signature_free(cert->sig); + cert->sig = NULL; + } else { + pr_devel("Cert Signature: %s + %s\n", + cert->sig->pkey_algo, cert->sig->hash_algo); + } + + /* Don't permit addition of blacklisted keys */ + ret = -EKEYREJECTED; + if (cert->blacklisted) + goto error_free_cert; + + /* Propose a description */ + sulen = strlen(cert->subject); + if (cert->raw_skid) { + srlen = cert->raw_skid_size; + q = cert->raw_skid; + } else { + srlen = cert->raw_serial_size; + q = cert->raw_serial; + } + + ret = -ENOMEM; + desc = kmalloc(sulen + 2 + srlen * 2 + 1, GFP_KERNEL); + if (!desc) + goto error_free_cert; + p = memcpy(desc, cert->subject, sulen); + p += sulen; + *p++ = ':'; + *p++ = ' '; + p = bin2hex(p, q, srlen); + *p = 0; + + kids = kmalloc(sizeof(struct asymmetric_key_ids), GFP_KERNEL); + if (!kids) + goto error_free_desc; + kids->id[0] = cert->id; + kids->id[1] = cert->skid; + + /* We're pinning the module by being linked against it */ + __module_get(public_key_subtype.owner); + prep->payload.data[asym_subtype] = &public_key_subtype; + prep->payload.data[asym_key_ids] = kids; + prep->payload.data[asym_crypto] = cert->pub; + prep->payload.data[asym_auth] = cert->sig; + prep->description = desc; + prep->quotalen = 100; + + /* We've finished with the certificate */ + cert->pub = NULL; + cert->id = NULL; + cert->skid = NULL; + cert->sig = NULL; + desc = NULL; + ret = 0; + +error_free_desc: + kfree(desc); +error_free_cert: + x509_free_certificate(cert); + return ret; +} + +static struct asymmetric_key_parser x509_key_parser = { + .owner = THIS_MODULE, + .name = "x509", + .parse = x509_key_preparse, +}; + +/* + * Module stuff + */ +static int __init x509_key_init(void) +{ + return register_asymmetric_key_parser(&x509_key_parser); +} + +static void __exit x509_key_exit(void) +{ + unregister_asymmetric_key_parser(&x509_key_parser); +} + +module_init(x509_key_init); +module_exit(x509_key_exit); +#endif /* !__UBOOT__ */ + +MODULE_DESCRIPTION("X.509 certificate parser"); +MODULE_AUTHOR("Red Hat, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/date.c b/lib/date.c index c57317d2c2..0456de78ab 100644 --- a/drivers/rtc/date.c +++ b/lib/date.c @@ -8,9 +8,9 @@ #include <command.h> #include <errno.h> #include <rtc.h> +#include <linux/time.h> -#if defined(CONFIG_CMD_DATE) || defined(CONFIG_DM_RTC) || \ - defined(CONFIG_TIMESTAMP) +#if defined(CONFIG_LIB_DATE) || defined(CONFIG_TIMESTAMP) #define FEBRUARY 2 #define STARTOFTIME 1970 @@ -97,4 +97,23 @@ unsigned long rtc_mktime(const struct rtc_time *tm) return (hours * 60 + tm->tm_min) * 60 + tm->tm_sec; } +#endif /* CONFIG_LIB_DATE || CONFIG_TIMESTAMP */ + +#ifdef CONFIG_LIB_DATE +/* for compatibility with linux code */ +time64_t mktime64(const unsigned int year, const unsigned int mon, + const unsigned int day, const unsigned int hour, + const unsigned int min, const unsigned int sec) +{ + struct rtc_time time; + + time.tm_year = year; + time.tm_mon = mon; + time.tm_mday = day; + time.tm_hour = hour; + time.tm_min = min; + time.tm_sec = sec; + + return (time64_t)rtc_mktime((const struct rtc_time *)&time); +} #endif diff --git a/lib/linux_compat.c b/lib/linux_compat.c index 81ea8fb126..3f440deaa0 100644 --- a/lib/linux_compat.c +++ b/lib/linux_compat.c @@ -40,3 +40,22 @@ void *kmem_cache_alloc(struct kmem_cache *obj, int flag) { return malloc_cache_aligned(obj->sz); } + +/** + * kmemdup - duplicate region of memory + * + * @src: memory region to duplicate + * @len: memory region length + * @gfp: GFP mask to use + * + * Return: newly allocated copy of @src or %NULL in case of error + */ +void *kmemdup(const void *src, size_t len, gfp_t gfp) +{ + void *p; + + p = kmalloc(len, gfp); + if (p) + memcpy(p, src, len); + return p; +} diff --git a/lib/oid_registry.c b/lib/oid_registry.c new file mode 100644 index 0000000000..209edc73b9 --- /dev/null +++ b/lib/oid_registry.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* ASN.1 Object identifier (OID) registry + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#ifdef __UBOOT__ +#include <linux/compat.h> +#else +#include <linux/module.h> +#include <linux/export.h> +#endif +#include <linux/oid_registry.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/bug.h> +#include "oid_registry_data.c" + +MODULE_DESCRIPTION("OID Registry"); +MODULE_AUTHOR("Red Hat, Inc."); +MODULE_LICENSE("GPL"); + +/** + * look_up_OID - Find an OID registration for the specified data + * @data: Binary representation of the OID + * @datasize: Size of the binary representation + */ +enum OID look_up_OID(const void *data, size_t datasize) +{ + const unsigned char *octets = data; + enum OID oid; + unsigned char xhash; + unsigned i, j, k, hash; + size_t len; + + /* Hash the OID data */ + hash = datasize - 1; + + for (i = 0; i < datasize; i++) + hash += octets[i] * 33; + hash = (hash >> 24) ^ (hash >> 16) ^ (hash >> 8) ^ hash; + hash &= 0xff; + + /* Binary search the OID registry. OIDs are stored in ascending order + * of hash value then ascending order of size and then in ascending + * order of reverse value. + */ + i = 0; + k = OID__NR; + while (i < k) { + j = (i + k) / 2; + + xhash = oid_search_table[j].hash; + if (xhash > hash) { + k = j; + continue; + } + if (xhash < hash) { + i = j + 1; + continue; + } + + oid = oid_search_table[j].oid; + len = oid_index[oid + 1] - oid_index[oid]; + if (len > datasize) { + k = j; + continue; + } + if (len < datasize) { + i = j + 1; + continue; + } + + /* Variation is most likely to be at the tail end of the + * OID, so do the comparison in reverse. + */ + while (len > 0) { + unsigned char a = oid_data[oid_index[oid] + --len]; + unsigned char b = octets[len]; + if (a > b) { + k = j; + goto next; + } + if (a < b) { + i = j + 1; + goto next; + } + } + return oid; + next: + ; + } + + return OID__NR; +} +EXPORT_SYMBOL_GPL(look_up_OID); + +/* + * sprint_OID - Print an Object Identifier into a buffer + * @data: The encoded OID to print + * @datasize: The size of the encoded OID + * @buffer: The buffer to render into + * @bufsize: The size of the buffer + * + * The OID is rendered into the buffer in "a.b.c.d" format and the number of + * bytes is returned. -EBADMSG is returned if the data could not be intepreted + * and -ENOBUFS if the buffer was too small. + */ +int sprint_oid(const void *data, size_t datasize, char *buffer, size_t bufsize) +{ + const unsigned char *v = data, *end = v + datasize; + unsigned long num; + unsigned char n; + size_t ret; + int count; + + if (v >= end) + goto bad; + + n = *v++; + ret = count = snprintf(buffer, bufsize, "%u.%u", n / 40, n % 40); + if (count >= bufsize) + return -ENOBUFS; + buffer += count; + bufsize -= count; + + while (v < end) { + num = 0; + n = *v++; + if (!(n & 0x80)) { + num = n; + } else { + num = n & 0x7f; + do { + if (v >= end) + goto bad; + n = *v++; + num <<= 7; + num |= n & 0x7f; + } while (n & 0x80); + } + ret += count = snprintf(buffer, bufsize, ".%lu", num); + if (count >= bufsize) + return -ENOBUFS; + buffer += count; + bufsize -= count; + } + + return ret; + +bad: + snprintf(buffer, bufsize, "(bad)"); + return -EBADMSG; +} +EXPORT_SYMBOL_GPL(sprint_oid); + +/** + * sprint_OID - Print an Object Identifier into a buffer + * @oid: The OID to print + * @buffer: The buffer to render into + * @bufsize: The size of the buffer + * + * The OID is rendered into the buffer in "a.b.c.d" format and the number of + * bytes is returned. + */ +int sprint_OID(enum OID oid, char *buffer, size_t bufsize) +{ + int ret; + + BUG_ON(oid >= OID__NR); + + ret = sprint_oid(oid_data + oid_index[oid], + oid_index[oid + 1] - oid_index[oid], + buffer, bufsize); + BUG_ON(ret == -EBADMSG); + return ret; +} +EXPORT_SYMBOL_GPL(sprint_OID); diff --git a/scripts/Makefile.build b/scripts/Makefile.build index f7a041296d..26eb701f8d 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -328,10 +328,10 @@ $(obj)/%.lds: $(src)/%.lds.S FORCE # ASN.1 grammar # --------------------------------------------------------------------------- quiet_cmd_asn1_compiler = ASN.1 $@ - cmd_asn1_compiler = $(objtree)/scripts/asn1_compiler $< \ + cmd_asn1_compiler = $(objtree)/tools/asn1_compiler $< \ $(subst .h,.c,$@) $(subst .c,.h,$@) -$(obj)/%-asn1.c $(obj)/%-asn1.h: $(src)/%.asn1 $(objtree)/scripts/asn1_compiler +$(obj)/%.asn1.c $(obj)/%.asn1.h: $(src)/%.asn1 $(objtree)/tools/asn1_compiler $(call cmd,asn1_compiler) # Build the compiled-in targets diff --git a/scripts/build_OID_registry b/scripts/build_OID_registry new file mode 100755 index 0000000000..d7fc32ea8a --- /dev/null +++ b/scripts/build_OID_registry @@ -0,0 +1,203 @@ +#!/usr/bin/perl -w +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Build a static ASN.1 Object Identified (OID) registry +# +# Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. +# Written by David Howells (dhowells@redhat.com) +# + +use strict; + +my @names = (); +my @oids = (); + +if ($#ARGV != 1) { + print STDERR "Format: ", $0, " <in-h-file> <out-c-file>\n"; + exit(2); +} + +# +# Open the file to read from +# +open IN_FILE, "<$ARGV[0]" || die; +while (<IN_FILE>) { + chomp; + if (m!\s+OID_([a-zA-z][a-zA-Z0-9_]+),\s+/[*]\s+([012][.0-9]*)\s+[*]/!) { + push @names, $1; + push @oids, $2; + } +} +close IN_FILE || die; + +# +# Open the files to write into +# +open C_FILE, ">$ARGV[1]" or die; +print C_FILE "/*\n"; +print C_FILE " * Automatically generated by ", $0, ". Do not edit\n"; +print C_FILE " */\n"; + +# +# Split the data up into separate lists and also determine the lengths of the +# encoded data arrays. +# +my @indices = (); +my @lengths = (); +my $total_length = 0; + +for (my $i = 0; $i <= $#names; $i++) { + my $name = $names[$i]; + my $oid = $oids[$i]; + + my @components = split(/[.]/, $oid); + + # Determine the encoded length of this OID + my $size = $#components; + for (my $loop = 2; $loop <= $#components; $loop++) { + my $c = $components[$loop]; + + # We will base128 encode the number + my $tmp = ($c == 0) ? 0 : int(log($c)/log(2)); + $tmp = int($tmp / 7); + $size += $tmp; + } + push @lengths, $size; + push @indices, $total_length; + $total_length += $size; +} + +# +# Emit the look-up-by-OID index table +# +print C_FILE "\n"; +if ($total_length <= 255) { + print C_FILE "static const unsigned char oid_index[OID__NR + 1] = {\n"; +} else { + print C_FILE "static const unsigned short oid_index[OID__NR + 1] = {\n"; +} +for (my $i = 0; $i <= $#names; $i++) { + print C_FILE "\t[OID_", $names[$i], "] = ", $indices[$i], ",\n" +} +print C_FILE "\t[OID__NR] = ", $total_length, "\n"; +print C_FILE "};\n"; + +# +# Encode the OIDs +# +my @encoded_oids = (); + +for (my $i = 0; $i <= $#names; $i++) { + my @octets = (); + + my @components = split(/[.]/, $oids[$i]); + + push @octets, $components[0] * 40 + $components[1]; + + for (my $loop = 2; $loop <= $#components; $loop++) { + my $c = $components[$loop]; + + # Base128 encode the number + my $tmp = ($c == 0) ? 0 : int(log($c)/log(2)); + $tmp = int($tmp / 7); + + for (; $tmp > 0; $tmp--) { + push @octets, (($c >> $tmp * 7) & 0x7f) | 0x80; + } + push @octets, $c & 0x7f; + } + + push @encoded_oids, \@octets; +} + +# +# Create a hash value for each OID +# +my @hash_values = (); +for (my $i = 0; $i <= $#names; $i++) { + my @octets = @{$encoded_oids[$i]}; + + my $hash = $#octets; + foreach (@octets) { + $hash += $_ * 33; + } + + $hash = ($hash >> 24) ^ ($hash >> 16) ^ ($hash >> 8) ^ ($hash); + + push @hash_values, $hash & 0xff; +} + +# +# Emit the OID data +# +print C_FILE "\n"; +print C_FILE "static const unsigned char oid_data[", $total_length, "] = {\n"; +for (my $i = 0; $i <= $#names; $i++) { + my @octets = @{$encoded_oids[$i]}; + print C_FILE "\t"; + print C_FILE $_, ", " foreach (@octets); + print C_FILE "\t// ", $names[$i]; + print C_FILE "\n"; +} +print C_FILE "};\n"; + +# +# Build the search index table (ordered by length then hash then content) +# +my @index_table = ( 0 .. $#names ); + +@index_table = sort { + my @octets_a = @{$encoded_oids[$a]}; + my @octets_b = @{$encoded_oids[$b]}; + + return $hash_values[$a] <=> $hash_values[$b] + if ($hash_values[$a] != $hash_values[$b]); + return $#octets_a <=> $#octets_b + if ($#octets_a != $#octets_b); + for (my $i = $#octets_a; $i >= 0; $i--) { + return $octets_a[$i] <=> $octets_b[$i] + if ($octets_a[$i] != $octets_b[$i]); + } + return 0; + +} @index_table; + +# +# Emit the search index and hash value table +# +print C_FILE "\n"; +print C_FILE "static const struct {\n"; +print C_FILE "\tunsigned char hash;\n"; +if ($#names <= 255) { + print C_FILE "\tenum OID oid : 8;\n"; +} else { + print C_FILE "\tenum OID oid : 16;\n"; +} +print C_FILE "} oid_search_table[OID__NR] = {\n"; +for (my $i = 0; $i <= $#names; $i++) { + my @octets = @{$encoded_oids[$index_table[$i]]}; + printf(C_FILE "\t[%3u] = { %3u, OID_%-35s }, // ", + $i, + $hash_values[$index_table[$i]], + $names[$index_table[$i]]); + printf C_FILE "%02x", $_ foreach (@octets); + print C_FILE "\n"; +} +print C_FILE "};\n"; + +# +# Emit the OID debugging name table +# +#print C_FILE "\n"; +#print C_FILE "const char *const oid_name_table[OID__NR + 1] = {\n"; +# +#for (my $i = 0; $i <= $#names; $i++) { +# print C_FILE "\t\"", $names[$i], "\",\n" +#} +#print C_FILE "\t\"Unknown-OID\"\n"; +#print C_FILE "};\n"; + +# +# Polish off +# +close C_FILE or die; diff --git a/test/Kconfig b/test/Kconfig index f53629aac5..cb51b46721 100644 --- a/test/Kconfig +++ b/test/Kconfig @@ -12,7 +12,23 @@ config UT_LIB default y help Enables the 'ut lib' command which tests library functions like - memcat(), memcyp(), memmove(). + memcat(), memcyp(), memmove() and ASN1 compiler/decoder. + +if UT_LIB + +config UT_LIB_ASN1 + bool "Unit test for asn1 compiler and decoder function" + default y + imply ASYMMETRIC_KEY_TYPE + imply ASYMMETRIC_PUBLIC_KEY_SUBTYPE + imply X509_CERTIFICATE_PARSER + imply PKCS7_MESSAGE_PARSER + imply RSA_PUBLIC_KEY_PARSER + help + Enables a test which exercises asn1 compiler and decoder function + via various parsers. + +endif config UT_TIME bool "Unit tests for time functions" diff --git a/test/lib/Makefile b/test/lib/Makefile index b13aaca7ce..72d2ec74b5 100644 --- a/test/lib/Makefile +++ b/test/lib/Makefile @@ -7,3 +7,4 @@ obj-y += hexdump.o obj-y += lmb.o obj-y += string.o obj-$(CONFIG_ERRNO_STR) += test_errno_str.o +obj-$(CONFIG_UT_LIB_ASN1) += asn1.o diff --git a/test/lib/asn1.c b/test/lib/asn1.c new file mode 100644 index 0000000000..d2b3f67e68 --- /dev/null +++ b/test/lib/asn1.c @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2019 Linaro Limited + * Author: AKASHI Takahiro + * + * Unit test for asn1 compiler and asn1 decoder function via various parsers + */ + +#include <common.h> +#include <command.h> +#include <test/lib.h> +#include <test/test.h> +#include <test/ut.h> + +#ifdef CONFIG_PKCS7_MESSAGE_PARSER +#include "../../lib/crypto/pkcs7_parser.h" +#else +#ifdef CONFIG_X509_CERTIFICATE_PARSER +#include "../../lib/crypto/x509_parser.h" +#endif +#endif + +#ifdef CONFIG_X509_CERTIFICATE_PARSER +static const unsigned char cert_data[] = { + 0x30, 0x82, 0x03, 0xc7, 0x30, 0x82, 0x02, 0xaf, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x09, 0x00, 0xd7, 0x17, 0x0a, 0x76, 0xd5, 0xd3, 0x4d, 0xeb, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0b, 0x05, 0x00, 0x30, 0x7a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, + 0x55, 0x04, 0x08, 0x0c, 0x05, 0x54, 0x6f, 0x6b, 0x79, 0x6f, 0x31, 0x0e, + 0x30, 0x0c, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x05, 0x54, 0x6f, 0x6b, + 0x79, 0x6f, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, + 0x06, 0x4c, 0x69, 0x6e, 0x61, 0x72, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x02, 0x53, 0x57, 0x31, 0x0f, 0x30, 0x0d, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x06, 0x54, 0x65, 0x73, 0x74, 0x65, + 0x72, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x09, 0x01, 0x16, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x40, 0x74, + 0x65, 0x73, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x1e, 0x17, 0x0d, 0x31, + 0x39, 0x31, 0x30, 0x31, 0x38, 0x30, 0x33, 0x31, 0x33, 0x33, 0x31, 0x5a, + 0x17, 0x0d, 0x32, 0x30, 0x31, 0x30, 0x31, 0x37, 0x30, 0x33, 0x31, 0x33, + 0x33, 0x31, 0x5a, 0x30, 0x7a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, + 0x55, 0x04, 0x08, 0x0c, 0x05, 0x54, 0x6f, 0x6b, 0x79, 0x6f, 0x31, 0x0e, + 0x30, 0x0c, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x05, 0x54, 0x6f, 0x6b, + 0x79, 0x6f, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, + 0x06, 0x4c, 0x69, 0x6e, 0x61, 0x72, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x02, 0x53, 0x57, 0x31, 0x0f, 0x30, 0x0d, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x06, 0x54, 0x65, 0x73, 0x74, 0x65, + 0x72, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x09, 0x01, 0x16, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x40, 0x74, + 0x65, 0x73, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x82, 0x01, 0x22, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, + 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, + 0x82, 0x01, 0x01, 0x00, 0x9f, 0x37, 0x4d, 0x95, 0x7e, 0x36, 0xb7, 0xaf, + 0xf4, 0xd6, 0xce, 0x39, 0x04, 0xee, 0xbf, 0x36, 0xb2, 0xcc, 0xa3, 0x8b, + 0x9e, 0xac, 0x62, 0x8a, 0xe9, 0xae, 0x18, 0xcf, 0xe8, 0x95, 0xfd, 0xcb, + 0xad, 0x34, 0x8a, 0x5f, 0x55, 0xe6, 0x0c, 0x5e, 0xf8, 0x76, 0xc1, 0xa2, + 0xc3, 0xd4, 0x73, 0x13, 0x8a, 0x71, 0x1b, 0xfd, 0x58, 0x27, 0xea, 0x4d, + 0x41, 0xff, 0x63, 0xbb, 0xad, 0x97, 0x62, 0xba, 0xe4, 0xe5, 0x97, 0x45, + 0xa3, 0x5b, 0xd5, 0x5b, 0x53, 0x55, 0x10, 0x19, 0xfa, 0xac, 0xbd, 0xdb, + 0x77, 0x62, 0x23, 0x50, 0x3f, 0x35, 0xdb, 0x8a, 0xf6, 0xee, 0x7a, 0x31, + 0xec, 0x92, 0xf5, 0x78, 0x35, 0x92, 0x76, 0x3c, 0x5f, 0xe7, 0xee, 0xc9, + 0xed, 0x01, 0x1c, 0x42, 0x55, 0xd6, 0x7e, 0xa6, 0xca, 0x7c, 0xd1, 0x15, + 0x16, 0x87, 0x7c, 0x99, 0x63, 0xc0, 0xa9, 0x25, 0x49, 0xbc, 0x4e, 0xdc, + 0x2d, 0x4b, 0xcb, 0x52, 0xd7, 0x67, 0xe9, 0x83, 0x6b, 0x5e, 0x5b, 0x48, + 0x80, 0x33, 0xe9, 0xcc, 0xe8, 0xfe, 0x19, 0xc8, 0xc2, 0x61, 0x74, 0x52, + 0x25, 0x92, 0x48, 0xea, 0xad, 0x15, 0x16, 0x64, 0x6e, 0x53, 0x30, 0x77, + 0xa2, 0xef, 0x61, 0x92, 0x1b, 0x5e, 0xbe, 0x07, 0xf2, 0x3c, 0xf8, 0x35, + 0x7d, 0x76, 0x4f, 0x78, 0xa9, 0x2a, 0xf1, 0x32, 0xff, 0xec, 0x89, 0xa9, + 0x22, 0x4c, 0x3d, 0xc8, 0x65, 0xca, 0xf4, 0xa2, 0x6d, 0x3f, 0xa4, 0x0a, + 0xfa, 0x9e, 0xe4, 0xf0, 0xdb, 0x39, 0xb1, 0xf9, 0xf0, 0xfb, 0x04, 0x81, + 0x44, 0xa7, 0xd7, 0x61, 0xdf, 0x2d, 0x13, 0x45, 0x2c, 0xae, 0xf0, 0x0e, + 0xc4, 0x07, 0x5d, 0x7d, 0x2b, 0xb2, 0x20, 0x75, 0x33, 0x6b, 0x5b, 0xf7, + 0xe7, 0x17, 0x51, 0xf1, 0xab, 0xc1, 0x9e, 0xc6, 0xf0, 0x30, 0xc6, 0x25, + 0x26, 0x3e, 0xd7, 0xd7, 0xa3, 0xcc, 0x15, 0x95, 0x02, 0x03, 0x01, 0x00, + 0x01, 0xa3, 0x50, 0x30, 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, + 0x04, 0x16, 0x04, 0x14, 0x45, 0x8a, 0x76, 0xf7, 0x4f, 0xf4, 0x0e, 0xa0, + 0xf2, 0x02, 0xe1, 0xe7, 0xe9, 0xc7, 0x7d, 0x51, 0x55, 0x92, 0x33, 0xcd, + 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, + 0x14, 0x45, 0x8a, 0x76, 0xf7, 0x4f, 0xf4, 0x0e, 0xa0, 0xf2, 0x02, 0xe1, + 0xe7, 0xe9, 0xc7, 0x7d, 0x51, 0x55, 0x92, 0x33, 0xcd, 0x30, 0x0c, 0x06, + 0x03, 0x55, 0x1d, 0x13, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, + 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x47, 0x93, 0x82, 0x0e, 0x8a, + 0x70, 0x9d, 0x6c, 0x7a, 0xdb, 0x04, 0xb4, 0xc9, 0xef, 0x98, 0x28, 0xc6, + 0xd9, 0x53, 0x90, 0xc8, 0x25, 0x83, 0x07, 0x23, 0xe7, 0x59, 0x38, 0xc1, + 0xc0, 0x50, 0x28, 0x99, 0x92, 0xfb, 0x21, 0x24, 0x72, 0xe5, 0xa6, 0x57, + 0x30, 0x31, 0xb3, 0xdf, 0xa0, 0x17, 0xa9, 0x73, 0x9c, 0x39, 0x83, 0xfb, + 0xe4, 0xfa, 0x20, 0x1d, 0xfa, 0x33, 0x20, 0x0c, 0x72, 0x2a, 0x50, 0x40, + 0xbd, 0x2d, 0x33, 0xa2, 0xfc, 0x06, 0xf9, 0xfe, 0x86, 0x4f, 0x50, 0x1d, + 0x65, 0x37, 0xe9, 0x30, 0x33, 0x82, 0xa1, 0x75, 0x8f, 0x5d, 0x33, 0x84, + 0x0d, 0xf2, 0x09, 0x04, 0xc0, 0x7a, 0x12, 0x79, 0xdb, 0x4f, 0x77, 0x04, + 0xe4, 0xd8, 0x0b, 0x87, 0x19, 0xba, 0xb7, 0x3c, 0xa6, 0x45, 0xaa, 0x91, + 0x62, 0x7f, 0x01, 0x7d, 0xc6, 0x20, 0x6d, 0x71, 0x15, 0x74, 0x5e, 0x87, + 0xb3, 0x60, 0x17, 0x9c, 0xc0, 0xed, 0x01, 0x4b, 0xb3, 0x23, 0x24, 0xc1, + 0xcb, 0x7a, 0x83, 0x03, 0x26, 0x2d, 0xde, 0x47, 0xc5, 0x11, 0x94, 0x28, + 0x27, 0x15, 0x92, 0x00, 0x8b, 0x2e, 0x51, 0x42, 0xca, 0x4b, 0x4a, 0x2c, + 0x51, 0x37, 0x56, 0xd0, 0xbc, 0x33, 0xd5, 0xd5, 0x3e, 0x79, 0x5c, 0x3f, + 0x9d, 0x6e, 0xb1, 0xe9, 0x71, 0xf1, 0x2c, 0xe9, 0xb4, 0x88, 0x2c, 0xd2, + 0x49, 0x97, 0xce, 0x29, 0x94, 0x16, 0xc9, 0xf9, 0x64, 0x0e, 0xd0, 0xd9, + 0x7a, 0x53, 0x10, 0x1a, 0xee, 0x83, 0x73, 0x93, 0x1b, 0xdf, 0x8a, 0x77, + 0xc0, 0x56, 0x63, 0xab, 0x5a, 0x65, 0xc5, 0xc5, 0x3b, 0xf3, 0x30, 0x80, + 0xfc, 0x38, 0x8b, 0xc9, 0xcd, 0xc3, 0x4f, 0x2e, 0x2d, 0x67, 0xcc, 0x17, + 0x18, 0x9b, 0x3e, 0xc6, 0x47, 0x03, 0xfc, 0x35, 0xa8, 0x35, 0x06, 0x5a, + 0x77, 0xe5, 0x97, 0x71, 0xbb, 0x27, 0x93, 0x0d, 0x1f, 0x0e, 0x8c +}; + +static unsigned int cert_data_len = 971; + +/** + * lib_asn1_x509() - unit test for asn1 decoder function + * with x509 certificate parser + * + * @uts: unit test state + * Return: 0 = success, 1 = failure + */ +static int lib_asn1_x509(struct unit_test_state *uts) +{ + struct x509_certificate *cert; + + cert = x509_cert_parse(cert_data, cert_data_len); + + ut_assertf(cert != NULL, "decoding failed\n"); + ut_assertf(!strcmp(cert->subject, "Linaro: Tester"), + "subject doesn't match\n"); + ut_assertf(!strcmp(cert->issuer, "Linaro: Tester"), + "issuer doesn't match\n"); + ut_assertf(cert->pub, "public key doesn't exist\n"); + ut_assertf(cert->pub->keylen == 0x10e, "key length doesn't match\n"); + ut_assertf(!strcmp(cert->pub->pkey_algo, "rsa"), "algo isn't rsa\n"); + ut_assertf(cert->valid_from == 0x5da92ddb, + "valid_from doesn't match\n"); + ut_assertf(cert->valid_to == 0x5f8a615b, "valid_to doesn't match\n"); + + x509_free_certificate(cert); + + return CMD_RET_SUCCESS; +} + +LIB_TEST(lib_asn1_x509, 0); +#endif /* CONFIG_X509_CERTIFICATE_PARSER */ + +#ifdef CONFIG_PKCS7_MESSAGE_PARSER +/* + * sbsign --key priv.pem --cert cert.pem --detach --out Image.pk Image + */ +static const unsigned char image_pk7[] = { + 0x30, 0x82, 0x07, 0x0f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x07, 0x02, 0xa0, 0x82, 0x07, 0x00, 0x30, 0x82, 0x06, 0xfc, 0x02, + 0x01, 0x01, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x30, 0x78, 0x06, 0x0a, 0x2b, + 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x04, 0xa0, 0x6a, 0x30, + 0x68, 0x30, 0x33, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, + 0x02, 0x01, 0x0f, 0x30, 0x25, 0x03, 0x01, 0x00, 0xa0, 0x20, 0xa2, 0x1e, + 0x80, 0x1c, 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x4f, 0x00, 0x62, + 0x00, 0x73, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x74, 0x00, 0x65, + 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x3e, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, + 0x20, 0x9e, 0x90, 0x99, 0x6d, 0xf2, 0xb5, 0x3d, 0x3f, 0xfc, 0x38, 0xb6, + 0xf2, 0x1f, 0xd2, 0x24, 0x88, 0x43, 0x77, 0x7d, 0xc1, 0x2c, 0x9e, 0x8a, + 0xf6, 0xf7, 0xdd, 0x9e, 0x9c, 0x5f, 0x18, 0x36, 0xc5, 0xa0, 0x82, 0x03, + 0xcb, 0x30, 0x82, 0x03, 0xc7, 0x30, 0x82, 0x02, 0xaf, 0xa0, 0x03, 0x02, + 0x01, 0x02, 0x02, 0x09, 0x00, 0xd7, 0x17, 0x0a, 0x76, 0xd5, 0xd3, 0x4d, + 0xeb, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x0b, 0x05, 0x00, 0x30, 0x7a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x0e, 0x30, 0x0c, 0x06, + 0x03, 0x55, 0x04, 0x08, 0x0c, 0x05, 0x54, 0x6f, 0x6b, 0x79, 0x6f, 0x31, + 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x05, 0x54, 0x6f, + 0x6b, 0x79, 0x6f, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, + 0x0c, 0x06, 0x4c, 0x69, 0x6e, 0x61, 0x72, 0x6f, 0x31, 0x0b, 0x30, 0x09, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x02, 0x53, 0x57, 0x31, 0x0f, 0x30, + 0x0d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x06, 0x54, 0x65, 0x73, 0x74, + 0x65, 0x72, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x40, + 0x74, 0x65, 0x73, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x1e, 0x17, 0x0d, + 0x31, 0x39, 0x31, 0x30, 0x31, 0x38, 0x30, 0x33, 0x31, 0x33, 0x33, 0x31, + 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x31, 0x30, 0x31, 0x37, 0x30, 0x33, 0x31, + 0x33, 0x33, 0x31, 0x5a, 0x30, 0x7a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x0e, 0x30, 0x0c, 0x06, + 0x03, 0x55, 0x04, 0x08, 0x0c, 0x05, 0x54, 0x6f, 0x6b, 0x79, 0x6f, 0x31, + 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x05, 0x54, 0x6f, + 0x6b, 0x79, 0x6f, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, + 0x0c, 0x06, 0x4c, 0x69, 0x6e, 0x61, 0x72, 0x6f, 0x31, 0x0b, 0x30, 0x09, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x02, 0x53, 0x57, 0x31, 0x0f, 0x30, + 0x0d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x06, 0x54, 0x65, 0x73, 0x74, + 0x65, 0x72, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x40, + 0x74, 0x65, 0x73, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x82, 0x01, 0x22, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, + 0x02, 0x82, 0x01, 0x01, 0x00, 0x9f, 0x37, 0x4d, 0x95, 0x7e, 0x36, 0xb7, + 0xaf, 0xf4, 0xd6, 0xce, 0x39, 0x04, 0xee, 0xbf, 0x36, 0xb2, 0xcc, 0xa3, + 0x8b, 0x9e, 0xac, 0x62, 0x8a, 0xe9, 0xae, 0x18, 0xcf, 0xe8, 0x95, 0xfd, + 0xcb, 0xad, 0x34, 0x8a, 0x5f, 0x55, 0xe6, 0x0c, 0x5e, 0xf8, 0x76, 0xc1, + 0xa2, 0xc3, 0xd4, 0x73, 0x13, 0x8a, 0x71, 0x1b, 0xfd, 0x58, 0x27, 0xea, + 0x4d, 0x41, 0xff, 0x63, 0xbb, 0xad, 0x97, 0x62, 0xba, 0xe4, 0xe5, 0x97, + 0x45, 0xa3, 0x5b, 0xd5, 0x5b, 0x53, 0x55, 0x10, 0x19, 0xfa, 0xac, 0xbd, + 0xdb, 0x77, 0x62, 0x23, 0x50, 0x3f, 0x35, 0xdb, 0x8a, 0xf6, 0xee, 0x7a, + 0x31, 0xec, 0x92, 0xf5, 0x78, 0x35, 0x92, 0x76, 0x3c, 0x5f, 0xe7, 0xee, + 0xc9, 0xed, 0x01, 0x1c, 0x42, 0x55, 0xd6, 0x7e, 0xa6, 0xca, 0x7c, 0xd1, + 0x15, 0x16, 0x87, 0x7c, 0x99, 0x63, 0xc0, 0xa9, 0x25, 0x49, 0xbc, 0x4e, + 0xdc, 0x2d, 0x4b, 0xcb, 0x52, 0xd7, 0x67, 0xe9, 0x83, 0x6b, 0x5e, 0x5b, + 0x48, 0x80, 0x33, 0xe9, 0xcc, 0xe8, 0xfe, 0x19, 0xc8, 0xc2, 0x61, 0x74, + 0x52, 0x25, 0x92, 0x48, 0xea, 0xad, 0x15, 0x16, 0x64, 0x6e, 0x53, 0x30, + 0x77, 0xa2, 0xef, 0x61, 0x92, 0x1b, 0x5e, 0xbe, 0x07, 0xf2, 0x3c, 0xf8, + 0x35, 0x7d, 0x76, 0x4f, 0x78, 0xa9, 0x2a, 0xf1, 0x32, 0xff, 0xec, 0x89, + 0xa9, 0x22, 0x4c, 0x3d, 0xc8, 0x65, 0xca, 0xf4, 0xa2, 0x6d, 0x3f, 0xa4, + 0x0a, 0xfa, 0x9e, 0xe4, 0xf0, 0xdb, 0x39, 0xb1, 0xf9, 0xf0, 0xfb, 0x04, + 0x81, 0x44, 0xa7, 0xd7, 0x61, 0xdf, 0x2d, 0x13, 0x45, 0x2c, 0xae, 0xf0, + 0x0e, 0xc4, 0x07, 0x5d, 0x7d, 0x2b, 0xb2, 0x20, 0x75, 0x33, 0x6b, 0x5b, + 0xf7, 0xe7, 0x17, 0x51, 0xf1, 0xab, 0xc1, 0x9e, 0xc6, 0xf0, 0x30, 0xc6, + 0x25, 0x26, 0x3e, 0xd7, 0xd7, 0xa3, 0xcc, 0x15, 0x95, 0x02, 0x03, 0x01, + 0x00, 0x01, 0xa3, 0x50, 0x30, 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, + 0x0e, 0x04, 0x16, 0x04, 0x14, 0x45, 0x8a, 0x76, 0xf7, 0x4f, 0xf4, 0x0e, + 0xa0, 0xf2, 0x02, 0xe1, 0xe7, 0xe9, 0xc7, 0x7d, 0x51, 0x55, 0x92, 0x33, + 0xcd, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, + 0x80, 0x14, 0x45, 0x8a, 0x76, 0xf7, 0x4f, 0xf4, 0x0e, 0xa0, 0xf2, 0x02, + 0xe1, 0xe7, 0xe9, 0xc7, 0x7d, 0x51, 0x55, 0x92, 0x33, 0xcd, 0x30, 0x0c, + 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x47, 0x93, 0x82, 0x0e, + 0x8a, 0x70, 0x9d, 0x6c, 0x7a, 0xdb, 0x04, 0xb4, 0xc9, 0xef, 0x98, 0x28, + 0xc6, 0xd9, 0x53, 0x90, 0xc8, 0x25, 0x83, 0x07, 0x23, 0xe7, 0x59, 0x38, + 0xc1, 0xc0, 0x50, 0x28, 0x99, 0x92, 0xfb, 0x21, 0x24, 0x72, 0xe5, 0xa6, + 0x57, 0x30, 0x31, 0xb3, 0xdf, 0xa0, 0x17, 0xa9, 0x73, 0x9c, 0x39, 0x83, + 0xfb, 0xe4, 0xfa, 0x20, 0x1d, 0xfa, 0x33, 0x20, 0x0c, 0x72, 0x2a, 0x50, + 0x40, 0xbd, 0x2d, 0x33, 0xa2, 0xfc, 0x06, 0xf9, 0xfe, 0x86, 0x4f, 0x50, + 0x1d, 0x65, 0x37, 0xe9, 0x30, 0x33, 0x82, 0xa1, 0x75, 0x8f, 0x5d, 0x33, + 0x84, 0x0d, 0xf2, 0x09, 0x04, 0xc0, 0x7a, 0x12, 0x79, 0xdb, 0x4f, 0x77, + 0x04, 0xe4, 0xd8, 0x0b, 0x87, 0x19, 0xba, 0xb7, 0x3c, 0xa6, 0x45, 0xaa, + 0x91, 0x62, 0x7f, 0x01, 0x7d, 0xc6, 0x20, 0x6d, 0x71, 0x15, 0x74, 0x5e, + 0x87, 0xb3, 0x60, 0x17, 0x9c, 0xc0, 0xed, 0x01, 0x4b, 0xb3, 0x23, 0x24, + 0xc1, 0xcb, 0x7a, 0x83, 0x03, 0x26, 0x2d, 0xde, 0x47, 0xc5, 0x11, 0x94, + 0x28, 0x27, 0x15, 0x92, 0x00, 0x8b, 0x2e, 0x51, 0x42, 0xca, 0x4b, 0x4a, + 0x2c, 0x51, 0x37, 0x56, 0xd0, 0xbc, 0x33, 0xd5, 0xd5, 0x3e, 0x79, 0x5c, + 0x3f, 0x9d, 0x6e, 0xb1, 0xe9, 0x71, 0xf1, 0x2c, 0xe9, 0xb4, 0x88, 0x2c, + 0xd2, 0x49, 0x97, 0xce, 0x29, 0x94, 0x16, 0xc9, 0xf9, 0x64, 0x0e, 0xd0, + 0xd9, 0x7a, 0x53, 0x10, 0x1a, 0xee, 0x83, 0x73, 0x93, 0x1b, 0xdf, 0x8a, + 0x77, 0xc0, 0x56, 0x63, 0xab, 0x5a, 0x65, 0xc5, 0xc5, 0x3b, 0xf3, 0x30, + 0x80, 0xfc, 0x38, 0x8b, 0xc9, 0xcd, 0xc3, 0x4f, 0x2e, 0x2d, 0x67, 0xcc, + 0x17, 0x18, 0x9b, 0x3e, 0xc6, 0x47, 0x03, 0xfc, 0x35, 0xa8, 0x35, 0x06, + 0x5a, 0x77, 0xe5, 0x97, 0x71, 0xbb, 0x27, 0x93, 0x0d, 0x1f, 0x0e, 0x8c, + 0x31, 0x82, 0x02, 0x9b, 0x30, 0x82, 0x02, 0x97, 0x02, 0x01, 0x01, 0x30, + 0x81, 0x87, 0x30, 0x7a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, + 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55, + 0x04, 0x08, 0x0c, 0x05, 0x54, 0x6f, 0x6b, 0x79, 0x6f, 0x31, 0x0e, 0x30, + 0x0c, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x05, 0x54, 0x6f, 0x6b, 0x79, + 0x6f, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, + 0x4c, 0x69, 0x6e, 0x61, 0x72, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x0b, 0x0c, 0x02, 0x53, 0x57, 0x31, 0x0f, 0x30, 0x0d, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x0c, 0x06, 0x54, 0x65, 0x73, 0x74, 0x65, 0x72, + 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x09, 0x01, 0x16, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x40, 0x74, 0x65, + 0x73, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x02, 0x09, 0x00, 0xd7, 0x17, 0x0a, + 0x76, 0xd5, 0xd3, 0x4d, 0xeb, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0xa0, 0x81, 0xe5, 0x30, + 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x03, + 0x31, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, + 0x01, 0x04, 0x30, 0x1c, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x09, 0x05, 0x31, 0x0f, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x30, 0x31, + 0x38, 0x30, 0x35, 0x35, 0x35, 0x32, 0x36, 0x5a, 0x30, 0x2f, 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x04, 0x31, 0x22, 0x04, + 0x20, 0x13, 0xe9, 0x2d, 0xcd, 0x35, 0x43, 0xe0, 0x13, 0x34, 0xc5, 0x67, + 0xde, 0xdd, 0x75, 0xdc, 0x62, 0x97, 0x76, 0x7d, 0x5b, 0xa0, 0xb4, 0x4d, + 0x4f, 0xef, 0xb8, 0xa7, 0x95, 0x50, 0xcb, 0x0f, 0xec, 0x30, 0x79, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x0f, 0x31, 0x6c, + 0x30, 0x6a, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x01, 0x2a, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, + 0x03, 0x04, 0x01, 0x16, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x01, 0x02, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x03, 0x07, 0x30, 0x0e, 0x06, 0x08, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x03, 0x02, 0x02, 0x02, 0x00, 0x80, 0x30, 0x0d, 0x06, + 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x03, 0x02, 0x02, 0x01, 0x40, + 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x07, 0x30, 0x0d, 0x06, + 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x03, 0x02, 0x02, 0x01, 0x28, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, + 0x01, 0x05, 0x00, 0x04, 0x82, 0x01, 0x00, 0x38, 0x40, 0x09, 0xc7, 0xc4, + 0xf7, 0x78, 0x48, 0x75, 0x1e, 0xb2, 0x50, 0x95, 0x0a, 0x52, 0xee, 0x57, + 0x60, 0xc5, 0xf4, 0xdb, 0xca, 0x67, 0xb0, 0x19, 0xad, 0x68, 0xb1, 0xe1, + 0x1e, 0xb7, 0xf6, 0x53, 0x3d, 0x13, 0xb1, 0x11, 0x37, 0xa7, 0x6e, 0x9b, + 0x18, 0x1d, 0x0e, 0xbd, 0xc4, 0xb2, 0xd0, 0x36, 0x6c, 0x0c, 0x5a, 0x11, + 0x50, 0xcc, 0xdb, 0x1f, 0x6b, 0xcb, 0x28, 0x80, 0xd5, 0x3c, 0x4f, 0x93, + 0x0b, 0xd1, 0x45, 0x75, 0xa1, 0x89, 0x00, 0x71, 0x7d, 0x55, 0xcc, 0x1c, + 0x0a, 0xc9, 0xc4, 0xe6, 0x87, 0xf2, 0x87, 0x0d, 0x2e, 0x79, 0x71, 0x85, + 0x01, 0xd7, 0x32, 0x87, 0x9a, 0x11, 0xc6, 0x9a, 0xbb, 0x0a, 0x7b, 0xce, + 0xfe, 0xc8, 0xee, 0x10, 0x3c, 0xa6, 0x47, 0xdd, 0xbb, 0xa7, 0xf5, 0x19, + 0x50, 0xd5, 0x2a, 0x11, 0x44, 0x2f, 0x65, 0x09, 0x69, 0x50, 0xfa, 0xbd, + 0x02, 0xe4, 0x90, 0xdc, 0x2a, 0x7c, 0xdb, 0x82, 0x03, 0xa5, 0x28, 0x91, + 0x74, 0x7c, 0xd3, 0x83, 0xc8, 0x11, 0x1a, 0x14, 0x1b, 0xba, 0xb1, 0x82, + 0xbd, 0x53, 0xad, 0x9c, 0x34, 0x05, 0xfa, 0x2d, 0x14, 0x58, 0x5e, 0x50, + 0x64, 0x60, 0x5c, 0x21, 0x7c, 0xe6, 0xf0, 0x2b, 0xa2, 0xec, 0xe5, 0xeb, + 0xda, 0x88, 0xe2, 0x19, 0x36, 0x96, 0x65, 0xf7, 0x4c, 0x62, 0x9b, 0x75, + 0x24, 0xb4, 0xb1, 0x34, 0x83, 0xba, 0x05, 0x01, 0xd8, 0xe1, 0x33, 0xd3, + 0x1a, 0xd6, 0x09, 0x84, 0x31, 0xd0, 0x67, 0xf3, 0x3b, 0x0e, 0x19, 0x98, + 0x7e, 0x07, 0xdc, 0xe1, 0xd8, 0x45, 0x84, 0xa2, 0xdd, 0x8a, 0x04, 0x6a, + 0x43, 0xcf, 0xff, 0x7c, 0x9e, 0x83, 0xa8, 0x5d, 0xbc, 0x1f, 0x45, 0x86, + 0x5b, 0x2d, 0xcd, 0x9d, 0xa0, 0xba, 0x4d, 0xd2, 0xc6, 0xb9, 0xc5, 0x34, + 0x39, 0x29, 0x20, 0xee, 0x27, 0x60, 0x46, 0x9c, 0x62, 0xbe, 0xf2 +}; + +static unsigned int image_pk7_len = 1811; + +/** + * lib_asn1_pkcs7() - unit test for asn1 decoder function + * with pkcs7 message parser + * + * @uts: unit test state + * Return: 0 = success, 1 = failure + */ +static int lib_asn1_pkcs7(struct unit_test_state *uts) +{ + struct pkcs7_message *pkcs7; + + pkcs7 = pkcs7_parse_message(image_pk7, image_pk7_len); + + ut_assertf(pkcs7 != NULL, "decoding failed\n"); + ut_assertf(pkcs7->data_len == 104, "signature size doesn't match\n"); + ut_assertf(pkcs7->signed_infos != NULL, "sign-info doesn't exist\n"); + ut_assertf(pkcs7->signed_infos->msgdigest_len == 32, + "digest size doesn't match\n"); + ut_assertf(pkcs7->signed_infos->aa_set == 0xf, + "authenticated attributes doesn't match\n"); + + pkcs7_free_message(pkcs7); + + return CMD_RET_SUCCESS; +} + +LIB_TEST(lib_asn1_pkcs7, 0); +#endif /* CONFIG_PKCS7_MESSAGE_PARSER */ + +#ifdef CONFIG_RSA_PUBLIC_KEY_PARSER +#include <crypto/internal/rsa.h> + +/* + * openssl genrsa 2048 -out private.pem + * openssl rsa -in private.pem -pubout -outform der -out public.der + * dd if=public.der of=public.raw bs=24 skip=1 + */ +static const unsigned char public_key[] = { + 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xca, 0x25, 0x23, + 0xe0, 0x0a, 0x4d, 0x8f, 0x56, 0xfc, 0xc9, 0x06, 0x4c, 0xcc, 0x94, 0x43, + 0xe0, 0x56, 0x44, 0x6e, 0x37, 0x54, 0x87, 0x12, 0x84, 0xf9, 0x07, 0x4f, + 0xe4, 0x23, 0x40, 0xc3, 0x43, 0x84, 0x37, 0x86, 0xd3, 0x9d, 0x95, 0x1c, + 0xe4, 0x8a, 0x66, 0x02, 0x09, 0xe2, 0x3d, 0xce, 0x2c, 0xc6, 0x02, 0x6a, + 0xd4, 0x65, 0x61, 0xff, 0x85, 0x6f, 0x88, 0x63, 0xba, 0x31, 0x62, 0x1e, + 0xb7, 0x95, 0xe9, 0x08, 0x3c, 0xe9, 0x35, 0xde, 0xfd, 0x65, 0x92, 0xb8, + 0x9e, 0x71, 0xa4, 0xcd, 0x47, 0xfd, 0x04, 0x26, 0xb9, 0x78, 0xbf, 0x05, + 0x0d, 0xfc, 0x00, 0x84, 0x08, 0xfc, 0xc4, 0x4b, 0xea, 0xf5, 0x97, 0x68, + 0x0d, 0x97, 0xd7, 0xff, 0x4f, 0x92, 0x82, 0xd7, 0xbb, 0xef, 0xb7, 0x67, + 0x8e, 0x72, 0x54, 0xe8, 0xc5, 0x9e, 0xfd, 0xd8, 0x38, 0xe9, 0xbe, 0x19, + 0x37, 0x5b, 0x36, 0x8b, 0xbf, 0x49, 0xa1, 0x59, 0x3a, 0x9d, 0xad, 0x92, + 0x08, 0x0b, 0xe3, 0xa4, 0xa4, 0x7d, 0xd3, 0x70, 0xc0, 0xb8, 0xfb, 0xc7, + 0xda, 0xd3, 0x19, 0x86, 0x37, 0x9a, 0xcd, 0xab, 0x30, 0x96, 0xab, 0xa4, + 0xa2, 0x31, 0xa0, 0x38, 0xfb, 0xbf, 0x85, 0xd3, 0x24, 0x39, 0xed, 0xbf, + 0xe1, 0x31, 0xed, 0x6c, 0x39, 0xc1, 0xe5, 0x05, 0x2e, 0x12, 0x30, 0x36, + 0x73, 0x5d, 0x62, 0xf3, 0x82, 0xaf, 0x38, 0xc8, 0xca, 0xfa, 0xa1, 0x99, + 0x57, 0x3c, 0xe1, 0xc1, 0x7b, 0x05, 0x0b, 0xcc, 0x2e, 0xa9, 0x10, 0xc8, + 0x68, 0xbd, 0x27, 0xb6, 0x19, 0x9c, 0xd2, 0xad, 0xb3, 0x1f, 0xca, 0x35, + 0x6e, 0x84, 0x23, 0xa1, 0xe9, 0xa4, 0x4c, 0xab, 0x19, 0x09, 0x79, 0x6e, + 0x3c, 0x7b, 0x74, 0xfc, 0x33, 0x05, 0xcf, 0xa4, 0x2e, 0xeb, 0x55, 0x60, + 0x05, 0xc7, 0xcf, 0x3f, 0x92, 0xac, 0x2d, 0x69, 0x0b, 0x19, 0x16, 0x79, + 0x75, 0x02, 0x03, 0x01, 0x00, 0x01 +}; + +static unsigned int public_key_len = 270; + +/** + * lib_asn1_pkey() - unit test for asn1 decoder function + * with RSA public key parser + * + * @uts: unit test state + * Return: 0 = success, 1 = failure + */ +static int lib_asn1_pkey(struct unit_test_state *uts) +{ + struct rsa_key pkey; + int ret; + + ret = rsa_parse_pub_key(&pkey, public_key, public_key_len); + + ut_assertf(ret == 0, "decoding failed (%d)\n", ret); + ut_assertf(pkey.n_sz == 257, "public key modulus size doesn't match\n"); + ut_assertf(pkey.e_sz == 3, "public key exponent size doesn't match\n"); + ut_assertf(pkey.e[0] == 0x01 && pkey.e[1] == 0x00 && pkey.e[2] == 0x01, + "public key exponent doesn't match\n"); + + return CMD_RET_SUCCESS; +} + +LIB_TEST(lib_asn1_pkey, 0); +#endif /* CONFIG_RSA_PUBLIC_KEY_PARSER */ diff --git a/tools/Makefile b/tools/Makefile index 24581adccd..345bc84e48 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -207,6 +207,9 @@ endif hostprogs-$(CONFIG_MIPS) += mips-relocs +hostprogs-$(CONFIG_ASN1_COMPILER) += asn1_compiler +HOSTCFLAGS_asn1_compiler.o = -idirafter $(srctree)/include + # We build some files with extra pedantic flags to try to minimize things # that won't build on some weird host compiler -- though there are lots of # exceptions for files that aren't complaint. diff --git a/tools/asn1_compiler.c b/tools/asn1_compiler.c new file mode 100644 index 0000000000..adabd41452 --- /dev/null +++ b/tools/asn1_compiler.c @@ -0,0 +1,1611 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Simplified ASN.1 notation parser + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <linux/asn1_ber_bytecode.h> + +enum token_type { + DIRECTIVE_ABSENT, + DIRECTIVE_ALL, + DIRECTIVE_ANY, + DIRECTIVE_APPLICATION, + DIRECTIVE_AUTOMATIC, + DIRECTIVE_BEGIN, + DIRECTIVE_BIT, + DIRECTIVE_BMPString, + DIRECTIVE_BOOLEAN, + DIRECTIVE_BY, + DIRECTIVE_CHARACTER, + DIRECTIVE_CHOICE, + DIRECTIVE_CLASS, + DIRECTIVE_COMPONENT, + DIRECTIVE_COMPONENTS, + DIRECTIVE_CONSTRAINED, + DIRECTIVE_CONTAINING, + DIRECTIVE_DEFAULT, + DIRECTIVE_DEFINED, + DIRECTIVE_DEFINITIONS, + DIRECTIVE_EMBEDDED, + DIRECTIVE_ENCODED, + DIRECTIVE_ENCODING_CONTROL, + DIRECTIVE_END, + DIRECTIVE_ENUMERATED, + DIRECTIVE_EXCEPT, + DIRECTIVE_EXPLICIT, + DIRECTIVE_EXPORTS, + DIRECTIVE_EXTENSIBILITY, + DIRECTIVE_EXTERNAL, + DIRECTIVE_FALSE, + DIRECTIVE_FROM, + DIRECTIVE_GeneralString, + DIRECTIVE_GeneralizedTime, + DIRECTIVE_GraphicString, + DIRECTIVE_IA5String, + DIRECTIVE_IDENTIFIER, + DIRECTIVE_IMPLICIT, + DIRECTIVE_IMPLIED, + DIRECTIVE_IMPORTS, + DIRECTIVE_INCLUDES, + DIRECTIVE_INSTANCE, + DIRECTIVE_INSTRUCTIONS, + DIRECTIVE_INTEGER, + DIRECTIVE_INTERSECTION, + DIRECTIVE_ISO646String, + DIRECTIVE_MAX, + DIRECTIVE_MIN, + DIRECTIVE_MINUS_INFINITY, + DIRECTIVE_NULL, + DIRECTIVE_NumericString, + DIRECTIVE_OBJECT, + DIRECTIVE_OCTET, + DIRECTIVE_OF, + DIRECTIVE_OPTIONAL, + DIRECTIVE_ObjectDescriptor, + DIRECTIVE_PATTERN, + DIRECTIVE_PDV, + DIRECTIVE_PLUS_INFINITY, + DIRECTIVE_PRESENT, + DIRECTIVE_PRIVATE, + DIRECTIVE_PrintableString, + DIRECTIVE_REAL, + DIRECTIVE_RELATIVE_OID, + DIRECTIVE_SEQUENCE, + DIRECTIVE_SET, + DIRECTIVE_SIZE, + DIRECTIVE_STRING, + DIRECTIVE_SYNTAX, + DIRECTIVE_T61String, + DIRECTIVE_TAGS, + DIRECTIVE_TRUE, + DIRECTIVE_TeletexString, + DIRECTIVE_UNION, + DIRECTIVE_UNIQUE, + DIRECTIVE_UNIVERSAL, + DIRECTIVE_UTCTime, + DIRECTIVE_UTF8String, + DIRECTIVE_UniversalString, + DIRECTIVE_VideotexString, + DIRECTIVE_VisibleString, + DIRECTIVE_WITH, + NR__DIRECTIVES, + TOKEN_ASSIGNMENT = NR__DIRECTIVES, + TOKEN_OPEN_CURLY, + TOKEN_CLOSE_CURLY, + TOKEN_OPEN_SQUARE, + TOKEN_CLOSE_SQUARE, + TOKEN_OPEN_ACTION, + TOKEN_CLOSE_ACTION, + TOKEN_COMMA, + TOKEN_NUMBER, + TOKEN_TYPE_NAME, + TOKEN_ELEMENT_NAME, + NR__TOKENS +}; + +static const unsigned char token_to_tag[NR__TOKENS] = { + /* EOC goes first */ + [DIRECTIVE_BOOLEAN] = ASN1_BOOL, + [DIRECTIVE_INTEGER] = ASN1_INT, + [DIRECTIVE_BIT] = ASN1_BTS, + [DIRECTIVE_OCTET] = ASN1_OTS, + [DIRECTIVE_NULL] = ASN1_NULL, + [DIRECTIVE_OBJECT] = ASN1_OID, + [DIRECTIVE_ObjectDescriptor] = ASN1_ODE, + [DIRECTIVE_EXTERNAL] = ASN1_EXT, + [DIRECTIVE_REAL] = ASN1_REAL, + [DIRECTIVE_ENUMERATED] = ASN1_ENUM, + [DIRECTIVE_EMBEDDED] = 0, + [DIRECTIVE_UTF8String] = ASN1_UTF8STR, + [DIRECTIVE_RELATIVE_OID] = ASN1_RELOID, + /* 14 */ + /* 15 */ + [DIRECTIVE_SEQUENCE] = ASN1_SEQ, + [DIRECTIVE_SET] = ASN1_SET, + [DIRECTIVE_NumericString] = ASN1_NUMSTR, + [DIRECTIVE_PrintableString] = ASN1_PRNSTR, + [DIRECTIVE_T61String] = ASN1_TEXSTR, + [DIRECTIVE_TeletexString] = ASN1_TEXSTR, + [DIRECTIVE_VideotexString] = ASN1_VIDSTR, + [DIRECTIVE_IA5String] = ASN1_IA5STR, + [DIRECTIVE_UTCTime] = ASN1_UNITIM, + [DIRECTIVE_GeneralizedTime] = ASN1_GENTIM, + [DIRECTIVE_GraphicString] = ASN1_GRASTR, + [DIRECTIVE_VisibleString] = ASN1_VISSTR, + [DIRECTIVE_GeneralString] = ASN1_GENSTR, + [DIRECTIVE_UniversalString] = ASN1_UNITIM, + [DIRECTIVE_CHARACTER] = ASN1_CHRSTR, + [DIRECTIVE_BMPString] = ASN1_BMPSTR, +}; + +static const char asn1_classes[4][5] = { + [ASN1_UNIV] = "UNIV", + [ASN1_APPL] = "APPL", + [ASN1_CONT] = "CONT", + [ASN1_PRIV] = "PRIV" +}; + +static const char asn1_methods[2][5] = { + [ASN1_UNIV] = "PRIM", + [ASN1_APPL] = "CONS" +}; + +static const char *const asn1_universal_tags[32] = { + "EOC", + "BOOL", + "INT", + "BTS", + "OTS", + "NULL", + "OID", + "ODE", + "EXT", + "REAL", + "ENUM", + "EPDV", + "UTF8STR", + "RELOID", + NULL, /* 14 */ + NULL, /* 15 */ + "SEQ", + "SET", + "NUMSTR", + "PRNSTR", + "TEXSTR", + "VIDSTR", + "IA5STR", + "UNITIM", + "GENTIM", + "GRASTR", + "VISSTR", + "GENSTR", + "UNISTR", + "CHRSTR", + "BMPSTR", + NULL /* 31 */ +}; + +static const char *filename; +static const char *grammar_name; +static const char *outputname; +static const char *headername; + +static const char *const directives[NR__DIRECTIVES] = { +#define _(X) [DIRECTIVE_##X] = #X + _(ABSENT), + _(ALL), + _(ANY), + _(APPLICATION), + _(AUTOMATIC), + _(BEGIN), + _(BIT), + _(BMPString), + _(BOOLEAN), + _(BY), + _(CHARACTER), + _(CHOICE), + _(CLASS), + _(COMPONENT), + _(COMPONENTS), + _(CONSTRAINED), + _(CONTAINING), + _(DEFAULT), + _(DEFINED), + _(DEFINITIONS), + _(EMBEDDED), + _(ENCODED), + [DIRECTIVE_ENCODING_CONTROL] = "ENCODING-CONTROL", + _(END), + _(ENUMERATED), + _(EXCEPT), + _(EXPLICIT), + _(EXPORTS), + _(EXTENSIBILITY), + _(EXTERNAL), + _(FALSE), + _(FROM), + _(GeneralString), + _(GeneralizedTime), + _(GraphicString), + _(IA5String), + _(IDENTIFIER), + _(IMPLICIT), + _(IMPLIED), + _(IMPORTS), + _(INCLUDES), + _(INSTANCE), + _(INSTRUCTIONS), + _(INTEGER), + _(INTERSECTION), + _(ISO646String), + _(MAX), + _(MIN), + [DIRECTIVE_MINUS_INFINITY] = "MINUS-INFINITY", + [DIRECTIVE_NULL] = "NULL", + _(NumericString), + _(OBJECT), + _(OCTET), + _(OF), + _(OPTIONAL), + _(ObjectDescriptor), + _(PATTERN), + _(PDV), + [DIRECTIVE_PLUS_INFINITY] = "PLUS-INFINITY", + _(PRESENT), + _(PRIVATE), + _(PrintableString), + _(REAL), + [DIRECTIVE_RELATIVE_OID] = "RELATIVE-OID", + _(SEQUENCE), + _(SET), + _(SIZE), + _(STRING), + _(SYNTAX), + _(T61String), + _(TAGS), + _(TRUE), + _(TeletexString), + _(UNION), + _(UNIQUE), + _(UNIVERSAL), + _(UTCTime), + _(UTF8String), + _(UniversalString), + _(VideotexString), + _(VisibleString), + _(WITH) +}; + +struct action { + struct action *next; + char *name; + unsigned char index; +}; + +static struct action *action_list; +static unsigned nr_actions; + +struct token { + unsigned short line; + enum token_type token_type : 8; + unsigned char size; + struct action *action; + char *content; + struct type *type; +}; + +static struct token *token_list; +static unsigned nr_tokens; +static bool verbose_opt; +static bool debug_opt; + +#define verbose(fmt, ...) do { if (verbose_opt) printf(fmt, ## __VA_ARGS__); } while (0) +#define debug(fmt, ...) do { if (debug_opt) printf(fmt, ## __VA_ARGS__); } while (0) + +static int directive_compare(const void *_key, const void *_pdir) +{ + const struct token *token = _key; + const char *const *pdir = _pdir, *dir = *pdir; + size_t dlen, clen; + int val; + + dlen = strlen(dir); + clen = (dlen < token->size) ? dlen : token->size; + + //debug("cmp(%s,%s) = ", token->content, dir); + + val = memcmp(token->content, dir, clen); + if (val != 0) { + //debug("%d [cmp]\n", val); + return val; + } + + if (dlen == token->size) { + //debug("0\n"); + return 0; + } + //debug("%d\n", (int)dlen - (int)token->size); + return dlen - token->size; /* shorter -> negative */ +} + +/* + * Tokenise an ASN.1 grammar + */ +static void tokenise(char *buffer, char *end) +{ + struct token *tokens; + char *line, *nl, *start, *p, *q; + unsigned tix, lineno; + + /* Assume we're going to have half as many tokens as we have + * characters + */ + token_list = tokens = calloc((end - buffer) / 2, sizeof(struct token)); + if (!tokens) { + perror(NULL); + exit(1); + } + tix = 0; + + lineno = 0; + while (buffer < end) { + /* First of all, break out a line */ + lineno++; + line = buffer; + nl = memchr(line, '\n', end - buffer); + if (!nl) { + buffer = nl = end; + } else { + buffer = nl + 1; + *nl = '\0'; + } + + /* Remove "--" comments */ + p = line; + next_comment: + while ((p = memchr(p, '-', nl - p))) { + if (p[1] == '-') { + /* Found a comment; see if there's a terminator */ + q = p + 2; + while ((q = memchr(q, '-', nl - q))) { + if (q[1] == '-') { + /* There is - excise the comment */ + q += 2; + memmove(p, q, nl - q); + goto next_comment; + } + q++; + } + *p = '\0'; + nl = p; + break; + } else { + p++; + } + } + + p = line; + while (p < nl) { + /* Skip white space */ + while (p < nl && isspace(*p)) + *(p++) = 0; + if (p >= nl) + break; + + tokens[tix].line = lineno; + start = p; + + /* Handle string tokens */ + if (isalpha(*p)) { + const char **dir; + + /* Can be a directive, type name or element + * name. Find the end of the name. + */ + q = p + 1; + while (q < nl && (isalnum(*q) || *q == '-' || *q == '_')) + q++; + tokens[tix].size = q - p; + p = q; + + tokens[tix].content = malloc(tokens[tix].size + 1); + if (!tokens[tix].content) { + perror(NULL); + exit(1); + } + memcpy(tokens[tix].content, start, tokens[tix].size); + tokens[tix].content[tokens[tix].size] = 0; + + /* If it begins with a lowercase letter then + * it's an element name + */ + if (islower(tokens[tix].content[0])) { + tokens[tix++].token_type = TOKEN_ELEMENT_NAME; + continue; + } + + /* Otherwise we need to search the directive + * table + */ + dir = bsearch(&tokens[tix], directives, + sizeof(directives) / sizeof(directives[1]), + sizeof(directives[1]), + directive_compare); + if (dir) { + tokens[tix++].token_type = dir - directives; + continue; + } + + tokens[tix++].token_type = TOKEN_TYPE_NAME; + continue; + } + + /* Handle numbers */ + if (isdigit(*p)) { + /* Find the end of the number */ + q = p + 1; + while (q < nl && (isdigit(*q))) + q++; + tokens[tix].size = q - p; + p = q; + tokens[tix].content = malloc(tokens[tix].size + 1); + if (!tokens[tix].content) { + perror(NULL); + exit(1); + } + memcpy(tokens[tix].content, start, tokens[tix].size); + tokens[tix].content[tokens[tix].size] = 0; + tokens[tix++].token_type = TOKEN_NUMBER; + continue; + } + + if (nl - p >= 3) { + if (memcmp(p, "::=", 3) == 0) { + p += 3; + tokens[tix].size = 3; + tokens[tix].content = "::="; + tokens[tix++].token_type = TOKEN_ASSIGNMENT; + continue; + } + } + + if (nl - p >= 2) { + if (memcmp(p, "({", 2) == 0) { + p += 2; + tokens[tix].size = 2; + tokens[tix].content = "({"; + tokens[tix++].token_type = TOKEN_OPEN_ACTION; + continue; + } + if (memcmp(p, "})", 2) == 0) { + p += 2; + tokens[tix].size = 2; + tokens[tix].content = "})"; + tokens[tix++].token_type = TOKEN_CLOSE_ACTION; + continue; + } + } + + if (nl - p >= 1) { + tokens[tix].size = 1; + switch (*p) { + case '{': + p += 1; + tokens[tix].content = "{"; + tokens[tix++].token_type = TOKEN_OPEN_CURLY; + continue; + case '}': + p += 1; + tokens[tix].content = "}"; + tokens[tix++].token_type = TOKEN_CLOSE_CURLY; + continue; + case '[': + p += 1; + tokens[tix].content = "["; + tokens[tix++].token_type = TOKEN_OPEN_SQUARE; + continue; + case ']': + p += 1; + tokens[tix].content = "]"; + tokens[tix++].token_type = TOKEN_CLOSE_SQUARE; + continue; + case ',': + p += 1; + tokens[tix].content = ","; + tokens[tix++].token_type = TOKEN_COMMA; + continue; + default: + break; + } + } + + fprintf(stderr, "%s:%u: Unknown character in grammar: '%c'\n", + filename, lineno, *p); + exit(1); + } + } + + nr_tokens = tix; + verbose("Extracted %u tokens\n", nr_tokens); + +#if 0 + { + int n; + for (n = 0; n < nr_tokens; n++) + debug("Token %3u: '%s'\n", n, token_list[n].content); + } +#endif +} + +static void build_type_list(void); +static void parse(void); +static void dump_elements(void); +static void render(FILE *out, FILE *hdr); + +/* + * + */ +int main(int argc, char **argv) +{ + struct stat st; + ssize_t readlen; + FILE *out, *hdr; + char *buffer, *p; + char *kbuild_verbose; + int fd; + + kbuild_verbose = getenv("KBUILD_VERBOSE"); + if (kbuild_verbose) + verbose_opt = atoi(kbuild_verbose); + + while (argc > 4) { + if (strcmp(argv[1], "-v") == 0) + verbose_opt = true; + else if (strcmp(argv[1], "-d") == 0) + debug_opt = true; + else + break; + memmove(&argv[1], &argv[2], (argc - 2) * sizeof(char *)); + argc--; + } + + if (argc != 4) { + fprintf(stderr, "Format: %s [-v] [-d] <grammar-file> <c-file> <hdr-file>\n", + argv[0]); + exit(2); + } + + filename = argv[1]; + outputname = argv[2]; + headername = argv[3]; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + perror(filename); + exit(1); + } + + if (fstat(fd, &st) < 0) { + perror(filename); + exit(1); + } + + if (!(buffer = malloc(st.st_size + 1))) { + perror(NULL); + exit(1); + } + + if ((readlen = read(fd, buffer, st.st_size)) < 0) { + perror(filename); + exit(1); + } + + if (close(fd) < 0) { + perror(filename); + exit(1); + } + + if (readlen != st.st_size) { + fprintf(stderr, "%s: Short read\n", filename); + exit(1); + } + + p = strrchr(argv[1], '/'); + p = p ? p + 1 : argv[1]; + grammar_name = strdup(p); + if (!p) { + perror(NULL); + exit(1); + } + p = strchr(grammar_name, '.'); + if (p) + *p = '\0'; + + buffer[readlen] = 0; + tokenise(buffer, buffer + readlen); + build_type_list(); + parse(); + dump_elements(); + + out = fopen(outputname, "w"); + if (!out) { + perror(outputname); + exit(1); + } + + hdr = fopen(headername, "w"); + if (!hdr) { + perror(headername); + exit(1); + } + + render(out, hdr); + + if (fclose(out) < 0) { + perror(outputname); + exit(1); + } + + if (fclose(hdr) < 0) { + perror(headername); + exit(1); + } + + return 0; +} + +enum compound { + NOT_COMPOUND, + SET, + SET_OF, + SEQUENCE, + SEQUENCE_OF, + CHOICE, + ANY, + TYPE_REF, + TAG_OVERRIDE +}; + +struct element { + struct type *type_def; + struct token *name; + struct token *type; + struct action *action; + struct element *children; + struct element *next; + struct element *render_next; + struct element *list_next; + uint8_t n_elements; + enum compound compound : 8; + enum asn1_class class : 8; + enum asn1_method method : 8; + uint8_t tag; + unsigned entry_index; + unsigned flags; +#define ELEMENT_IMPLICIT 0x0001 +#define ELEMENT_EXPLICIT 0x0002 +#define ELEMENT_TAG_SPECIFIED 0x0004 +#define ELEMENT_RENDERED 0x0008 +#define ELEMENT_SKIPPABLE 0x0010 +#define ELEMENT_CONDITIONAL 0x0020 +}; + +struct type { + struct token *name; + struct token *def; + struct element *element; + unsigned ref_count; + unsigned flags; +#define TYPE_STOP_MARKER 0x0001 +#define TYPE_BEGIN 0x0002 +}; + +static struct type *type_list; +static struct type **type_index; +static unsigned nr_types; + +static int type_index_compare(const void *_a, const void *_b) +{ + const struct type *const *a = _a, *const *b = _b; + + if ((*a)->name->size != (*b)->name->size) + return (*a)->name->size - (*b)->name->size; + else + return memcmp((*a)->name->content, (*b)->name->content, + (*a)->name->size); +} + +static int type_finder(const void *_key, const void *_ti) +{ + const struct token *token = _key; + const struct type *const *ti = _ti; + const struct type *type = *ti; + + if (token->size != type->name->size) + return token->size - type->name->size; + else + return memcmp(token->content, type->name->content, + token->size); +} + +/* + * Build up a list of types and a sorted index to that list. + */ +static void build_type_list(void) +{ + struct type *types; + unsigned nr, t, n; + + nr = 0; + for (n = 0; n < nr_tokens - 1; n++) + if (token_list[n + 0].token_type == TOKEN_TYPE_NAME && + token_list[n + 1].token_type == TOKEN_ASSIGNMENT) + nr++; + + if (nr == 0) { + fprintf(stderr, "%s: No defined types\n", filename); + exit(1); + } + + nr_types = nr; + types = type_list = calloc(nr + 1, sizeof(type_list[0])); + if (!type_list) { + perror(NULL); + exit(1); + } + type_index = calloc(nr, sizeof(type_index[0])); + if (!type_index) { + perror(NULL); + exit(1); + } + + t = 0; + types[t].flags |= TYPE_BEGIN; + for (n = 0; n < nr_tokens - 1; n++) { + if (token_list[n + 0].token_type == TOKEN_TYPE_NAME && + token_list[n + 1].token_type == TOKEN_ASSIGNMENT) { + types[t].name = &token_list[n]; + type_index[t] = &types[t]; + t++; + } + } + types[t].name = &token_list[n + 1]; + types[t].flags |= TYPE_STOP_MARKER; + + qsort(type_index, nr, sizeof(type_index[0]), type_index_compare); + + verbose("Extracted %u types\n", nr_types); +#if 0 + for (n = 0; n < nr_types; n++) { + struct type *type = type_index[n]; + debug("- %*.*s\n", type->name->content); + } +#endif +} + +static struct element *parse_type(struct token **_cursor, struct token *stop, + struct token *name); + +/* + * Parse the token stream + */ +static void parse(void) +{ + struct token *cursor; + struct type *type; + + /* Parse one type definition statement at a time */ + type = type_list; + do { + cursor = type->name; + + if (cursor[0].token_type != TOKEN_TYPE_NAME || + cursor[1].token_type != TOKEN_ASSIGNMENT) + abort(); + cursor += 2; + + type->element = parse_type(&cursor, type[1].name, NULL); + type->element->type_def = type; + + if (cursor != type[1].name) { + fprintf(stderr, "%s:%d: Parse error at token '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + + } while (type++, !(type->flags & TYPE_STOP_MARKER)); + + verbose("Extracted %u actions\n", nr_actions); +} + +static struct element *element_list; + +static struct element *alloc_elem(struct token *type) +{ + struct element *e = calloc(1, sizeof(*e)); + if (!e) { + perror(NULL); + exit(1); + } + e->list_next = element_list; + element_list = e; + return e; +} + +static struct element *parse_compound(struct token **_cursor, struct token *end, + int alternates); + +/* + * Parse one type definition statement + */ +static struct element *parse_type(struct token **_cursor, struct token *end, + struct token *name) +{ + struct element *top, *element; + struct action *action, **ppaction; + struct token *cursor = *_cursor; + struct type **ref; + char *p; + int labelled = 0, implicit = 0; + + top = element = alloc_elem(cursor); + element->class = ASN1_UNIV; + element->method = ASN1_PRIM; + element->tag = token_to_tag[cursor->token_type]; + element->name = name; + + /* Extract the tag value if one given */ + if (cursor->token_type == TOKEN_OPEN_SQUARE) { + cursor++; + if (cursor >= end) + goto overrun_error; + switch (cursor->token_type) { + case DIRECTIVE_UNIVERSAL: + element->class = ASN1_UNIV; + cursor++; + break; + case DIRECTIVE_APPLICATION: + element->class = ASN1_APPL; + cursor++; + break; + case TOKEN_NUMBER: + element->class = ASN1_CONT; + break; + case DIRECTIVE_PRIVATE: + element->class = ASN1_PRIV; + cursor++; + break; + default: + fprintf(stderr, "%s:%d: Unrecognised tag class token '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != TOKEN_NUMBER) { + fprintf(stderr, "%s:%d: Missing tag number '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + + element->tag &= ~0x1f; + element->tag |= strtoul(cursor->content, &p, 10); + element->flags |= ELEMENT_TAG_SPECIFIED; + if (p - cursor->content != cursor->size) + abort(); + cursor++; + + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != TOKEN_CLOSE_SQUARE) { + fprintf(stderr, "%s:%d: Missing closing square bracket '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + cursor++; + if (cursor >= end) + goto overrun_error; + labelled = 1; + } + + /* Handle implicit and explicit markers */ + if (cursor->token_type == DIRECTIVE_IMPLICIT) { + element->flags |= ELEMENT_IMPLICIT; + implicit = 1; + cursor++; + if (cursor >= end) + goto overrun_error; + } else if (cursor->token_type == DIRECTIVE_EXPLICIT) { + element->flags |= ELEMENT_EXPLICIT; + cursor++; + if (cursor >= end) + goto overrun_error; + } + + if (labelled) { + if (!implicit) + element->method |= ASN1_CONS; + element->compound = implicit ? TAG_OVERRIDE : SEQUENCE; + element->children = alloc_elem(cursor); + element = element->children; + element->class = ASN1_UNIV; + element->method = ASN1_PRIM; + element->tag = token_to_tag[cursor->token_type]; + element->name = name; + } + + /* Extract the type we're expecting here */ + element->type = cursor; + switch (cursor->token_type) { + case DIRECTIVE_ANY: + element->compound = ANY; + cursor++; + break; + + case DIRECTIVE_NULL: + case DIRECTIVE_BOOLEAN: + case DIRECTIVE_ENUMERATED: + case DIRECTIVE_INTEGER: + element->compound = NOT_COMPOUND; + cursor++; + break; + + case DIRECTIVE_EXTERNAL: + element->method = ASN1_CONS; + + case DIRECTIVE_BMPString: + case DIRECTIVE_GeneralString: + case DIRECTIVE_GraphicString: + case DIRECTIVE_IA5String: + case DIRECTIVE_ISO646String: + case DIRECTIVE_NumericString: + case DIRECTIVE_PrintableString: + case DIRECTIVE_T61String: + case DIRECTIVE_TeletexString: + case DIRECTIVE_UniversalString: + case DIRECTIVE_UTF8String: + case DIRECTIVE_VideotexString: + case DIRECTIVE_VisibleString: + case DIRECTIVE_ObjectDescriptor: + case DIRECTIVE_GeneralizedTime: + case DIRECTIVE_UTCTime: + element->compound = NOT_COMPOUND; + cursor++; + break; + + case DIRECTIVE_BIT: + case DIRECTIVE_OCTET: + element->compound = NOT_COMPOUND; + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != DIRECTIVE_STRING) + goto parse_error; + cursor++; + break; + + case DIRECTIVE_OBJECT: + element->compound = NOT_COMPOUND; + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != DIRECTIVE_IDENTIFIER) + goto parse_error; + cursor++; + break; + + case TOKEN_TYPE_NAME: + element->compound = TYPE_REF; + ref = bsearch(cursor, type_index, nr_types, sizeof(type_index[0]), + type_finder); + if (!ref) { + fprintf(stderr, "%s:%d: Type '%s' undefined\n", + filename, cursor->line, cursor->content); + exit(1); + } + cursor->type = *ref; + (*ref)->ref_count++; + cursor++; + break; + + case DIRECTIVE_CHOICE: + element->compound = CHOICE; + cursor++; + element->children = parse_compound(&cursor, end, 1); + break; + + case DIRECTIVE_SEQUENCE: + element->compound = SEQUENCE; + element->method = ASN1_CONS; + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type == DIRECTIVE_OF) { + element->compound = SEQUENCE_OF; + cursor++; + if (cursor >= end) + goto overrun_error; + element->children = parse_type(&cursor, end, NULL); + } else { + element->children = parse_compound(&cursor, end, 0); + } + break; + + case DIRECTIVE_SET: + element->compound = SET; + element->method = ASN1_CONS; + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type == DIRECTIVE_OF) { + element->compound = SET_OF; + cursor++; + if (cursor >= end) + goto parse_error; + element->children = parse_type(&cursor, end, NULL); + } else { + element->children = parse_compound(&cursor, end, 1); + } + break; + + default: + fprintf(stderr, "%s:%d: Token '%s' does not introduce a type\n", + filename, cursor->line, cursor->content); + exit(1); + } + + /* Handle elements that are optional */ + if (cursor < end && (cursor->token_type == DIRECTIVE_OPTIONAL || + cursor->token_type == DIRECTIVE_DEFAULT) + ) { + cursor++; + top->flags |= ELEMENT_SKIPPABLE; + } + + if (cursor < end && cursor->token_type == TOKEN_OPEN_ACTION) { + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != TOKEN_ELEMENT_NAME) { + fprintf(stderr, "%s:%d: Token '%s' is not an action function name\n", + filename, cursor->line, cursor->content); + exit(1); + } + + action = malloc(sizeof(struct action)); + if (!action) { + perror(NULL); + exit(1); + } + action->index = 0; + action->name = cursor->content; + + for (ppaction = &action_list; + *ppaction; + ppaction = &(*ppaction)->next + ) { + int cmp = strcmp(action->name, (*ppaction)->name); + if (cmp == 0) { + free(action); + action = *ppaction; + goto found; + } + if (cmp < 0) { + action->next = *ppaction; + *ppaction = action; + nr_actions++; + goto found; + } + } + action->next = NULL; + *ppaction = action; + nr_actions++; + found: + + element->action = action; + cursor->action = action; + cursor++; + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != TOKEN_CLOSE_ACTION) { + fprintf(stderr, "%s:%d: Missing close action, got '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + cursor++; + } + + *_cursor = cursor; + return top; + +parse_error: + fprintf(stderr, "%s:%d: Unexpected token '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + +overrun_error: + fprintf(stderr, "%s: Unexpectedly hit EOF\n", filename); + exit(1); +} + +/* + * Parse a compound type list + */ +static struct element *parse_compound(struct token **_cursor, struct token *end, + int alternates) +{ + struct element *children, **child_p = &children, *element; + struct token *cursor = *_cursor, *name; + + if (cursor->token_type != TOKEN_OPEN_CURLY) { + fprintf(stderr, "%s:%d: Expected compound to start with brace not '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + cursor++; + if (cursor >= end) + goto overrun_error; + + if (cursor->token_type == TOKEN_OPEN_CURLY) { + fprintf(stderr, "%s:%d: Empty compound\n", + filename, cursor->line); + exit(1); + } + + for (;;) { + name = NULL; + if (cursor->token_type == TOKEN_ELEMENT_NAME) { + name = cursor; + cursor++; + if (cursor >= end) + goto overrun_error; + } + + element = parse_type(&cursor, end, name); + if (alternates) + element->flags |= ELEMENT_SKIPPABLE | ELEMENT_CONDITIONAL; + + *child_p = element; + child_p = &element->next; + + if (cursor >= end) + goto overrun_error; + if (cursor->token_type != TOKEN_COMMA) + break; + cursor++; + if (cursor >= end) + goto overrun_error; + } + + children->flags &= ~ELEMENT_CONDITIONAL; + + if (cursor->token_type != TOKEN_CLOSE_CURLY) { + fprintf(stderr, "%s:%d: Expected compound closure, got '%s'\n", + filename, cursor->line, cursor->content); + exit(1); + } + cursor++; + + *_cursor = cursor; + return children; + +overrun_error: + fprintf(stderr, "%s: Unexpectedly hit EOF\n", filename); + exit(1); +} + +static void dump_element(const struct element *e, int level) +{ + const struct element *c; + const struct type *t = e->type_def; + const char *name = e->name ? e->name->content : "."; + const char *tname = t && t->name ? t->name->content : "."; + char tag[32]; + + if (e->class == 0 && e->method == 0 && e->tag == 0) + strcpy(tag, "<...>"); + else if (e->class == ASN1_UNIV) + sprintf(tag, "%s %s %s", + asn1_classes[e->class], + asn1_methods[e->method], + asn1_universal_tags[e->tag]); + else + sprintf(tag, "%s %s %u", + asn1_classes[e->class], + asn1_methods[e->method], + e->tag); + + printf("%c%c%c%c%c %c %*s[*] \e[33m%s\e[m %s %s \e[35m%s\e[m\n", + e->flags & ELEMENT_IMPLICIT ? 'I' : '-', + e->flags & ELEMENT_EXPLICIT ? 'E' : '-', + e->flags & ELEMENT_TAG_SPECIFIED ? 'T' : '-', + e->flags & ELEMENT_SKIPPABLE ? 'S' : '-', + e->flags & ELEMENT_CONDITIONAL ? 'C' : '-', + "-tTqQcaro"[e->compound], + level, "", + tag, + tname, + name, + e->action ? e->action->name : ""); + if (e->compound == TYPE_REF) + dump_element(e->type->type->element, level + 3); + else + for (c = e->children; c; c = c->next) + dump_element(c, level + 3); +} + +static void dump_elements(void) +{ + if (debug_opt) + dump_element(type_list[0].element, 0); +} + +static void render_element(FILE *out, struct element *e, struct element *tag); +static void render_out_of_line_list(FILE *out); + +static int nr_entries; +static int render_depth = 1; +static struct element *render_list, **render_list_p = &render_list; + +__attribute__((format(printf, 2, 3))) +static void render_opcode(FILE *out, const char *fmt, ...) +{ + va_list va; + + if (out) { + fprintf(out, "\t[%4d] =%*s", nr_entries, render_depth, ""); + va_start(va, fmt); + vfprintf(out, fmt, va); + va_end(va); + } + nr_entries++; +} + +__attribute__((format(printf, 2, 3))) +static void render_more(FILE *out, const char *fmt, ...) +{ + va_list va; + + if (out) { + va_start(va, fmt); + vfprintf(out, fmt, va); + va_end(va); + } +} + +/* + * Render the grammar into a state machine definition. + */ +static void render(FILE *out, FILE *hdr) +{ + struct element *e; + struct action *action; + struct type *root; + int index; + + fprintf(hdr, "/*\n"); + fprintf(hdr, " * Automatically generated by asn1_compiler. Do not edit\n"); + fprintf(hdr, " *\n"); + fprintf(hdr, " * ASN.1 parser for %s\n", grammar_name); + fprintf(hdr, " */\n"); + fprintf(hdr, "#include <linux/asn1_decoder.h>\n"); + fprintf(hdr, "\n"); + fprintf(hdr, "extern const struct asn1_decoder %s_decoder;\n", grammar_name); + if (ferror(hdr)) { + perror(headername); + exit(1); + } + + fprintf(out, "/*\n"); + fprintf(out, " * Automatically generated by asn1_compiler. Do not edit\n"); + fprintf(out, " *\n"); + fprintf(out, " * ASN.1 parser for %s\n", grammar_name); + fprintf(out, " */\n"); + fprintf(out, "#include <linux/asn1_ber_bytecode.h>\n"); + fprintf(out, "#include \"%s.asn1.h\"\n", grammar_name); + fprintf(out, "\n"); + if (ferror(out)) { + perror(outputname); + exit(1); + } + + /* Tabulate the action functions we might have to call */ + fprintf(hdr, "\n"); + index = 0; + for (action = action_list; action; action = action->next) { + action->index = index++; + fprintf(hdr, + "extern int %s(void *, size_t, unsigned char," + " const void *, size_t);\n", + action->name); + } + fprintf(hdr, "\n"); + + fprintf(out, "enum %s_actions {\n", grammar_name); + for (action = action_list; action; action = action->next) + fprintf(out, "\tACT_%s = %u,\n", + action->name, action->index); + fprintf(out, "\tNR__%s_actions = %u\n", grammar_name, nr_actions); + fprintf(out, "};\n"); + + fprintf(out, "\n"); + fprintf(out, "static const asn1_action_t %s_action_table[NR__%s_actions] = {\n", + grammar_name, grammar_name); + for (action = action_list; action; action = action->next) + fprintf(out, "\t[%4u] = %s,\n", action->index, action->name); + fprintf(out, "};\n"); + + if (ferror(out)) { + perror(outputname); + exit(1); + } + + /* We do two passes - the first one calculates all the offsets */ + verbose("Pass 1\n"); + nr_entries = 0; + root = &type_list[0]; + render_element(NULL, root->element, NULL); + render_opcode(NULL, "ASN1_OP_COMPLETE,\n"); + render_out_of_line_list(NULL); + + for (e = element_list; e; e = e->list_next) + e->flags &= ~ELEMENT_RENDERED; + + /* And then we actually render */ + verbose("Pass 2\n"); + fprintf(out, "\n"); + fprintf(out, "static const unsigned char %s_machine[] = {\n", + grammar_name); + + nr_entries = 0; + root = &type_list[0]; + render_element(out, root->element, NULL); + render_opcode(out, "ASN1_OP_COMPLETE,\n"); + render_out_of_line_list(out); + + fprintf(out, "};\n"); + + fprintf(out, "\n"); + fprintf(out, "const struct asn1_decoder %s_decoder = {\n", grammar_name); + fprintf(out, "\t.machine = %s_machine,\n", grammar_name); + fprintf(out, "\t.machlen = sizeof(%s_machine),\n", grammar_name); + fprintf(out, "\t.actions = %s_action_table,\n", grammar_name); + fprintf(out, "};\n"); +} + +/* + * Render the out-of-line elements + */ +static void render_out_of_line_list(FILE *out) +{ + struct element *e, *ce; + const char *act; + int entry; + + while ((e = render_list)) { + render_list = e->render_next; + if (!render_list) + render_list_p = &render_list; + + render_more(out, "\n"); + e->entry_index = entry = nr_entries; + render_depth++; + for (ce = e->children; ce; ce = ce->next) + render_element(out, ce, NULL); + render_depth--; + + act = e->action ? "_ACT" : ""; + switch (e->compound) { + case SEQUENCE: + render_opcode(out, "ASN1_OP_END_SEQ%s,\n", act); + break; + case SEQUENCE_OF: + render_opcode(out, "ASN1_OP_END_SEQ_OF%s,\n", act); + render_opcode(out, "_jump_target(%u),\n", entry); + break; + case SET: + render_opcode(out, "ASN1_OP_END_SET%s,\n", act); + break; + case SET_OF: + render_opcode(out, "ASN1_OP_END_SET_OF%s,\n", act); + render_opcode(out, "_jump_target(%u),\n", entry); + break; + default: + break; + } + if (e->action) + render_opcode(out, "_action(ACT_%s),\n", + e->action->name); + render_opcode(out, "ASN1_OP_RETURN,\n"); + } +} + +/* + * Render an element. + */ +static void render_element(FILE *out, struct element *e, struct element *tag) +{ + struct element *ec, *x; + const char *cond, *act; + int entry, skippable = 0, outofline = 0; + + if (e->flags & ELEMENT_SKIPPABLE || + (tag && tag->flags & ELEMENT_SKIPPABLE)) + skippable = 1; + + if ((e->type_def && e->type_def->ref_count > 1) || + skippable) + outofline = 1; + + if (e->type_def && out) { + render_more(out, "\t// %s\n", e->type_def->name->content); + } + + /* Render the operation */ + cond = (e->flags & ELEMENT_CONDITIONAL || + (tag && tag->flags & ELEMENT_CONDITIONAL)) ? "COND_" : ""; + act = e->action ? "_ACT" : ""; + switch (e->compound) { + case ANY: + render_opcode(out, "ASN1_OP_%sMATCH_ANY%s%s,", + cond, act, skippable ? "_OR_SKIP" : ""); + if (e->name) + render_more(out, "\t\t// %s", e->name->content); + render_more(out, "\n"); + goto dont_render_tag; + + case TAG_OVERRIDE: + render_element(out, e->children, e); + return; + + case SEQUENCE: + case SEQUENCE_OF: + case SET: + case SET_OF: + render_opcode(out, "ASN1_OP_%sMATCH%s%s,", + cond, + outofline ? "_JUMP" : "", + skippable ? "_OR_SKIP" : ""); + break; + + case CHOICE: + goto dont_render_tag; + + case TYPE_REF: + if (e->class == ASN1_UNIV && e->method == ASN1_PRIM && e->tag == 0) + goto dont_render_tag; + default: + render_opcode(out, "ASN1_OP_%sMATCH%s%s,", + cond, act, + skippable ? "_OR_SKIP" : ""); + break; + } + + x = tag ?: e; + if (x->name) + render_more(out, "\t\t// %s", x->name->content); + render_more(out, "\n"); + + /* Render the tag */ + if (!tag || !(tag->flags & ELEMENT_TAG_SPECIFIED)) + tag = e; + + if (tag->class == ASN1_UNIV && + tag->tag != 14 && + tag->tag != 15 && + tag->tag != 31) + render_opcode(out, "_tag(%s, %s, %s),\n", + asn1_classes[tag->class], + asn1_methods[tag->method | e->method], + asn1_universal_tags[tag->tag]); + else + render_opcode(out, "_tagn(%s, %s, %2u),\n", + asn1_classes[tag->class], + asn1_methods[tag->method | e->method], + tag->tag); + tag = NULL; +dont_render_tag: + + /* Deal with compound types */ + switch (e->compound) { + case TYPE_REF: + render_element(out, e->type->type->element, tag); + if (e->action) + render_opcode(out, "ASN1_OP_%sACT,\n", + skippable ? "MAYBE_" : ""); + break; + + case SEQUENCE: + if (outofline) { + /* Render out-of-line for multiple use or + * skipability */ + render_opcode(out, "_jump_target(%u),", e->entry_index); + if (e->type_def && e->type_def->name) + render_more(out, "\t\t// --> %s", + e->type_def->name->content); + render_more(out, "\n"); + if (!(e->flags & ELEMENT_RENDERED)) { + e->flags |= ELEMENT_RENDERED; + *render_list_p = e; + render_list_p = &e->render_next; + } + return; + } else { + /* Render inline for single use */ + render_depth++; + for (ec = e->children; ec; ec = ec->next) + render_element(out, ec, NULL); + render_depth--; + render_opcode(out, "ASN1_OP_END_SEQ%s,\n", act); + } + break; + + case SEQUENCE_OF: + case SET_OF: + if (outofline) { + /* Render out-of-line for multiple use or + * skipability */ + render_opcode(out, "_jump_target(%u),", e->entry_index); + if (e->type_def && e->type_def->name) + render_more(out, "\t\t// --> %s", + e->type_def->name->content); + render_more(out, "\n"); + if (!(e->flags & ELEMENT_RENDERED)) { + e->flags |= ELEMENT_RENDERED; + *render_list_p = e; + render_list_p = &e->render_next; + } + return; + } else { + /* Render inline for single use */ + entry = nr_entries; + render_depth++; + render_element(out, e->children, NULL); + render_depth--; + if (e->compound == SEQUENCE_OF) + render_opcode(out, "ASN1_OP_END_SEQ_OF%s,\n", act); + else + render_opcode(out, "ASN1_OP_END_SET_OF%s,\n", act); + render_opcode(out, "_jump_target(%u),\n", entry); + } + break; + + case SET: + /* I can't think of a nice way to do SET support without having + * a stack of bitmasks to make sure no element is repeated. + * The bitmask has also to be checked that no non-optional + * elements are left out whilst not preventing optional + * elements from being left out. + */ + fprintf(stderr, "The ASN.1 SET type is not currently supported.\n"); + exit(1); + + case CHOICE: + for (ec = e->children; ec; ec = ec->next) + render_element(out, ec, ec); + if (!skippable) + render_opcode(out, "ASN1_OP_COND_FAIL,\n"); + if (e->action) + render_opcode(out, "ASN1_OP_ACT,\n"); + break; + + default: + break; + } + + if (e->action) + render_opcode(out, "_action(ACT_%s),\n", e->action->name); +} |