* [PATCH] tools: add a simple script to generate EFI variables @ 2020-11-30 15:16 Paulo Alcantara 2020-11-30 16:38 ` Heinrich Schuchardt ` (2 more replies) 0 siblings, 3 replies; 9+ messages in thread From: Paulo Alcantara @ 2020-11-30 15:16 UTC (permalink / raw) To: u-boot This script generates EFI variables for U-Boot variable store format. An example of generating secure boot variables $ openssl x509 -in foo.crt -outform DER -out foo.der $ efisiglist -a -c foo.der -o foo.esl $ efivar.py -i ubootefi.var add -n db -d foo.esl -t file $ efivar.py -i ubootefi.var add -n kek -d foo.esl -t file $ efivar.py -i ubootefi.var add -n pk -d foo.esl -t file Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz> --- tools/efivar.py | 141 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100755 tools/efivar.py diff --git a/tools/efivar.py b/tools/efivar.py new file mode 100755 index 000000000000..31e5508f08fd --- /dev/null +++ b/tools/efivar.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +## SPDX-License-Identifier: GPL-2.0-only +# +# Generate UEFI variables for U-Boot. +# +# (c) 2020 Paulo Alcantara <palcantara@suse.de> +# + +import os +import struct +import uuid +import time +import zlib +import argparse +import subprocess as sp + +# U-Boot variable store format (version 1) +UBOOT_EFI_VAR_FILE_MAGIC = 0x0161566966456255 + +# UEFI variable attributes +EFI_VARIABLE_NON_VOLATILE = 0x1 +EFI_VARIABLE_BOOTSERVICE_ACCESS = 0x2 +EFI_VARIABLE_RUNTIME_ACCESS = 0x4 +EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x20 +NV_BS = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS +NV_BS_RT = NV_BS | EFI_VARIABLE_RUNTIME_ACCESS +NV_BS_RT_AT = NV_BS_RT | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS + +# UEFI variable GUIDs +EFI_GLOBAL_VARIABLE_GUID = '{8be4df61-93ca-11d2-aa0d-00e098032b8c}' +EFI_IMAGE_SECURITY_DATABASE_GUID = '{d719b2cb-3d3a-4596-a3bc-dad00e67656f}' + +class EfiStruct: + # struct efi_var_file + var_file_fmt = '<QQLL' + var_file_size = struct.calcsize(var_file_fmt) + # struct efi_var_entry + var_entry_fmt = '<LLQ16s' + var_entry_size = struct.calcsize(var_entry_fmt) + +class EfiVariableStore: + def __init__(self, infile): + self.infile = infile + self.efi = EfiStruct() + if os.path.exists(self.infile) and os.stat(self.infile).st_size > self.efi.var_file_size: + with open(self.infile, 'rb') as f: + # skip header since it will be recreated by save() + self.buf = f.read()[self.efi.var_file_size:] + else: + self.buf = bytearray() + + def _set_var(self, guid, name_data, size, attr, tsec): + ent = struct.pack(self.efi.var_entry_fmt, + size, + attr, + tsec, + uuid.UUID(guid).bytes_le) + ent += name_data + self.buf += ent + + def set_var(self, guid, name, data, size, attr): + tsec = int(time.time()) if attr & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS else 0 + nd = name.encode('utf_16_le') + b"\x00\x00" + data + # U-Boot variable format requires the name + data blob to be 8-byte aligned + pad = ((len(nd) + 7) & ~7) - len(nd) + nd += bytes([0] * pad) + + return self._set_var(guid, nd, size, attr, tsec) + + def save(self): + hdr = struct.pack(self.efi.var_file_fmt, + 0, + UBOOT_EFI_VAR_FILE_MAGIC, + len(self.buf) + self.efi.var_file_size, + zlib.crc32(self.buf) & 0xffffffff) + + with open(self.infile, 'wb') as f: + f.write(hdr) + f.write(self.buf) + +def parse_attrs(attr): + attrs = { + 'nv': EFI_VARIABLE_NON_VOLATILE, + 'bs': EFI_VARIABLE_BOOTSERVICE_ACCESS, + 'rt': EFI_VARIABLE_RUNTIME_ACCESS, + 'at': EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, + } + v = 0 + for i in attr.split(','): + v |= attrs[i.lower()] + return v + +def parse_data(val, vtype): + fmt = { 'u8': '<B', 'u16': '<H', 'u32': '<L', 'u64': '<Q' } + if vtype.lower() == 'file': + with open(val, 'rb') as f: + data = f.read() + return data, len(data) + if vtype.lower() == 'str': + data = val.encode('utf-8') + b'\x00' + return data, len(data) + i = fmt[vtype.lower()] + return struct.pack(i, int(val)), struct.calcsize(i) + +def cmd_add(args): + env = EfiVariableStore(args.infile) + data, size = parse_data(args.data, args.type) + + if args.name.lower() == 'pk': + env.set_var(guid=EFI_GLOBAL_VARIABLE_GUID, name='PK', data=data, size=size, attr=NV_BS_RT_AT) + elif args.name.lower() == 'kek': + env.set_var(guid=EFI_GLOBAL_VARIABLE_GUID, name='KEK', data=data, size=size, attr=NV_BS_RT_AT) + elif args.name.lower() == 'db': + env.set_var(guid=EFI_IMAGE_SECURITY_DATABASE_GUID, name='db', data=data, size=size, attr=NV_BS_RT_AT) + elif args.name.lower() == 'dbx': + env.set_var(guid=EFI_IMAGE_SECURITY_DATABASE_GUID, name='dbx', data=data, size=size, attr=NV_BS_RT_AT) + else: + guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID + attr = parse_attrs(args.attr) if args.attr else NV_BS + env.set_var(guid=guid, name=args.name, data=data, size=size, attr=attr) + + env.save() + +def main(): + ap = argparse.ArgumentParser(description='Generate U-Boot variable store') + ap.add_argument('--infile', '-i', required=True, help='file to save the UEFI variables') + subp = ap.add_subparsers(help="sub-command help") + + addp = subp.add_parser('add', help='add UEFI variable') + addp.add_argument('--name', '-n', required=True, help='variable name') + addp.add_argument('--attr', '-a', help='variable attributes (default: nv,bs)') + addp.add_argument('--guid', '-g', help="variable guid (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) + addp.add_argument('--type', '-t', required=True, help='variable type (values: file|u8|u16|u32|u64|str)') + addp.add_argument('--data', '-d', required=True, help='variable data') + addp.set_defaults(func=cmd_add) + + args = ap.parse_args() + args.func(args) + +if __name__ == '__main__': + main() -- 2.29.2 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH] tools: add a simple script to generate EFI variables 2020-11-30 15:16 [PATCH] tools: add a simple script to generate EFI variables Paulo Alcantara @ 2020-11-30 16:38 ` Heinrich Schuchardt 2020-11-30 18:26 ` Paulo Alcantara 2020-12-01 22:58 ` [PATCH v2] " Paulo Alcantara 2020-12-02 16:46 ` [PATCH v3] " Paulo Alcantara 2 siblings, 1 reply; 9+ messages in thread From: Heinrich Schuchardt @ 2020-11-30 16:38 UTC (permalink / raw) To: u-boot On 11/30/20 4:16 PM, Paulo Alcantara wrote: > This script generates EFI variables for U-Boot variable store format. Hello Paulo, thanks for you valuable contribution. Wouldn't it make sense to allow overwriting and deleting variables too? Best regards Heinrich > > An example of generating secure boot variables > > $ openssl x509 -in foo.crt -outform DER -out foo.der > $ efisiglist -a -c foo.der -o foo.esl > $ efivar.py -i ubootefi.var add -n db -d foo.esl -t file > $ efivar.py -i ubootefi.var add -n kek -d foo.esl -t file > $ efivar.py -i ubootefi.var add -n pk -d foo.esl -t file > > Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz> > --- > tools/efivar.py | 141 ++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 141 insertions(+) > create mode 100755 tools/efivar.py > > diff --git a/tools/efivar.py b/tools/efivar.py > new file mode 100755 > index 000000000000..31e5508f08fd > --- /dev/null > +++ b/tools/efivar.py > @@ -0,0 +1,141 @@ > +#!/usr/bin/env python3 > +## SPDX-License-Identifier: GPL-2.0-only > +# > +# Generate UEFI variables for U-Boot. > +# > +# (c) 2020 Paulo Alcantara <palcantara@suse.de> > +# > + > +import os > +import struct > +import uuid > +import time > +import zlib > +import argparse > +import subprocess as sp > + > +# U-Boot variable store format (version 1) > +UBOOT_EFI_VAR_FILE_MAGIC = 0x0161566966456255 > + > +# UEFI variable attributes > +EFI_VARIABLE_NON_VOLATILE = 0x1 > +EFI_VARIABLE_BOOTSERVICE_ACCESS = 0x2 > +EFI_VARIABLE_RUNTIME_ACCESS = 0x4 > +EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x20 > +NV_BS = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS > +NV_BS_RT = NV_BS | EFI_VARIABLE_RUNTIME_ACCESS > +NV_BS_RT_AT = NV_BS_RT | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS > + > +# UEFI variable GUIDs > +EFI_GLOBAL_VARIABLE_GUID = '{8be4df61-93ca-11d2-aa0d-00e098032b8c}' > +EFI_IMAGE_SECURITY_DATABASE_GUID = '{d719b2cb-3d3a-4596-a3bc-dad00e67656f}' > + > +class EfiStruct: > + # struct efi_var_file > + var_file_fmt = '<QQLL' > + var_file_size = struct.calcsize(var_file_fmt) > + # struct efi_var_entry > + var_entry_fmt = '<LLQ16s' > + var_entry_size = struct.calcsize(var_entry_fmt) > + > +class EfiVariableStore: > + def __init__(self, infile): > + self.infile = infile > + self.efi = EfiStruct() > + if os.path.exists(self.infile) and os.stat(self.infile).st_size > self.efi.var_file_size: > + with open(self.infile, 'rb') as f: > + # skip header since it will be recreated by save() > + self.buf = f.read()[self.efi.var_file_size:] > + else: > + self.buf = bytearray() > + > + def _set_var(self, guid, name_data, size, attr, tsec): > + ent = struct.pack(self.efi.var_entry_fmt, > + size, > + attr, > + tsec, > + uuid.UUID(guid).bytes_le) > + ent += name_data > + self.buf += ent > + > + def set_var(self, guid, name, data, size, attr): > + tsec = int(time.time()) if attr & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS else 0 > + nd = name.encode('utf_16_le') + b"\x00\x00" + data > + # U-Boot variable format requires the name + data blob to be 8-byte aligned > + pad = ((len(nd) + 7) & ~7) - len(nd) > + nd += bytes([0] * pad) > + > + return self._set_var(guid, nd, size, attr, tsec) > + > + def save(self): > + hdr = struct.pack(self.efi.var_file_fmt, > + 0, > + UBOOT_EFI_VAR_FILE_MAGIC, > + len(self.buf) + self.efi.var_file_size, > + zlib.crc32(self.buf) & 0xffffffff) > + > + with open(self.infile, 'wb') as f: > + f.write(hdr) > + f.write(self.buf) > + > +def parse_attrs(attr): > + attrs = { > + 'nv': EFI_VARIABLE_NON_VOLATILE, > + 'bs': EFI_VARIABLE_BOOTSERVICE_ACCESS, > + 'rt': EFI_VARIABLE_RUNTIME_ACCESS, > + 'at': EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, > + } > + v = 0 > + for i in attr.split(','): > + v |= attrs[i.lower()] > + return v > + > +def parse_data(val, vtype): > + fmt = { 'u8': '<B', 'u16': '<H', 'u32': '<L', 'u64': '<Q' } > + if vtype.lower() == 'file': > + with open(val, 'rb') as f: > + data = f.read() > + return data, len(data) > + if vtype.lower() == 'str': > + data = val.encode('utf-8') + b'\x00' > + return data, len(data) > + i = fmt[vtype.lower()] > + return struct.pack(i, int(val)), struct.calcsize(i) > + > +def cmd_add(args): > + env = EfiVariableStore(args.infile) > + data, size = parse_data(args.data, args.type) > + > + if args.name.lower() == 'pk': > + env.set_var(guid=EFI_GLOBAL_VARIABLE_GUID, name='PK', data=data, size=size, attr=NV_BS_RT_AT) > + elif args.name.lower() == 'kek': > + env.set_var(guid=EFI_GLOBAL_VARIABLE_GUID, name='KEK', data=data, size=size, attr=NV_BS_RT_AT) > + elif args.name.lower() == 'db': > + env.set_var(guid=EFI_IMAGE_SECURITY_DATABASE_GUID, name='db', data=data, size=size, attr=NV_BS_RT_AT) > + elif args.name.lower() == 'dbx': > + env.set_var(guid=EFI_IMAGE_SECURITY_DATABASE_GUID, name='dbx', data=data, size=size, attr=NV_BS_RT_AT) > + else: > + guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID > + attr = parse_attrs(args.attr) if args.attr else NV_BS > + env.set_var(guid=guid, name=args.name, data=data, size=size, attr=attr) > + > + env.save() > + > +def main(): > + ap = argparse.ArgumentParser(description='Generate U-Boot variable store') > + ap.add_argument('--infile', '-i', required=True, help='file to save the UEFI variables') > + subp = ap.add_subparsers(help="sub-command help") > + > + addp = subp.add_parser('add', help='add UEFI variable') > + addp.add_argument('--name', '-n', required=True, help='variable name') > + addp.add_argument('--attr', '-a', help='variable attributes (default: nv,bs)') > + addp.add_argument('--guid', '-g', help="variable guid (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) > + addp.add_argument('--type', '-t', required=True, help='variable type (values: file|u8|u16|u32|u64|str)') > + addp.add_argument('--data', '-d', required=True, help='variable data') > + addp.set_defaults(func=cmd_add) > + > + args = ap.parse_args() > + args.func(args) > + > +if __name__ == '__main__': > + main() > ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH] tools: add a simple script to generate EFI variables 2020-11-30 16:38 ` Heinrich Schuchardt @ 2020-11-30 18:26 ` Paulo Alcantara 0 siblings, 0 replies; 9+ messages in thread From: Paulo Alcantara @ 2020-11-30 18:26 UTC (permalink / raw) To: u-boot Hi Heinrich, Heinrich Schuchardt <xypron.glpk@gmx.de> writes: > On 11/30/20 4:16 PM, Paulo Alcantara wrote: >> This script generates EFI variables for U-Boot variable store format. > > Wouldn't it make sense to allow overwriting and deleting variables too? Absolutely. I'll repost it with those features. Thanks! ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2] tools: add a simple script to generate EFI variables 2020-11-30 15:16 [PATCH] tools: add a simple script to generate EFI variables Paulo Alcantara 2020-11-30 16:38 ` Heinrich Schuchardt @ 2020-12-01 22:58 ` Paulo Alcantara 2020-12-02 10:50 ` Heinrich Schuchardt 2020-12-02 16:46 ` [PATCH v3] " Paulo Alcantara 2 siblings, 1 reply; 9+ messages in thread From: Paulo Alcantara @ 2020-12-01 22:58 UTC (permalink / raw) To: u-boot This script generates EFI variables for U-Boot variable store format. A few examples: - Generating secure boot keys $ openssl x509 -in foo.crt -outform DER -out foo.der $ efisiglist -a -c foo.der -o foo.esl $ ./efivar.py -i ubootefi.var set -n db -d foo.esl -t file $ ./efivar.py -i ubootefi.var set -n kek -d foo.esl -t file $ ./efivar.py -i ubootefi.var set -n pk -d foo.esl -t file - Printing out variables $ ./efivar.py -i ubootefi.var set -n var1 -a nv,bs -d foo -t str $ ./efivar.py -i ubootefi.var set -n var2 -a nv,bs -d bar -t str $ ./efivar.py -i ubootefi.var print var1: 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID NV|BS, DataSize = 0x3 0000000000: 66 6F 6F foo var2: 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID NV|BS, DataSize = 0x3 0000000000: 62 61 72 bar - Removing variables $ ./efivar.py -i ubootefi.var set -n var1 -a nv,bs -d foo -t str $ ./efivar.py -i ubootefi.var print -n var1 var1: 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID NV|BS, DataSize = 0x3 0000000000: 66 6F 6F foo $ ./efivar.py -i ubootefi.var set -n var1 $ ./efivar.py -i ubootefi.var print -n var1 err: variable not found Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz> --- tools/efivar.py | 276 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100755 tools/efivar.py diff --git a/tools/efivar.py b/tools/efivar.py new file mode 100755 index 000000000000..ecd12319c43b --- /dev/null +++ b/tools/efivar.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +## SPDX-License-Identifier: GPL-2.0-only +# +# Generate UEFI variables for U-Boot. +# +# (c) 2020 Paulo Alcantara <palcantara@suse.de> +# + +import os +import struct +import uuid +import time +import zlib +import argparse + +# U-Boot variable store format (version 1) +UBOOT_EFI_VAR_FILE_MAGIC = 0x0161566966456255 + +# UEFI variable attributes +EFI_VARIABLE_NON_VOLATILE = 0x1 +EFI_VARIABLE_BOOTSERVICE_ACCESS = 0x2 +EFI_VARIABLE_RUNTIME_ACCESS = 0x4 +EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS = 0x10 +EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x20 +EFI_VARIABLE_READ_ONLY = 1 << 31 +NV_BS = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS +NV_BS_RT = NV_BS | EFI_VARIABLE_RUNTIME_ACCESS +NV_BS_RT_AT = NV_BS_RT | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS + +# UEFI variable GUIDs +EFI_GLOBAL_VARIABLE_GUID = '8be4df61-93ca-11d2-aa0d-00e098032b8c' +EFI_IMAGE_SECURITY_DATABASE_GUID = 'd719b2cb-3d3a-4596-a3bc-dad00e67656f' + +var_attrs = { + 'NV': EFI_VARIABLE_NON_VOLATILE, + 'BS': EFI_VARIABLE_BOOTSERVICE_ACCESS, + 'RT': EFI_VARIABLE_RUNTIME_ACCESS, + 'AT': EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, + 'RO': EFI_VARIABLE_READ_ONLY, + 'AW': EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS, +} + +var_guids = { + 'EFI_GLOBAL_VARIABLE_GUID': '8be4df61-93ca-11d2-aa0d-00e098032b8c', + 'EFI_IMAGE_SECURITY_DATABASE_GUID': 'd719b2cb-3d3a-4596-a3bc-dad00e67656f', +} + +class EfiStruct: + # struct efi_var_file + var_file_fmt = '<QQLL' + var_file_size = struct.calcsize(var_file_fmt) + # struct efi_var_entry + var_entry_fmt = '<LLQ16s' + var_entry_size = struct.calcsize(var_entry_fmt) + +class EfiVariable: + def __init__(self, size, attrs, time, guid, name, data): + self.size = size + self.attrs = attrs + self.time = time + self.guid = guid + self.name = name + self.data = data + +class EfiVariableStore: + def __init__(self, infile): + self.infile = infile + self.efi = EfiStruct() + if os.path.exists(self.infile) and os.stat(self.infile).st_size > self.efi.var_file_size: + with open(self.infile, 'rb') as f: + buf = f.read() + self._check_header(buf) + self.ents = buf[self.efi.var_file_size:] + else: + self.ents = bytearray() + + def _check_header(self, buf): + hdr = struct.unpack_from(self.efi.var_file_fmt, buf, 0) + magic, crc32 = hdr[1], hdr[3] + + if magic != UBOOT_EFI_VAR_FILE_MAGIC: + print("err: invalid magic number: %s"%hex(magic)) + exit(1) + if crc32 != zlib.crc32(buf[self.efi.var_file_size:]) & 0xffffffff: + print("err: invalid crc32: %s"%hex(crc32)) + exit(1) + + def _get_var_name(self, buf): + name = '' + for i in range(0, len(buf) - 1, 2): + if not buf[i] and not buf[i+1]: + break + name += chr(buf[i]) + return ''.join([chr(x) for x in name.encode('utf_16_le') if x]), i + 2 + + def _next_var(self, offs=0): + size, attrs, time, guid = struct.unpack_from(self.efi.var_entry_fmt, self.ents, offs) + data_fmt = str(size)+"s" + offs += self.efi.var_entry_size + name, namelen = self._get_var_name(self.ents[offs:]) + offs += namelen + data = struct.unpack_from(data_fmt, self.ents, offs)[0] + offs = (offs + len(data) + 7) & ~7 + return EfiVariable(size, attrs, time, uuid.UUID(bytes_le=guid), name, data), offs + + def __iter__(self): + self.offs = 0 + return self + + def __next__(self): + if self.offs < len(self.ents): + var, noffs = self._next_var(self.offs) + self.offs = noffs + return var + else: + raise StopIteration + + def __len__(self): + return len(self.ents) + + def _set_var(self, guid, name_data, size, attrs, tsec): + ent = struct.pack(self.efi.var_entry_fmt, + size, + attrs, + tsec, + uuid.UUID(guid).bytes_le) + ent += name_data + self.ents += ent + + def set_var(self, guid, name, data, size, attrs): + offs = 0 + while offs < len(self.ents): + var, loffs = self._next_var(offs) + if var.name == name and str(var.guid) == guid: + if not data or not attrs: + self.ents = self.ents[:offs] + self.ents[loffs:] + return + if var.attrs != attrs: + print("err: invalid attributes") + exit(1) + # make room for updating var + self.ents = self.ents[:offs] + self.ents[loffs:] + break + offs = loffs + + if not data or not attrs: + print("err: variable not found") + exit(1) + + tsec = int(time.time()) if attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS else 0 + nd = name.encode('utf_16_le') + b"\x00\x00" + data + # U-Boot variable format requires the name + data blob to be 8-byte aligned + pad = ((len(nd) + 7) & ~7) - len(nd) + nd += bytes([0] * pad) + + return self._set_var(guid, nd, size, attrs, tsec) + + def save(self): + hdr = struct.pack(self.efi.var_file_fmt, + 0, + UBOOT_EFI_VAR_FILE_MAGIC, + len(self.ents) + self.efi.var_file_size, + zlib.crc32(self.ents) & 0xffffffff) + + with open(self.infile, 'wb') as f: + f.write(hdr) + f.write(self.ents) + +def parse_attrs(attrs): + v = 0 + if attrs: + for i in attrs.split(','): + v |= var_attrs[i.upper()] + return v + +def parse_data(val, vtype): + if not val or not vtype: + return None, 0 + fmt = { 'u8': '<B', 'u16': '<H', 'u32': '<L', 'u64': '<Q' } + if vtype.lower() == 'file': + with open(val, 'rb') as f: + data = f.read() + return data, len(data) + if vtype.lower() == 'str': + data = val.encode('utf-8') + return data, len(data) + i = fmt[vtype.lower()] + return struct.pack(i, int(val)), struct.calcsize(i) + +def cmd_set(args): + env = EfiVariableStore(args.infile) + data, size = parse_data(args.data, args.type) + + if args.name.lower() == 'pk': + env.set_var(guid=EFI_GLOBAL_VARIABLE_GUID, name='PK', data=data, size=size, attrs=NV_BS_RT_AT) + elif args.name.lower() == 'kek': + env.set_var(guid=EFI_GLOBAL_VARIABLE_GUID, name='KEK', data=data, size=size, attrs=NV_BS_RT_AT) + elif args.name.lower() == 'db': + env.set_var(guid=EFI_IMAGE_SECURITY_DATABASE_GUID, name='db', data=data, size=size, attrs=NV_BS_RT_AT) + elif args.name.lower() == 'dbx': + env.set_var(guid=EFI_IMAGE_SECURITY_DATABASE_GUID, name='dbx', data=data, size=size, attrs=NV_BS_RT_AT) + else: + guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID + attrs = parse_attrs(args.attrs) + env.set_var(guid=guid, name=args.name, data=data, size=size, attrs=attrs) + + env.save() + +def print_var(var): + print(var.name+':') + print(" "+str(var.guid)+' '+''.join([x for x in var_guids if str(var.guid) == var_guids[x]])) + print(" "+'|'.join([x for x in var_attrs if var.attrs & var_attrs[x]])+", DataSize = %s"%hex(var.size)) + hexdump(var.data) + +def cmd_print(args): + env = EfiVariableStore(args.infile) + if not args.name and not args.guid and not len(env): + return + + found = False + for var in env: + if not args.name: + if args.guid and args.guid != str(var.guid): + continue + print_var(var) + found = True + else: + if args.name != var.name or (args.guid and args.guid != str(var.guid)): + continue + print_var(var) + found = True + + if not found: + print("err: variable not found") + exit(1) + +def main(): + ap = argparse.ArgumentParser(description='Generate U-Boot variable store') + ap.add_argument('--infile', '-i', required=True, help='file to save the UEFI variables') + subp = ap.add_subparsers(help="sub-command help") + + printp = subp.add_parser('print', help='get/list UEFI variables') + printp.add_argument('--guid', '-g', help='variable guid') + printp.add_argument('--name', '-n', help='variable name') + printp.set_defaults(func=cmd_print) + + setp = subp.add_parser('set', help='set UEFI variable') + setp.add_argument('--name', '-n', required=True, help='variable name') + setp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)') + setp.add_argument('--guid', '-g', help="variable guid (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) + setp.add_argument('--type', '-t', help='variable type (values: file|u8|u16|u32|u64|str)') + setp.add_argument('--data', '-d', help='variable data') + setp.set_defaults(func=cmd_set) + + args = ap.parse_args() + args.func(args) + +def group(a, *ns): + for n in ns: + a = [a[i:i+n] for i in range(0, len(a), n)] + return a + +def join(a, *cs): + return [cs[0].join(join(t, *cs[1:])) for t in a] if cs else a + +def hexdump(data): + toHex = lambda c: '{:02X}'.format(c) + toChr = lambda c: chr(c) if 32 <= c < 127 else '.' + make = lambda f, *cs: join(group(list(map(f, data)), 8, 2), *cs) + hs = make(toHex, ' ', ' ') + cs = make(toChr, ' ', '') + for i, (h, c) in enumerate(zip(hs, cs)): + print (' {:010X}: {:48} {:16}'.format(i * 16, h, c)) + +if __name__ == '__main__': + main() -- 2.29.2 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2] tools: add a simple script to generate EFI variables 2020-12-01 22:58 ` [PATCH v2] " Paulo Alcantara @ 2020-12-02 10:50 ` Heinrich Schuchardt 0 siblings, 0 replies; 9+ messages in thread From: Heinrich Schuchardt @ 2020-12-02 10:50 UTC (permalink / raw) To: u-boot On 12/1/20 11:58 PM, Paulo Alcantara wrote: > This script generates EFI variables for U-Boot variable store format. > > A few examples: > > - Generating secure boot keys > > $ openssl x509 -in foo.crt -outform DER -out foo.der > $ efisiglist -a -c foo.der -o foo.esl > $ ./efivar.py -i ubootefi.var set -n db -d foo.esl -t file > $ ./efivar.py -i ubootefi.var set -n kek -d foo.esl -t file > $ ./efivar.py -i ubootefi.var set -n pk -d foo.esl -t file > > - Printing out variables > > $ ./efivar.py -i ubootefi.var set -n var1 -a nv,bs -d foo -t str > $ ./efivar.py -i ubootefi.var set -n var2 -a nv,bs -d bar -t str > $ ./efivar.py -i ubootefi.var print > var1: > 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID > NV|BS, DataSize = 0x3 > 0000000000: 66 6F 6F foo > var2: > 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID > NV|BS, DataSize = 0x3 > 0000000000: 62 61 72 bar > > - Removing variables > > $ ./efivar.py -i ubootefi.var set -n var1 -a nv,bs -d foo -t str > $ ./efivar.py -i ubootefi.var print -n var1 > var1: > 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID > NV|BS, DataSize = 0x3 > 0000000000: 66 6F 6F foo > $ ./efivar.py -i ubootefi.var set -n var1 > $ ./efivar.py -i ubootefi.var print -n var1 > err: variable not found > > Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz> > --- > tools/efivar.py | 276 ++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 276 insertions(+) > create mode 100755 tools/efivar.py > > diff --git a/tools/efivar.py b/tools/efivar.py > new file mode 100755 > index 000000000000..ecd12319c43b > --- /dev/null > +++ b/tools/efivar.py > @@ -0,0 +1,276 @@ > +#!/usr/bin/env python3 > +## SPDX-License-Identifier: GPL-2.0-only > +# > +# Generate UEFI variables for U-Boot. > +# > +# (c) 2020 Paulo Alcantara <palcantara@suse.de> > +# > + > +import os > +import struct > +import uuid > +import time > +import zlib > +import argparse > + > +# U-Boot variable store format (version 1) > +UBOOT_EFI_VAR_FILE_MAGIC = 0x0161566966456255 > + > +# UEFI variable attributes > +EFI_VARIABLE_NON_VOLATILE = 0x1 > +EFI_VARIABLE_BOOTSERVICE_ACCESS = 0x2 > +EFI_VARIABLE_RUNTIME_ACCESS = 0x4 > +EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS = 0x10 > +EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x20 > +EFI_VARIABLE_READ_ONLY = 1 << 31 > +NV_BS = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS > +NV_BS_RT = NV_BS | EFI_VARIABLE_RUNTIME_ACCESS > +NV_BS_RT_AT = NV_BS_RT | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS > + > +# UEFI variable GUIDs > +EFI_GLOBAL_VARIABLE_GUID = '8be4df61-93ca-11d2-aa0d-00e098032b8c' > +EFI_IMAGE_SECURITY_DATABASE_GUID = 'd719b2cb-3d3a-4596-a3bc-dad00e67656f' > + > +var_attrs = { > + 'NV': EFI_VARIABLE_NON_VOLATILE, > + 'BS': EFI_VARIABLE_BOOTSERVICE_ACCESS, > + 'RT': EFI_VARIABLE_RUNTIME_ACCESS, > + 'AT': EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, > + 'RO': EFI_VARIABLE_READ_ONLY, > + 'AW': EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS, > +} > + > +var_guids = { > + 'EFI_GLOBAL_VARIABLE_GUID': '8be4df61-93ca-11d2-aa0d-00e098032b8c', > + 'EFI_IMAGE_SECURITY_DATABASE_GUID': 'd719b2cb-3d3a-4596-a3bc-dad00e67656f', > +} > + > +class EfiStruct: > + # struct efi_var_file > + var_file_fmt = '<QQLL' > + var_file_size = struct.calcsize(var_file_fmt) > + # struct efi_var_entry > + var_entry_fmt = '<LLQ16s' > + var_entry_size = struct.calcsize(var_entry_fmt) > + > +class EfiVariable: > + def __init__(self, size, attrs, time, guid, name, data): > + self.size = size > + self.attrs = attrs > + self.time = time > + self.guid = guid > + self.name = name > + self.data = data > + > +class EfiVariableStore: > + def __init__(self, infile): > + self.infile = infile > + self.efi = EfiStruct() > + if os.path.exists(self.infile) and os.stat(self.infile).st_size > self.efi.var_file_size: > + with open(self.infile, 'rb') as f: > + buf = f.read() > + self._check_header(buf) > + self.ents = buf[self.efi.var_file_size:] > + else: > + self.ents = bytearray() > + > + def _check_header(self, buf): > + hdr = struct.unpack_from(self.efi.var_file_fmt, buf, 0) > + magic, crc32 = hdr[1], hdr[3] > + > + if magic != UBOOT_EFI_VAR_FILE_MAGIC: > + print("err: invalid magic number: %s"%hex(magic)) > + exit(1) > + if crc32 != zlib.crc32(buf[self.efi.var_file_size:]) & 0xffffffff: > + print("err: invalid crc32: %s"%hex(crc32)) > + exit(1) > + > + def _get_var_name(self, buf): > + name = '' > + for i in range(0, len(buf) - 1, 2): > + if not buf[i] and not buf[i+1]: > + break > + name += chr(buf[i]) > + return ''.join([chr(x) for x in name.encode('utf_16_le') if x]), i + 2 > + > + def _next_var(self, offs=0): > + size, attrs, time, guid = struct.unpack_from(self.efi.var_entry_fmt, self.ents, offs) > + data_fmt = str(size)+"s" > + offs += self.efi.var_entry_size > + name, namelen = self._get_var_name(self.ents[offs:]) > + offs += namelen > + data = struct.unpack_from(data_fmt, self.ents, offs)[0] > + offs = (offs + len(data) + 7) & ~7 > + return EfiVariable(size, attrs, time, uuid.UUID(bytes_le=guid), name, data), offs > + > + def __iter__(self): > + self.offs = 0 > + return self > + > + def __next__(self): > + if self.offs < len(self.ents): > + var, noffs = self._next_var(self.offs) > + self.offs = noffs > + return var > + else: > + raise StopIteration > + > + def __len__(self): > + return len(self.ents) > + > + def _set_var(self, guid, name_data, size, attrs, tsec): > + ent = struct.pack(self.efi.var_entry_fmt, > + size, > + attrs, > + tsec, > + uuid.UUID(guid).bytes_le) > + ent += name_data > + self.ents += ent > + > + def set_var(self, guid, name, data, size, attrs): > + offs = 0 > + while offs < len(self.ents): > + var, loffs = self._next_var(offs) > + if var.name == name and str(var.guid) == guid: > + if not data or not attrs: > + self.ents = self.ents[:offs] + self.ents[loffs:] > + return > + if var.attrs != attrs: > + print("err: invalid attributes") Thanks for adding deletion and modification of variables. print("attributes don't match") would better convey the meaning here. > + exit(1) > + # make room for updating var > + self.ents = self.ents[:offs] + self.ents[loffs:] > + break > + offs = loffs > + > + if not data or not attrs: > + print("err: variable not found") > + exit(1) > + > + tsec = int(time.time()) if attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS else 0 > + nd = name.encode('utf_16_le') + b"\x00\x00" + data > + # U-Boot variable format requires the name + data blob to be 8-byte aligned > + pad = ((len(nd) + 7) & ~7) - len(nd) > + nd += bytes([0] * pad) > + > + return self._set_var(guid, nd, size, attrs, tsec) > + > + def save(self): > + hdr = struct.pack(self.efi.var_file_fmt, > + 0, > + UBOOT_EFI_VAR_FILE_MAGIC, > + len(self.ents) + self.efi.var_file_size, > + zlib.crc32(self.ents) & 0xffffffff) > + > + with open(self.infile, 'wb') as f: > + f.write(hdr) > + f.write(self.ents) > + > +def parse_attrs(attrs): > + v = 0 > + if attrs: > + for i in attrs.split(','): > + v |= var_attrs[i.upper()] > + return v > + > +def parse_data(val, vtype): > + if not val or not vtype: > + return None, 0 > + fmt = { 'u8': '<B', 'u16': '<H', 'u32': '<L', 'u64': '<Q' } > + if vtype.lower() == 'file': > + with open(val, 'rb') as f: > + data = f.read() > + return data, len(data) > + if vtype.lower() == 'str': > + data = val.encode('utf-8') > + return data, len(data) > + i = fmt[vtype.lower()] > + return struct.pack(i, int(val)), struct.calcsize(i) > + > +def cmd_set(args): > + env = EfiVariableStore(args.infile) > + data, size = parse_data(args.data, args.type) > + > + if args.name.lower() == 'pk': > + env.set_var(guid=EFI_GLOBAL_VARIABLE_GUID, name='PK', data=data, size=size, attrs=NV_BS_RT_AT) > + elif args.name.lower() == 'kek': > + env.set_var(guid=EFI_GLOBAL_VARIABLE_GUID, name='KEK', data=data, size=size, attrs=NV_BS_RT_AT) > + elif args.name.lower() == 'db': > + env.set_var(guid=EFI_IMAGE_SECURITY_DATABASE_GUID, name='db', data=data, size=size, attrs=NV_BS_RT_AT) > + elif args.name.lower() == 'dbx': > + env.set_var(guid=EFI_IMAGE_SECURITY_DATABASE_GUID, name='dbx', data=data, size=size, attrs=NV_BS_RT_AT) > + else: > + guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID > + attrs = parse_attrs(args.attrs) > + env.set_var(guid=guid, name=args.name, data=data, size=size, attrs=attrs) > + > + env.save() > + > +def print_var(var): > + print(var.name+':') > + print(" "+str(var.guid)+' '+''.join([x for x in var_guids if str(var.guid) == var_guids[x]])) > + print(" "+'|'.join([x for x in var_attrs if var.attrs & var_attrs[x]])+", DataSize = %s"%hex(var.size)) > + hexdump(var.data) > + > +def cmd_print(args): > + env = EfiVariableStore(args.infile) > + if not args.name and not args.guid and not len(env): > + return > + > + found = False > + for var in env: > + if not args.name: > + if args.guid and args.guid != str(var.guid): > + continue > + print_var(var) > + found = True > + else: > + if args.name != var.name or (args.guid and args.guid != str(var.guid)): > + continue > + print_var(var) > + found = True > + > + if not found: > + print("err: variable not found") > + exit(1) > + > +def main(): > + ap = argparse.ArgumentParser(description='Generate U-Boot variable store') > + ap.add_argument('--infile', '-i', required=True, help='file to save the UEFI variables') > + subp = ap.add_subparsers(help="sub-command help") > + > + printp = subp.add_parser('print', help='get/list UEFI variables') > + printp.add_argument('--guid', '-g', help='variable guid') > + printp.add_argument('--name', '-n', help='variable name') > + printp.set_defaults(func=cmd_print) > + > + setp = subp.add_parser('set', help='set UEFI variable') > + setp.add_argument('--name', '-n', required=True, help='variable name') > + setp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)') The online help should indicate how deletion works. I find it problematic that if a user forgets to specify -a, the variable is deleted inadvertently. PK, KEK, db, dbx cannot be deleted at all. Please, use a separate sub-command for deletion and reintroduce the default for the attributes as nv,bs,rt which seems more useful than nv,bs. > + setp.add_argument('--guid', '-g', help="variable guid (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) %s/variable guid/vendor GUID/ > + setp.add_argument('--type', '-t', help='variable type (values: file|u8|u16|u32|u64|str)') > + setp.add_argument('--data', '-d', help='variable data') help='data or filename' Best regards Heinrich > + setp.set_defaults(func=cmd_set) > + > + args = ap.parse_args() > + args.func(args) > + > +def group(a, *ns): > + for n in ns: > + a = [a[i:i+n] for i in range(0, len(a), n)] > + return a > + > +def join(a, *cs): > + return [cs[0].join(join(t, *cs[1:])) for t in a] if cs else a > + > +def hexdump(data): > + toHex = lambda c: '{:02X}'.format(c) > + toChr = lambda c: chr(c) if 32 <= c < 127 else '.' > + make = lambda f, *cs: join(group(list(map(f, data)), 8, 2), *cs) > + hs = make(toHex, ' ', ' ') > + cs = make(toChr, ' ', '') > + for i, (h, c) in enumerate(zip(hs, cs)): > + print (' {:010X}: {:48} {:16}'.format(i * 16, h, c)) > + > +if __name__ == '__main__': > + main() > ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3] tools: add a simple script to generate EFI variables 2020-11-30 15:16 [PATCH] tools: add a simple script to generate EFI variables Paulo Alcantara 2020-11-30 16:38 ` Heinrich Schuchardt 2020-12-01 22:58 ` [PATCH v2] " Paulo Alcantara @ 2020-12-02 16:46 ` Paulo Alcantara 2020-12-08 23:10 ` [PATCH v4] " Paulo Alcantara 2 siblings, 1 reply; 9+ messages in thread From: Paulo Alcantara @ 2020-12-02 16:46 UTC (permalink / raw) To: u-boot This script generates EFI variables for U-Boot variable store format. A few examples: - Generating secure boot keys $ openssl x509 -in foo.crt -outform DER -out foo.der $ efisiglist -a -c foo.der -o foo.esl $ ./efivar.py -i ubootefi.var set -n db -d foo.esl -t file $ ./efivar.py -i ubootefi.var set -n kek -d foo.esl -t file $ ./efivar.py -i ubootefi.var set -n pk -d foo.esl -t file - Printing out variables $ ./efivar.py -i ubootefi.var set -n var1 -d foo -t str $ ./efivar.py -i ubootefi.var set -n var2 -d bar -t str $ ./efivar.py -i ubootefi.var print var1: 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID NV|BS|RT, DataSize = 0x3 0000000000: 66 6F 6F foo var2: 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID NV|BS|RT, DataSize = 0x3 0000000000: 62 61 72 bar - Removing variables $ ./efivar.py -i ubootefi.var set -n var1 -a nv,bs -d foo -t str $ ./efivar.py -i ubootefi.var print -n var1 var1: 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID NV|BS, DataSize = 0x3 0000000000: 66 6F 6F foo $ ./efivar.py -i ubootefi.var del -n var1 err: attributes don't match $ ./efivar.py -i ubootefi.var del -n var1 -a nv,bs $ ./efivar.py -i ubootefi.var print -n var1 err: variable not found Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz> --- tools/efivar.py | 306 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100755 tools/efivar.py diff --git a/tools/efivar.py b/tools/efivar.py new file mode 100755 index 000000000000..75fbce09b555 --- /dev/null +++ b/tools/efivar.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +## SPDX-License-Identifier: GPL-2.0-only +# +# Generate UEFI variables for U-Boot. +# +# (c) 2020 Paulo Alcantara <palcantara@suse.de> +# + +import os +import struct +import uuid +import time +import zlib +import argparse + +# U-Boot variable store format (version 1) +UBOOT_EFI_VAR_FILE_MAGIC = 0x0161566966456255 + +# UEFI variable attributes +EFI_VARIABLE_NON_VOLATILE = 0x1 +EFI_VARIABLE_BOOTSERVICE_ACCESS = 0x2 +EFI_VARIABLE_RUNTIME_ACCESS = 0x4 +EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS = 0x10 +EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x20 +EFI_VARIABLE_READ_ONLY = 1 << 31 +NV_BS = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS +NV_BS_RT = NV_BS | EFI_VARIABLE_RUNTIME_ACCESS +NV_BS_RT_AT = NV_BS_RT | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS +DEFAULT_VAR_ATTRS = NV_BS_RT + +# vendor GUIDs +EFI_GLOBAL_VARIABLE_GUID = '8be4df61-93ca-11d2-aa0d-00e098032b8c' +EFI_IMAGE_SECURITY_DATABASE_GUID = 'd719b2cb-3d3a-4596-a3bc-dad00e67656f' + +var_attrs = { + 'NV': EFI_VARIABLE_NON_VOLATILE, + 'BS': EFI_VARIABLE_BOOTSERVICE_ACCESS, + 'RT': EFI_VARIABLE_RUNTIME_ACCESS, + 'AT': EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, + 'RO': EFI_VARIABLE_READ_ONLY, + 'AW': EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS, +} + +var_guids = { + 'EFI_GLOBAL_VARIABLE_GUID': EFI_GLOBAL_VARIABLE_GUID, + 'EFI_IMAGE_SECURITY_DATABASE_GUID': EFI_IMAGE_SECURITY_DATABASE_GUID, +} + +class EfiStruct: + # struct efi_var_file + var_file_fmt = '<QQLL' + var_file_size = struct.calcsize(var_file_fmt) + # struct efi_var_entry + var_entry_fmt = '<LLQ16s' + var_entry_size = struct.calcsize(var_entry_fmt) + +class EfiVariable: + def __init__(self, size, attrs, time, guid, name, data): + self.size = size + self.attrs = attrs + self.time = time + self.guid = guid + self.name = name + self.data = data + +def calc_crc32(buf): + return zlib.crc32(buf) & 0xffffffff + +class EfiVariableStore: + def __init__(self, infile): + self.infile = infile + self.efi = EfiStruct() + if os.path.exists(self.infile) and os.stat(self.infile).st_size > self.efi.var_file_size: + with open(self.infile, 'rb') as f: + buf = f.read() + self._check_header(buf) + self.ents = buf[self.efi.var_file_size:] + else: + self.ents = bytearray() + + def _check_header(self, buf): + hdr = struct.unpack_from(self.efi.var_file_fmt, buf, 0) + magic, crc32 = hdr[1], hdr[3] + + if magic != UBOOT_EFI_VAR_FILE_MAGIC: + print("err: invalid magic number: %s"%hex(magic)) + exit(1) + if crc32 != calc_crc32(buf[self.efi.var_file_size:]): + print("err: invalid crc32: %s"%hex(crc32)) + exit(1) + + def _get_var_name(self, buf): + name = '' + for i in range(0, len(buf) - 1, 2): + if not buf[i] and not buf[i+1]: + break + name += chr(buf[i]) + return ''.join([chr(x) for x in name.encode('utf_16_le') if x]), i + 2 + + def _next_var(self, offs=0): + size, attrs, time, guid = struct.unpack_from(self.efi.var_entry_fmt, self.ents, offs) + data_fmt = str(size)+"s" + offs += self.efi.var_entry_size + name, namelen = self._get_var_name(self.ents[offs:]) + offs += namelen + data = struct.unpack_from(data_fmt, self.ents, offs)[0] + # offset to next 8-byte aligned variable entry + offs = (offs + len(data) + 7) & ~7 + return EfiVariable(size, attrs, time, uuid.UUID(bytes_le=guid), name, data), offs + + def __iter__(self): + self.offs = 0 + return self + + def __next__(self): + if self.offs < len(self.ents): + var, noffs = self._next_var(self.offs) + self.offs = noffs + return var + else: + raise StopIteration + + def __len__(self): + return len(self.ents) + + def _set_var(self, guid, name_data, size, attrs, tsec): + ent = struct.pack(self.efi.var_entry_fmt, + size, + attrs, + tsec, + uuid.UUID(guid).bytes_le) + ent += name_data + self.ents += ent + + def del_var(self, guid, name, attrs): + offs = 0 + while offs < len(self.ents): + var, loffs = self._next_var(offs) + if var.name == name and str(var.guid): + if var.attrs != attrs: + print("err: attributes don't match") + exit(1) + self.ents = self.ents[:offs] + self.ents[loffs:] + return + offs = loffs + print("err: variable not found") + exit(1) + + def set_var(self, guid, name, data, size, attrs): + offs = 0 + while offs < len(self.ents): + var, loffs = self._next_var(offs) + if var.name == name and str(var.guid) == guid: + if var.attrs != attrs: + print("err: attributes don't match") + exit(1) + # make room for updating var + self.ents = self.ents[:offs] + self.ents[loffs:] + break + offs = loffs + + tsec = int(time.time()) if attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS else 0 + nd = name.encode('utf_16_le') + b"\x00\x00" + data + # U-Boot variable format requires the name + data blob to be 8-byte aligned + pad = ((len(nd) + 7) & ~7) - len(nd) + nd += bytes([0] * pad) + + return self._set_var(guid, nd, size, attrs, tsec) + + def save(self): + hdr = struct.pack(self.efi.var_file_fmt, + 0, + UBOOT_EFI_VAR_FILE_MAGIC, + len(self.ents) + self.efi.var_file_size, + calc_crc32(self.ents)) + + with open(self.infile, 'wb') as f: + f.write(hdr) + f.write(self.ents) + +def parse_attrs(attrs): + v = DEFAULT_VAR_ATTRS + if attrs: + v = 0 + for i in attrs.split(','): + v |= var_attrs[i.upper()] + return v + +def parse_data(val, vtype): + if not val or not vtype: + return None, 0 + fmt = { 'u8': '<B', 'u16': '<H', 'u32': '<L', 'u64': '<Q' } + if vtype.lower() == 'file': + with open(val, 'rb') as f: + data = f.read() + return data, len(data) + if vtype.lower() == 'str': + data = val.encode('utf-8') + return data, len(data) + i = fmt[vtype.lower()] + return struct.pack(i, int(val)), struct.calcsize(i) + +def parse_set_args(args): + name = args.name + attrs = parse_attrs(args.attrs) + guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID + + if name.lower() == 'db' or name.lower() == 'dbx': + name = name.lower() + guid = EFI_IMAGE_SECURITY_DATABASE_GUID + attrs = NV_BS_RT_AT + elif name.lower() == 'pk' or name.lower() == 'kek': + name = name.upper() + guid = EFI_GLOBAL_VARIABLE_GUID + attrs = NV_BS_RT_AT + + data, size = parse_data(args.data, args.type) + return guid, name, attrs, data, size + +def cmd_set(args): + env = EfiVariableStore(args.infile) + guid, name, attrs, data, size = parse_set_args(args) + env.set_var(guid=guid, name=name, data=data, size=size, attrs=attrs) + env.save() + +def print_var(var): + print(var.name+':') + print(" "+str(var.guid)+' '+''.join([x for x in var_guids if str(var.guid) == var_guids[x]])) + print(" "+'|'.join([x for x in var_attrs if var.attrs & var_attrs[x]])+", DataSize = %s"%hex(var.size)) + hexdump(var.data) + +def cmd_print(args): + env = EfiVariableStore(args.infile) + if not args.name and not args.guid and not len(env): + return + + found = False + for var in env: + if not args.name: + if args.guid and args.guid != str(var.guid): + continue + print_var(var) + found = True + else: + if args.name != var.name or (args.guid and args.guid != str(var.guid)): + continue + print_var(var) + found = True + + if not found: + print("err: variable not found") + exit(1) + +def cmd_del(args): + env = EfiVariableStore(args.infile) + attrs = parse_attrs(args.attrs) + guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID + env.del_var(guid, args.name, attrs) + env.save() + +def main(): + ap = argparse.ArgumentParser(description='Generate U-Boot variable store') + ap.add_argument('--infile', '-i', required=True, help='file to save the UEFI variables') + subp = ap.add_subparsers(help="sub-command help") + + printp = subp.add_parser('print', help='get/list UEFI variables') + printp.add_argument('--name', '-n', help='variable name') + printp.add_argument('--guid', '-g', help='vendor GUID') + printp.set_defaults(func=cmd_print) + + setp = subp.add_parser('set', help='set UEFI variable') + setp.add_argument('--name', '-n', required=True, help='variable name') + setp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)') + setp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) + setp.add_argument('--type', '-t', help='variable type (values: file|u8|u16|u32|u64|str)') + setp.add_argument('--data', '-d', help='data or filename') + setp.set_defaults(func=cmd_set) + + delp = subp.add_parser('del', help='delete UEFI variable') + delp.add_argument('--name', '-n', required=True, help='variable name') + delp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)') + delp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) + delp.set_defaults(func=cmd_del) + + args = ap.parse_args() + args.func(args) + +def group(a, *ns): + for n in ns: + a = [a[i:i+n] for i in range(0, len(a), n)] + return a + +def join(a, *cs): + return [cs[0].join(join(t, *cs[1:])) for t in a] if cs else a + +def hexdump(data): + toHex = lambda c: '{:02X}'.format(c) + toChr = lambda c: chr(c) if 32 <= c < 127 else '.' + make = lambda f, *cs: join(group(list(map(f, data)), 8, 2), *cs) + hs = make(toHex, ' ', ' ') + cs = make(toChr, ' ', '') + for i, (h, c) in enumerate(zip(hs, cs)): + print (' {:010X}: {:48} {:16}'.format(i * 16, h, c)) + +if __name__ == '__main__': + main() -- 2.29.2 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v4] tools: add a simple script to generate EFI variables 2020-12-02 16:46 ` [PATCH v3] " Paulo Alcantara @ 2020-12-08 23:10 ` Paulo Alcantara 2020-12-20 10:52 ` Heinrich Schuchardt 0 siblings, 1 reply; 9+ messages in thread From: Paulo Alcantara @ 2020-12-08 23:10 UTC (permalink / raw) To: u-boot This script generates EFI variables for U-Boot variable store format. A few examples: - Generating secure boot keys $ openssl x509 -in foo.crt -outform DER -out foo.der $ efisiglist -a -c foo.der -o foo.esl $ ./efivar.py -i ubootefi.var set -n db -d foo.esl -t file $ ./efivar.py -i ubootefi.var set -n kek -d foo.esl -t file $ ./efivar.py -i ubootefi.var set -n pk -d foo.esl -t file - Printing out variables $ ./efivar.py -i ubootefi.var set -n var1 -d foo -t str $ ./efivar.py -i ubootefi.var set -n var2 -d bar -t str $ ./efivar.py -i ubootefi.var print var1: 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID NV|BS|RT, DataSize = 0x3 0000000000: 66 6F 6F foo var2: 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID NV|BS|RT, DataSize = 0x3 0000000000: 62 61 72 bar - Removing variables $ ./efivar.py -i ubootefi.var set -n var1 -a nv,bs -d foo -t str $ ./efivar.py -i ubootefi.var print -n var1 var1: 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID NV|BS, DataSize = 0x3 0000000000: 66 6F 6F foo $ ./efivar.py -i ubootefi.var del -n var1 err: attributes don't match $ ./efivar.py -i ubootefi.var del -n var1 -a nv,bs $ ./efivar.py -i ubootefi.var print -n var1 err: variable not found Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz> --- v4: add 'sign' command for authenticated EFI payloads --- tools/efivar.py | 380 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100755 tools/efivar.py diff --git a/tools/efivar.py b/tools/efivar.py new file mode 100755 index 000000000000..ebfcab2f0a2c --- /dev/null +++ b/tools/efivar.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python3 +## SPDX-License-Identifier: GPL-2.0-only +# +# EFI variable store utilities. +# +# (c) 2020 Paulo Alcantara <palcantara@suse.de> +# + +import os +import struct +import uuid +import time +import zlib +import argparse +from OpenSSL import crypto + +# U-Boot variable store format (version 1) +UBOOT_EFI_VAR_FILE_MAGIC = 0x0161566966456255 + +# UEFI variable attributes +EFI_VARIABLE_NON_VOLATILE = 0x1 +EFI_VARIABLE_BOOTSERVICE_ACCESS = 0x2 +EFI_VARIABLE_RUNTIME_ACCESS = 0x4 +EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS = 0x10 +EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x20 +EFI_VARIABLE_READ_ONLY = 1 << 31 +NV_BS = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS +NV_BS_RT = NV_BS | EFI_VARIABLE_RUNTIME_ACCESS +NV_BS_RT_AT = NV_BS_RT | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS +DEFAULT_VAR_ATTRS = NV_BS_RT + +# vendor GUIDs +EFI_GLOBAL_VARIABLE_GUID = '8be4df61-93ca-11d2-aa0d-00e098032b8c' +EFI_IMAGE_SECURITY_DATABASE_GUID = 'd719b2cb-3d3a-4596-a3bc-dad00e67656f' +EFI_CERT_TYPE_PKCS7_GUID = '4aafd29d-68df-49ee-8aa9-347d375665a7' +WIN_CERT_TYPE_EFI_GUID = 0x0ef1 +WIN_CERT_REVISION = 0x0200 + +var_attrs = { + 'NV': EFI_VARIABLE_NON_VOLATILE, + 'BS': EFI_VARIABLE_BOOTSERVICE_ACCESS, + 'RT': EFI_VARIABLE_RUNTIME_ACCESS, + 'AT': EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, + 'RO': EFI_VARIABLE_READ_ONLY, + 'AW': EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS, +} + +var_guids = { + 'EFI_GLOBAL_VARIABLE_GUID': EFI_GLOBAL_VARIABLE_GUID, + 'EFI_IMAGE_SECURITY_DATABASE_GUID': EFI_IMAGE_SECURITY_DATABASE_GUID, +} + +class EfiStruct: + # struct efi_var_file + var_file_fmt = '<QQLL' + var_file_size = struct.calcsize(var_file_fmt) + # struct efi_var_entry + var_entry_fmt = '<LLQ16s' + var_entry_size = struct.calcsize(var_entry_fmt) + # struct efi_time + var_time_fmt = '<H6BLh2B' + var_time_size = struct.calcsize(var_time_fmt) + # WIN_CERTIFICATE + var_win_cert_fmt = '<L2H' + var_win_cert_size = struct.calcsize(var_win_cert_fmt) + # WIN_CERTIFICATE_UEFI_GUID + var_win_cert_uefi_guid_fmt = var_win_cert_fmt+'16s' + var_win_cert_uefi_guid_size = struct.calcsize(var_win_cert_uefi_guid_fmt) + +class EfiVariable: + def __init__(self, size, attrs, time, guid, name, data): + self.size = size + self.attrs = attrs + self.time = time + self.guid = guid + self.name = name + self.data = data + +def calc_crc32(buf): + return zlib.crc32(buf) & 0xffffffff + +class EfiVariableStore: + def __init__(self, infile): + self.infile = infile + self.efi = EfiStruct() + if os.path.exists(self.infile) and os.stat(self.infile).st_size > self.efi.var_file_size: + with open(self.infile, 'rb') as f: + buf = f.read() + self._check_header(buf) + self.ents = buf[self.efi.var_file_size:] + else: + self.ents = bytearray() + + def _check_header(self, buf): + hdr = struct.unpack_from(self.efi.var_file_fmt, buf, 0) + magic, crc32 = hdr[1], hdr[3] + + if magic != UBOOT_EFI_VAR_FILE_MAGIC: + print("err: invalid magic number: %s"%hex(magic)) + exit(1) + if crc32 != calc_crc32(buf[self.efi.var_file_size:]): + print("err: invalid crc32: %s"%hex(crc32)) + exit(1) + + def _get_var_name(self, buf): + name = '' + for i in range(0, len(buf) - 1, 2): + if not buf[i] and not buf[i+1]: + break + name += chr(buf[i]) + return ''.join([chr(x) for x in name.encode('utf_16_le') if x]), i + 2 + + def _next_var(self, offs=0): + size, attrs, time, guid = struct.unpack_from(self.efi.var_entry_fmt, self.ents, offs) + data_fmt = str(size)+"s" + offs += self.efi.var_entry_size + name, namelen = self._get_var_name(self.ents[offs:]) + offs += namelen + data = struct.unpack_from(data_fmt, self.ents, offs)[0] + # offset to next 8-byte aligned variable entry + offs = (offs + len(data) + 7) & ~7 + return EfiVariable(size, attrs, time, uuid.UUID(bytes_le=guid), name, data), offs + + def __iter__(self): + self.offs = 0 + return self + + def __next__(self): + if self.offs < len(self.ents): + var, noffs = self._next_var(self.offs) + self.offs = noffs + return var + else: + raise StopIteration + + def __len__(self): + return len(self.ents) + + def _set_var(self, guid, name_data, size, attrs, tsec): + ent = struct.pack(self.efi.var_entry_fmt, + size, + attrs, + tsec, + uuid.UUID(guid).bytes_le) + ent += name_data + self.ents += ent + + def del_var(self, guid, name, attrs): + offs = 0 + while offs < len(self.ents): + var, loffs = self._next_var(offs) + if var.name == name and str(var.guid): + if var.attrs != attrs: + print("err: attributes don't match") + exit(1) + self.ents = self.ents[:offs] + self.ents[loffs:] + return + offs = loffs + print("err: variable not found") + exit(1) + + def set_var(self, guid, name, data, size, attrs): + offs = 0 + while offs < len(self.ents): + var, loffs = self._next_var(offs) + if var.name == name and str(var.guid) == guid: + if var.attrs != attrs: + print("err: attributes don't match") + exit(1) + # make room for updating var + self.ents = self.ents[:offs] + self.ents[loffs:] + break + offs = loffs + + tsec = int(time.time()) if attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS else 0 + nd = name.encode('utf_16_le') + b"\x00\x00" + data + # U-Boot variable format requires the name + data blob to be 8-byte aligned + pad = ((len(nd) + 7) & ~7) - len(nd) + nd += bytes([0] * pad) + + return self._set_var(guid, nd, size, attrs, tsec) + + def save(self): + hdr = struct.pack(self.efi.var_file_fmt, + 0, + UBOOT_EFI_VAR_FILE_MAGIC, + len(self.ents) + self.efi.var_file_size, + calc_crc32(self.ents)) + + with open(self.infile, 'wb') as f: + f.write(hdr) + f.write(self.ents) + +def parse_attrs(attrs): + v = DEFAULT_VAR_ATTRS + if attrs: + v = 0 + for i in attrs.split(','): + v |= var_attrs[i.upper()] + return v + +def parse_data(val, vtype): + if not val or not vtype: + return None, 0 + fmt = { 'u8': '<B', 'u16': '<H', 'u32': '<L', 'u64': '<Q' } + if vtype.lower() == 'file': + with open(val, 'rb') as f: + data = f.read() + return data, len(data) + if vtype.lower() == 'str': + data = val.encode('utf-8') + return data, len(data) + if vtype.lower() == 'nil': + return None, 0 + i = fmt[vtype.lower()] + return struct.pack(i, int(val)), struct.calcsize(i) + +def parse_args(args): + name = args.name + attrs = parse_attrs(args.attrs) + guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID + + if name.lower() == 'db' or name.lower() == 'dbx': + name = name.lower() + guid = EFI_IMAGE_SECURITY_DATABASE_GUID + attrs = NV_BS_RT_AT + elif name.lower() == 'pk' or name.lower() == 'kek': + name = name.upper() + guid = EFI_GLOBAL_VARIABLE_GUID + attrs = NV_BS_RT_AT + + data, size = parse_data(args.data, args.type) + return guid, name, attrs, data, size + +def cmd_set(args): + env = EfiVariableStore(args.infile) + guid, name, attrs, data, size = parse_args(args) + env.set_var(guid=guid, name=name, data=data, size=size, attrs=attrs) + env.save() + +def print_var(var): + print(var.name+':') + print(" "+str(var.guid)+' '+''.join([x for x in var_guids if str(var.guid) == var_guids[x]])) + print(" "+'|'.join([x for x in var_attrs if var.attrs & var_attrs[x]])+", DataSize = %s"%hex(var.size)) + hexdump(var.data) + +def cmd_print(args): + env = EfiVariableStore(args.infile) + if not args.name and not args.guid and not len(env): + return + + found = False + for var in env: + if not args.name: + if args.guid and args.guid != str(var.guid): + continue + print_var(var) + found = True + else: + if args.name != var.name or (args.guid and args.guid != str(var.guid)): + continue + print_var(var) + found = True + + if not found: + print("err: variable not found") + exit(1) + +def cmd_del(args): + env = EfiVariableStore(args.infile) + attrs = parse_attrs(args.attrs) + guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID + env.del_var(guid, args.name, attrs) + env.save() + +def pkcs7_sign(cert, key, buf): + with open(cert, 'r') as f: + crt = crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) + with open(key, 'r') as f: + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read()) + + PKCS7_BINARY = 0x80 + PKCS7_DETACHED = 0x40 + PKCS7_NOATTR = 0x100 + + bio_in = crypto._new_mem_buf(buf) + p7 = crypto._lib.PKCS7_sign(crt._x509, pkey._pkey, crypto._ffi.NULL, bio_in, + PKCS7_BINARY|PKCS7_DETACHED|PKCS7_NOATTR) + bio_out = crypto._new_mem_buf() + crypto._lib.i2d_PKCS7_bio(bio_out, p7) + return crypto._bio_to_string(bio_out) + +# UEFI 2.8 Errata B "8.2.2 Using the EFI_VARIABLE_AUTHENTICATION_2 descriptor" +def cmd_sign(args): + guid, name, attrs, data, size = parse_args(args) + attrs |= EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS + efi = EfiStruct() + + tm = time.localtime() + etime = struct.pack(efi.var_time_fmt, + tm.tm_year, tm.tm_mon, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + 0, 0, 0, 0, 0) + + buf = name.encode('utf_16_le') + uuid.UUID(guid).bytes_le + attrs.to_bytes(4, byteorder='little') + etime + if data: + buf += data + sig = pkcs7_sign(args.cert, args.key, buf) + + desc = struct.pack(efi.var_win_cert_uefi_guid_fmt, + efi.var_win_cert_uefi_guid_size + len(sig), + WIN_CERT_REVISION, + WIN_CERT_TYPE_EFI_GUID, + uuid.UUID(EFI_CERT_TYPE_PKCS7_GUID).bytes_le) + + with open(args.outfile, 'wb') as f: + if data: + f.write(etime + desc + sig + data) + else: + f.write(etime + desc + sig) + +def main(): + ap = argparse.ArgumentParser(description='EFI variable store utilities') + subp = ap.add_subparsers(help="sub-command help") + + printp = subp.add_parser('print', help='get/list EFI variables') + printp.add_argument('--infile', '-i', required=True, help='file to save the EFI variables') + printp.add_argument('--name', '-n', help='variable name') + printp.add_argument('--guid', '-g', help='vendor GUID') + printp.set_defaults(func=cmd_print) + + setp = subp.add_parser('set', help='set EFI variable') + setp.add_argument('--infile', '-i', required=True, help='file to save the EFI variables') + setp.add_argument('--name', '-n', required=True, help='variable name') + setp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)') + setp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) + setp.add_argument('--type', '-t', help='variable type (values: file|u8|u16|u32|u64|str)') + setp.add_argument('--data', '-d', help='data or filename') + setp.set_defaults(func=cmd_set) + + delp = subp.add_parser('del', help='delete EFI variable') + delp.add_argument('--infile', '-i', required=True, help='file to save the EFI variables') + delp.add_argument('--name', '-n', required=True, help='variable name') + delp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)') + delp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) + delp.set_defaults(func=cmd_del) + + signp = subp.add_parser('sign', help='sign time-based EFI payload') + signp.add_argument('--cert', '-c', required=True, help='x509 certificate filename in PEM format') + signp.add_argument('--key', '-k', required=True, help='signing certificate filename in PEM format') + signp.add_argument('--name', '-n', required=True, help='variable name') + signp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)') + signp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) + signp.add_argument('--type', '-t', required=True, help='variable type (values: file|u8|u16|u32|u64|str|nil)') + signp.add_argument('--data', '-d', help='data or filename') + signp.add_argument('--outfile', '-o', required=True, help='output filename of signed EFI payload') + signp.set_defaults(func=cmd_sign) + + args = ap.parse_args() + args.func(args) + +def group(a, *ns): + for n in ns: + a = [a[i:i+n] for i in range(0, len(a), n)] + return a + +def join(a, *cs): + return [cs[0].join(join(t, *cs[1:])) for t in a] if cs else a + +def hexdump(data): + toHex = lambda c: '{:02X}'.format(c) + toChr = lambda c: chr(c) if 32 <= c < 127 else '.' + make = lambda f, *cs: join(group(list(map(f, data)), 8, 2), *cs) + hs = make(toHex, ' ', ' ') + cs = make(toChr, ' ', '') + for i, (h, c) in enumerate(zip(hs, cs)): + print (' {:010X}: {:48} {:16}'.format(i * 16, h, c)) + +if __name__ == '__main__': + main() -- 2.29.2 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v4] tools: add a simple script to generate EFI variables 2020-12-08 23:10 ` [PATCH v4] " Paulo Alcantara @ 2020-12-20 10:52 ` Heinrich Schuchardt 2020-12-22 13:35 ` Paulo Alcantara 0 siblings, 1 reply; 9+ messages in thread From: Heinrich Schuchardt @ 2020-12-20 10:52 UTC (permalink / raw) To: u-boot On 12/9/20 12:10 AM, Paulo Alcantara wrote: > This script generates EFI variables for U-Boot variable store format. > > A few examples: > > - Generating secure boot keys > > $ openssl x509 -in foo.crt -outform DER -out foo.der > $ efisiglist -a -c foo.der -o foo.esl > $ ./efivar.py -i ubootefi.var set -n db -d foo.esl -t file > $ ./efivar.py -i ubootefi.var set -n kek -d foo.esl -t file efivar.py requires that 'set' is the first parameter. I will fix the commit message. Could you, please, provide a man-page for the tool as restructured text in doc/usage/. Please, check that it is correct with 'make htmldocs'. Best regards Heinrich > $ ./efivar.py -i ubootefi.var set -n pk -d foo.esl -t file > > - Printing out variables > > $ ./efivar.py -i ubootefi.var set -n var1 -d foo -t str > $ ./efivar.py -i ubootefi.var set -n var2 -d bar -t str > $ ./efivar.py -i ubootefi.var print > var1: > 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID > NV|BS|RT, DataSize = 0x3 > 0000000000: 66 6F 6F foo > var2: > 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID > NV|BS|RT, DataSize = 0x3 > 0000000000: 62 61 72 bar > > - Removing variables > > $ ./efivar.py -i ubootefi.var set -n var1 -a nv,bs -d foo -t str > $ ./efivar.py -i ubootefi.var print -n var1 > var1: > 8be4df61-93ca-11d2-aa0d-00e098032b8c EFI_GLOBAL_VARIABLE_GUID > NV|BS, DataSize = 0x3 > 0000000000: 66 6F 6F foo > $ ./efivar.py -i ubootefi.var del -n var1 > err: attributes don't match > $ ./efivar.py -i ubootefi.var del -n var1 -a nv,bs > $ ./efivar.py -i ubootefi.var print -n var1 > err: variable not found > > Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz> > --- > v4: add 'sign' command for authenticated EFI payloads > --- > tools/efivar.py | 380 ++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 380 insertions(+) > create mode 100755 tools/efivar.py > > diff --git a/tools/efivar.py b/tools/efivar.py > new file mode 100755 > index 000000000000..ebfcab2f0a2c > --- /dev/null > +++ b/tools/efivar.py > @@ -0,0 +1,380 @@ > +#!/usr/bin/env python3 > +## SPDX-License-Identifier: GPL-2.0-only > +# > +# EFI variable store utilities. > +# > +# (c) 2020 Paulo Alcantara <palcantara@suse.de> > +# > + > +import os > +import struct > +import uuid > +import time > +import zlib > +import argparse > +from OpenSSL import crypto > + > +# U-Boot variable store format (version 1) > +UBOOT_EFI_VAR_FILE_MAGIC = 0x0161566966456255 > + > +# UEFI variable attributes > +EFI_VARIABLE_NON_VOLATILE = 0x1 > +EFI_VARIABLE_BOOTSERVICE_ACCESS = 0x2 > +EFI_VARIABLE_RUNTIME_ACCESS = 0x4 > +EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS = 0x10 > +EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x20 > +EFI_VARIABLE_READ_ONLY = 1 << 31 > +NV_BS = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS > +NV_BS_RT = NV_BS | EFI_VARIABLE_RUNTIME_ACCESS > +NV_BS_RT_AT = NV_BS_RT | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS > +DEFAULT_VAR_ATTRS = NV_BS_RT > + > +# vendor GUIDs > +EFI_GLOBAL_VARIABLE_GUID = '8be4df61-93ca-11d2-aa0d-00e098032b8c' > +EFI_IMAGE_SECURITY_DATABASE_GUID = 'd719b2cb-3d3a-4596-a3bc-dad00e67656f' > +EFI_CERT_TYPE_PKCS7_GUID = '4aafd29d-68df-49ee-8aa9-347d375665a7' > +WIN_CERT_TYPE_EFI_GUID = 0x0ef1 > +WIN_CERT_REVISION = 0x0200 > + > +var_attrs = { > + 'NV': EFI_VARIABLE_NON_VOLATILE, > + 'BS': EFI_VARIABLE_BOOTSERVICE_ACCESS, > + 'RT': EFI_VARIABLE_RUNTIME_ACCESS, > + 'AT': EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, > + 'RO': EFI_VARIABLE_READ_ONLY, > + 'AW': EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS, > +} > + > +var_guids = { > + 'EFI_GLOBAL_VARIABLE_GUID': EFI_GLOBAL_VARIABLE_GUID, > + 'EFI_IMAGE_SECURITY_DATABASE_GUID': EFI_IMAGE_SECURITY_DATABASE_GUID, > +} > + > +class EfiStruct: > + # struct efi_var_file > + var_file_fmt = '<QQLL' > + var_file_size = struct.calcsize(var_file_fmt) > + # struct efi_var_entry > + var_entry_fmt = '<LLQ16s' > + var_entry_size = struct.calcsize(var_entry_fmt) > + # struct efi_time > + var_time_fmt = '<H6BLh2B' > + var_time_size = struct.calcsize(var_time_fmt) > + # WIN_CERTIFICATE > + var_win_cert_fmt = '<L2H' > + var_win_cert_size = struct.calcsize(var_win_cert_fmt) > + # WIN_CERTIFICATE_UEFI_GUID > + var_win_cert_uefi_guid_fmt = var_win_cert_fmt+'16s' > + var_win_cert_uefi_guid_size = struct.calcsize(var_win_cert_uefi_guid_fmt) > + > +class EfiVariable: > + def __init__(self, size, attrs, time, guid, name, data): > + self.size = size > + self.attrs = attrs > + self.time = time > + self.guid = guid > + self.name = name > + self.data = data > + > +def calc_crc32(buf): > + return zlib.crc32(buf) & 0xffffffff > + > +class EfiVariableStore: > + def __init__(self, infile): > + self.infile = infile > + self.efi = EfiStruct() > + if os.path.exists(self.infile) and os.stat(self.infile).st_size > self.efi.var_file_size: > + with open(self.infile, 'rb') as f: > + buf = f.read() > + self._check_header(buf) > + self.ents = buf[self.efi.var_file_size:] > + else: > + self.ents = bytearray() > + > + def _check_header(self, buf): > + hdr = struct.unpack_from(self.efi.var_file_fmt, buf, 0) > + magic, crc32 = hdr[1], hdr[3] > + > + if magic != UBOOT_EFI_VAR_FILE_MAGIC: > + print("err: invalid magic number: %s"%hex(magic)) > + exit(1) > + if crc32 != calc_crc32(buf[self.efi.var_file_size:]): > + print("err: invalid crc32: %s"%hex(crc32)) > + exit(1) > + > + def _get_var_name(self, buf): > + name = '' > + for i in range(0, len(buf) - 1, 2): > + if not buf[i] and not buf[i+1]: > + break > + name += chr(buf[i]) > + return ''.join([chr(x) for x in name.encode('utf_16_le') if x]), i + 2 > + > + def _next_var(self, offs=0): > + size, attrs, time, guid = struct.unpack_from(self.efi.var_entry_fmt, self.ents, offs) > + data_fmt = str(size)+"s" > + offs += self.efi.var_entry_size > + name, namelen = self._get_var_name(self.ents[offs:]) > + offs += namelen > + data = struct.unpack_from(data_fmt, self.ents, offs)[0] > + # offset to next 8-byte aligned variable entry > + offs = (offs + len(data) + 7) & ~7 > + return EfiVariable(size, attrs, time, uuid.UUID(bytes_le=guid), name, data), offs > + > + def __iter__(self): > + self.offs = 0 > + return self > + > + def __next__(self): > + if self.offs < len(self.ents): > + var, noffs = self._next_var(self.offs) > + self.offs = noffs > + return var > + else: > + raise StopIteration > + > + def __len__(self): > + return len(self.ents) > + > + def _set_var(self, guid, name_data, size, attrs, tsec): > + ent = struct.pack(self.efi.var_entry_fmt, > + size, > + attrs, > + tsec, > + uuid.UUID(guid).bytes_le) > + ent += name_data > + self.ents += ent > + > + def del_var(self, guid, name, attrs): > + offs = 0 > + while offs < len(self.ents): > + var, loffs = self._next_var(offs) > + if var.name == name and str(var.guid): > + if var.attrs != attrs: > + print("err: attributes don't match") > + exit(1) > + self.ents = self.ents[:offs] + self.ents[loffs:] > + return > + offs = loffs > + print("err: variable not found") > + exit(1) > + > + def set_var(self, guid, name, data, size, attrs): > + offs = 0 > + while offs < len(self.ents): > + var, loffs = self._next_var(offs) > + if var.name == name and str(var.guid) == guid: > + if var.attrs != attrs: > + print("err: attributes don't match") > + exit(1) > + # make room for updating var > + self.ents = self.ents[:offs] + self.ents[loffs:] > + break > + offs = loffs > + > + tsec = int(time.time()) if attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS else 0 > + nd = name.encode('utf_16_le') + b"\x00\x00" + data > + # U-Boot variable format requires the name + data blob to be 8-byte aligned > + pad = ((len(nd) + 7) & ~7) - len(nd) > + nd += bytes([0] * pad) > + > + return self._set_var(guid, nd, size, attrs, tsec) > + > + def save(self): > + hdr = struct.pack(self.efi.var_file_fmt, > + 0, > + UBOOT_EFI_VAR_FILE_MAGIC, > + len(self.ents) + self.efi.var_file_size, > + calc_crc32(self.ents)) > + > + with open(self.infile, 'wb') as f: > + f.write(hdr) > + f.write(self.ents) > + > +def parse_attrs(attrs): > + v = DEFAULT_VAR_ATTRS > + if attrs: > + v = 0 > + for i in attrs.split(','): > + v |= var_attrs[i.upper()] > + return v > + > +def parse_data(val, vtype): > + if not val or not vtype: > + return None, 0 > + fmt = { 'u8': '<B', 'u16': '<H', 'u32': '<L', 'u64': '<Q' } > + if vtype.lower() == 'file': > + with open(val, 'rb') as f: > + data = f.read() > + return data, len(data) > + if vtype.lower() == 'str': > + data = val.encode('utf-8') > + return data, len(data) > + if vtype.lower() == 'nil': > + return None, 0 > + i = fmt[vtype.lower()] > + return struct.pack(i, int(val)), struct.calcsize(i) > + > +def parse_args(args): > + name = args.name > + attrs = parse_attrs(args.attrs) > + guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID > + > + if name.lower() == 'db' or name.lower() == 'dbx': > + name = name.lower() > + guid = EFI_IMAGE_SECURITY_DATABASE_GUID > + attrs = NV_BS_RT_AT > + elif name.lower() == 'pk' or name.lower() == 'kek': > + name = name.upper() > + guid = EFI_GLOBAL_VARIABLE_GUID > + attrs = NV_BS_RT_AT > + > + data, size = parse_data(args.data, args.type) > + return guid, name, attrs, data, size > + > +def cmd_set(args): > + env = EfiVariableStore(args.infile) > + guid, name, attrs, data, size = parse_args(args) > + env.set_var(guid=guid, name=name, data=data, size=size, attrs=attrs) > + env.save() > + > +def print_var(var): > + print(var.name+':') > + print(" "+str(var.guid)+' '+''.join([x for x in var_guids if str(var.guid) == var_guids[x]])) > + print(" "+'|'.join([x for x in var_attrs if var.attrs & var_attrs[x]])+", DataSize = %s"%hex(var.size)) > + hexdump(var.data) > + > +def cmd_print(args): > + env = EfiVariableStore(args.infile) > + if not args.name and not args.guid and not len(env): > + return > + > + found = False > + for var in env: > + if not args.name: > + if args.guid and args.guid != str(var.guid): > + continue > + print_var(var) > + found = True > + else: > + if args.name != var.name or (args.guid and args.guid != str(var.guid)): > + continue > + print_var(var) > + found = True > + > + if not found: > + print("err: variable not found") > + exit(1) > + > +def cmd_del(args): > + env = EfiVariableStore(args.infile) > + attrs = parse_attrs(args.attrs) > + guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID > + env.del_var(guid, args.name, attrs) > + env.save() > + > +def pkcs7_sign(cert, key, buf): > + with open(cert, 'r') as f: > + crt = crypto.load_certificate(crypto.FILETYPE_PEM, f.read()) > + with open(key, 'r') as f: > + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read()) > + > + PKCS7_BINARY = 0x80 > + PKCS7_DETACHED = 0x40 > + PKCS7_NOATTR = 0x100 > + > + bio_in = crypto._new_mem_buf(buf) > + p7 = crypto._lib.PKCS7_sign(crt._x509, pkey._pkey, crypto._ffi.NULL, bio_in, > + PKCS7_BINARY|PKCS7_DETACHED|PKCS7_NOATTR) > + bio_out = crypto._new_mem_buf() > + crypto._lib.i2d_PKCS7_bio(bio_out, p7) > + return crypto._bio_to_string(bio_out) > + > +# UEFI 2.8 Errata B "8.2.2 Using the EFI_VARIABLE_AUTHENTICATION_2 descriptor" > +def cmd_sign(args): > + guid, name, attrs, data, size = parse_args(args) > + attrs |= EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS > + efi = EfiStruct() > + > + tm = time.localtime() > + etime = struct.pack(efi.var_time_fmt, > + tm.tm_year, tm.tm_mon, tm.tm_mday, > + tm.tm_hour, tm.tm_min, tm.tm_sec, > + 0, 0, 0, 0, 0) > + > + buf = name.encode('utf_16_le') + uuid.UUID(guid).bytes_le + attrs.to_bytes(4, byteorder='little') + etime > + if data: > + buf += data > + sig = pkcs7_sign(args.cert, args.key, buf) > + > + desc = struct.pack(efi.var_win_cert_uefi_guid_fmt, > + efi.var_win_cert_uefi_guid_size + len(sig), > + WIN_CERT_REVISION, > + WIN_CERT_TYPE_EFI_GUID, > + uuid.UUID(EFI_CERT_TYPE_PKCS7_GUID).bytes_le) > + > + with open(args.outfile, 'wb') as f: > + if data: > + f.write(etime + desc + sig + data) > + else: > + f.write(etime + desc + sig) > + > +def main(): > + ap = argparse.ArgumentParser(description='EFI variable store utilities') > + subp = ap.add_subparsers(help="sub-command help") > + > + printp = subp.add_parser('print', help='get/list EFI variables') > + printp.add_argument('--infile', '-i', required=True, help='file to save the EFI variables') > + printp.add_argument('--name', '-n', help='variable name') > + printp.add_argument('--guid', '-g', help='vendor GUID') > + printp.set_defaults(func=cmd_print) > + > + setp = subp.add_parser('set', help='set EFI variable') > + setp.add_argument('--infile', '-i', required=True, help='file to save the EFI variables') > + setp.add_argument('--name', '-n', required=True, help='variable name') > + setp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)') > + setp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) > + setp.add_argument('--type', '-t', help='variable type (values: file|u8|u16|u32|u64|str)') > + setp.add_argument('--data', '-d', help='data or filename') > + setp.set_defaults(func=cmd_set) > + > + delp = subp.add_parser('del', help='delete EFI variable') > + delp.add_argument('--infile', '-i', required=True, help='file to save the EFI variables') > + delp.add_argument('--name', '-n', required=True, help='variable name') > + delp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)') > + delp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) > + delp.set_defaults(func=cmd_del) > + > + signp = subp.add_parser('sign', help='sign time-based EFI payload') > + signp.add_argument('--cert', '-c', required=True, help='x509 certificate filename in PEM format') > + signp.add_argument('--key', '-k', required=True, help='signing certificate filename in PEM format') > + signp.add_argument('--name', '-n', required=True, help='variable name') > + signp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)') > + signp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID) > + signp.add_argument('--type', '-t', required=True, help='variable type (values: file|u8|u16|u32|u64|str|nil)') > + signp.add_argument('--data', '-d', help='data or filename') > + signp.add_argument('--outfile', '-o', required=True, help='output filename of signed EFI payload') > + signp.set_defaults(func=cmd_sign) > + > + args = ap.parse_args() > + args.func(args) > + > +def group(a, *ns): > + for n in ns: > + a = [a[i:i+n] for i in range(0, len(a), n)] > + return a > + > +def join(a, *cs): > + return [cs[0].join(join(t, *cs[1:])) for t in a] if cs else a > + > +def hexdump(data): > + toHex = lambda c: '{:02X}'.format(c) > + toChr = lambda c: chr(c) if 32 <= c < 127 else '.' > + make = lambda f, *cs: join(group(list(map(f, data)), 8, 2), *cs) > + hs = make(toHex, ' ', ' ') > + cs = make(toChr, ' ', '') > + for i, (h, c) in enumerate(zip(hs, cs)): > + print (' {:010X}: {:48} {:16}'.format(i * 16, h, c)) > + > +if __name__ == '__main__': > + main() > -- > 2.29.2 > ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v4] tools: add a simple script to generate EFI variables 2020-12-20 10:52 ` Heinrich Schuchardt @ 2020-12-22 13:35 ` Paulo Alcantara 0 siblings, 0 replies; 9+ messages in thread From: Paulo Alcantara @ 2020-12-22 13:35 UTC (permalink / raw) To: u-boot Heinrich Schuchardt <xypron.glpk@gmx.de> writes: > efivar.py requires that 'set' is the first parameter. > I will fix the commit message. Thanks! > Could you, please, provide a man-page for the tool as restructured text > in doc/usage/. Please, check that it is correct with 'make htmldocs'. Yes, will do it. ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2020-12-22 13:35 UTC | newest] Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2020-11-30 15:16 [PATCH] tools: add a simple script to generate EFI variables Paulo Alcantara 2020-11-30 16:38 ` Heinrich Schuchardt 2020-11-30 18:26 ` Paulo Alcantara 2020-12-01 22:58 ` [PATCH v2] " Paulo Alcantara 2020-12-02 10:50 ` Heinrich Schuchardt 2020-12-02 16:46 ` [PATCH v3] " Paulo Alcantara 2020-12-08 23:10 ` [PATCH v4] " Paulo Alcantara 2020-12-20 10:52 ` Heinrich Schuchardt 2020-12-22 13:35 ` Paulo Alcantara
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.