2 ## SPDX-License-Identifier: GPL-2.0-only
4 # EFI variable store utilities.
6 # (c) 2020 Paulo Alcantara <palcantara@suse.de>
15 from OpenSSL
import crypto
17 # U-Boot variable store format (version 1)
18 UBOOT_EFI_VAR_FILE_MAGIC
= 0x0161566966456255
20 # UEFI variable attributes
21 EFI_VARIABLE_NON_VOLATILE
= 0x1
22 EFI_VARIABLE_BOOTSERVICE_ACCESS
= 0x2
23 EFI_VARIABLE_RUNTIME_ACCESS
= 0x4
24 EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS
= 0x10
25 EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
= 0x20
26 EFI_VARIABLE_READ_ONLY
= 1 << 31
27 NV_BS
= EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS
28 NV_BS_RT
= NV_BS | EFI_VARIABLE_RUNTIME_ACCESS
29 NV_BS_RT_AT
= NV_BS_RT | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
30 DEFAULT_VAR_ATTRS
= NV_BS_RT
33 EFI_GLOBAL_VARIABLE_GUID
= '8be4df61-93ca-11d2-aa0d-00e098032b8c'
34 EFI_IMAGE_SECURITY_DATABASE_GUID
= 'd719b2cb-3d3a-4596-a3bc-dad00e67656f'
35 EFI_CERT_TYPE_PKCS7_GUID
= '4aafd29d-68df-49ee-8aa9-347d375665a7'
36 WIN_CERT_TYPE_EFI_GUID
= 0x0ef1
37 WIN_CERT_REVISION
= 0x0200
40 'NV': EFI_VARIABLE_NON_VOLATILE
,
41 'BS': EFI_VARIABLE_BOOTSERVICE_ACCESS
,
42 'RT': EFI_VARIABLE_RUNTIME_ACCESS
,
43 'AT': EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
,
44 'RO': EFI_VARIABLE_READ_ONLY
,
45 'AW': EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS
,
49 'EFI_GLOBAL_VARIABLE_GUID': EFI_GLOBAL_VARIABLE_GUID
,
50 'EFI_IMAGE_SECURITY_DATABASE_GUID': EFI_IMAGE_SECURITY_DATABASE_GUID
,
55 var_file_fmt
= '<QQLL'
56 var_file_size
= struct
.calcsize(var_file_fmt
)
57 # struct efi_var_entry
58 var_entry_fmt
= '<LLQ16s'
59 var_entry_size
= struct
.calcsize(var_entry_fmt
)
61 var_time_fmt
= '<H6BLh2B'
62 var_time_size
= struct
.calcsize(var_time_fmt
)
64 var_win_cert_fmt
= '<L2H'
65 var_win_cert_size
= struct
.calcsize(var_win_cert_fmt
)
66 # WIN_CERTIFICATE_UEFI_GUID
67 var_win_cert_uefi_guid_fmt
= var_win_cert_fmt
+'16s'
68 var_win_cert_uefi_guid_size
= struct
.calcsize(var_win_cert_uefi_guid_fmt
)
71 def __init__(self
, size
, attrs
, time
, guid
, name
, data
):
80 return zlib
.crc32(buf
) & 0xffffffff
82 class EfiVariableStore
:
83 def __init__(self
, infile
):
85 self
.efi
= EfiStruct()
86 if os
.path
.exists(self
.infile
) and os
.stat(self
.infile
).st_size
> self
.efi
.var_file_size
:
87 with
open(self
.infile
, 'rb') as f
:
89 self
._check
_header
(buf
)
90 self
.ents
= buf
[self
.efi
.var_file_size
:]
92 self
.ents
= bytearray()
94 def _check_header(self
, buf
):
95 hdr
= struct
.unpack_from(self
.efi
.var_file_fmt
, buf
, 0)
96 magic
, crc32
= hdr
[1], hdr
[3]
98 if magic
!= UBOOT_EFI_VAR_FILE_MAGIC
:
99 print("err: invalid magic number: %s"%hex(magic
))
101 if crc32
!= calc_crc32(buf
[self
.efi
.var_file_size
:]):
102 print("err: invalid crc32: %s"%hex(crc32
))
105 def _get_var_name(self
, buf
):
107 for i
in range(0, len(buf
) - 1, 2):
108 if not buf
[i
] and not buf
[i
+1]:
111 return ''.join([chr(x
) for x
in name
.encode('utf_16_le') if x
]), i
+ 2
113 def _next_var(self
, offs
=0):
114 size
, attrs
, time
, guid
= struct
.unpack_from(self
.efi
.var_entry_fmt
, self
.ents
, offs
)
115 data_fmt
= str(size
)+"s"
116 offs
+= self
.efi
.var_entry_size
117 name
, namelen
= self
._get
_var
_name
(self
.ents
[offs
:])
119 data
= struct
.unpack_from(data_fmt
, self
.ents
, offs
)[0]
120 # offset to next 8-byte aligned variable entry
121 offs
= (offs
+ len(data
) + 7) & ~
7
122 return EfiVariable(size
, attrs
, time
, uuid
.UUID(bytes_le
=guid
), name
, data
), offs
129 if self
.offs
< len(self
.ents
):
130 var
, noffs
= self
._next
_var
(self
.offs
)
137 return len(self
.ents
)
139 def _set_var(self
, guid
, name_data
, size
, attrs
, tsec
):
140 ent
= struct
.pack(self
.efi
.var_entry_fmt
,
144 uuid
.UUID(guid
).bytes_le
)
148 def del_var(self
, guid
, name
, attrs
):
150 while offs
< len(self
.ents
):
151 var
, loffs
= self
._next
_var
(offs
)
152 if var
.name
== name
and str(var
.guid
) == guid
:
153 if var
.attrs
!= attrs
:
154 print("err: attributes don't match")
156 self
.ents
= self
.ents
[:offs
] + self
.ents
[loffs
:]
159 print("err: variable not found")
162 def set_var(self
, guid
, name
, data
, size
, attrs
):
164 while offs
< len(self
.ents
):
165 var
, loffs
= self
._next
_var
(offs
)
166 if var
.name
== name
and str(var
.guid
) == guid
:
167 if var
.attrs
!= attrs
:
168 print("err: attributes don't match")
170 # make room for updating var
171 self
.ents
= self
.ents
[:offs
] + self
.ents
[loffs
:]
175 tsec
= int(time
.time()) if attrs
& EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
else 0
176 nd
= name
.encode('utf_16_le') + b
"\x00\x00" + data
177 # U-Boot variable format requires the name + data blob to be 8-byte aligned
178 pad
= ((len(nd
) + 7) & ~
7) - len(nd
)
179 nd
+= bytes([0] * pad
)
181 return self
._set
_var
(guid
, nd
, size
, attrs
, tsec
)
184 hdr
= struct
.pack(self
.efi
.var_file_fmt
,
186 UBOOT_EFI_VAR_FILE_MAGIC
,
187 len(self
.ents
) + self
.efi
.var_file_size
,
188 calc_crc32(self
.ents
))
190 with
open(self
.infile
, 'wb') as f
:
194 def parse_attrs(attrs
):
195 v
= DEFAULT_VAR_ATTRS
198 for i
in attrs
.split(','):
199 v |
= var_attrs
[i
.upper()]
202 def parse_data(val
, vtype
):
203 if not val
or not vtype
:
205 fmt
= { 'u8': '<B', 'u16': '<H', 'u32': '<L', 'u64': '<Q' }
206 if vtype
.lower() == 'file':
207 with
open(val
, 'rb') as f
:
209 return data
, len(data
)
210 if vtype
.lower() == 'str':
211 data
= val
.encode('utf-8')
212 return data
, len(data
)
213 if vtype
.lower() == 'nil':
215 i
= fmt
[vtype
.lower()]
216 return struct
.pack(i
, int(val
)), struct
.calcsize(i
)
218 def parse_args(args
):
220 attrs
= parse_attrs(args
.attrs
)
221 guid
= args
.guid
if args
.guid
else EFI_GLOBAL_VARIABLE_GUID
223 if name
.lower() == 'db' or name
.lower() == 'dbx':
225 guid
= EFI_IMAGE_SECURITY_DATABASE_GUID
227 elif name
.lower() == 'pk' or name
.lower() == 'kek':
229 guid
= EFI_GLOBAL_VARIABLE_GUID
232 data
, size
= parse_data(args
.data
, args
.type)
233 return guid
, name
, attrs
, data
, size
236 env
= EfiVariableStore(args
.infile
)
237 guid
, name
, attrs
, data
, size
= parse_args(args
)
238 env
.set_var(guid
=guid
, name
=name
, data
=data
, size
=size
, attrs
=attrs
)
243 print(" "+str(var
.guid
)+' '+''.join([x
for x
in var_guids
if str(var
.guid
) == var_guids
[x
]]))
244 print(" "+'|'.join([x
for x
in var_attrs
if var
.attrs
& var_attrs
[x
]])+", DataSize = %s"%hex(var
.size
))
248 env
= EfiVariableStore(args
.infile
)
249 if not args
.name
and not args
.guid
and not len(env
):
255 if args
.guid
and args
.guid
!= str(var
.guid
):
260 if args
.name
!= var
.name
or (args
.guid
and args
.guid
!= str(var
.guid
)):
266 print("err: variable not found")
270 env
= EfiVariableStore(args
.infile
)
271 attrs
= parse_attrs(args
.attrs
)
272 guid
= args
.guid
if args
.guid
else EFI_GLOBAL_VARIABLE_GUID
273 env
.del_var(guid
, args
.name
, attrs
)
276 def pkcs7_sign(cert
, key
, buf
):
277 with
open(cert
, 'r') as f
:
278 crt
= crypto
.load_certificate(crypto
.FILETYPE_PEM
, f
.read())
279 with
open(key
, 'r') as f
:
280 pkey
= crypto
.load_privatekey(crypto
.FILETYPE_PEM
, f
.read())
283 PKCS7_DETACHED
= 0x40
286 bio_in
= crypto
._new
_mem
_buf
(buf
)
287 p7
= crypto
._lib
.PKCS7_sign(crt
._x
509, pkey
._pkey
, crypto
._ffi
.NULL
, bio_in
,
288 PKCS7_BINARY|PKCS7_DETACHED|PKCS7_NOATTR
)
289 bio_out
= crypto
._new
_mem
_buf
()
290 crypto
._lib
.i2d_PKCS7_bio(bio_out
, p7
)
291 return crypto
._bio
_to
_string
(bio_out
)
293 # UEFI 2.8 Errata B "8.2.2 Using the EFI_VARIABLE_AUTHENTICATION_2 descriptor"
295 guid
, name
, attrs
, data
, _
= parse_args(args
)
296 attrs |
= EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
299 tm
= time
.localtime()
300 etime
= struct
.pack(efi
.var_time_fmt
,
301 tm
.tm_year
, tm
.tm_mon
, tm
.tm_mday
,
302 tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
,
305 buf
= name
.encode('utf_16_le') + uuid
.UUID(guid
).bytes_le
+ attrs
.to_bytes(4, byteorder
='little') + etime
308 sig
= pkcs7_sign(args
.cert
, args
.key
, buf
)
310 desc
= struct
.pack(efi
.var_win_cert_uefi_guid_fmt
,
311 efi
.var_win_cert_uefi_guid_size
+ len(sig
),
313 WIN_CERT_TYPE_EFI_GUID
,
314 uuid
.UUID(EFI_CERT_TYPE_PKCS7_GUID
).bytes_le
)
316 with
open(args
.outfile
, 'wb') as f
:
318 f
.write(etime
+ desc
+ sig
+ data
)
320 f
.write(etime
+ desc
+ sig
)
323 ap
= argparse
.ArgumentParser(description
='EFI variable store utilities')
324 subp
= ap
.add_subparsers(help="sub-command help")
326 printp
= subp
.add_parser('print', help='get/list EFI variables')
327 printp
.add_argument('--infile', '-i', required
=True, help='file to save the EFI variables')
328 printp
.add_argument('--name', '-n', help='variable name')
329 printp
.add_argument('--guid', '-g', help='vendor GUID')
330 printp
.set_defaults(func
=cmd_print
)
332 setp
= subp
.add_parser('set', help='set EFI variable')
333 setp
.add_argument('--infile', '-i', required
=True, help='file to save the EFI variables')
334 setp
.add_argument('--name', '-n', required
=True, help='variable name')
335 setp
.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)')
336 setp
.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI
_GLOBAL
_VARIABLE
_GUID
)
337 setp
.add_argument('--type', '-t', help='variable type (values: file|u8|u16|u32|u64|str)')
338 setp
.add_argument('--data', '-d', help='data or filename')
339 setp
.set_defaults(func
=cmd_set
)
341 delp
= subp
.add_parser('del', help='delete EFI variable')
342 delp
.add_argument('--infile', '-i', required
=True, help='file to save the EFI variables')
343 delp
.add_argument('--name', '-n', required
=True, help='variable name')
344 delp
.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)')
345 delp
.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI
_GLOBAL
_VARIABLE
_GUID
)
346 delp
.set_defaults(func
=cmd_del
)
348 signp
= subp
.add_parser('sign', help='sign time-based EFI payload')
349 signp
.add_argument('--cert', '-c', required
=True, help='x509 certificate filename in PEM format')
350 signp
.add_argument('--key', '-k', required
=True, help='signing certificate filename in PEM format')
351 signp
.add_argument('--name', '-n', required
=True, help='variable name')
352 signp
.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)')
353 signp
.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI
_GLOBAL
_VARIABLE
_GUID
)
354 signp
.add_argument('--type', '-t', required
=True, help='variable type (values: file|u8|u16|u32|u64|str|nil)')
355 signp
.add_argument('--data', '-d', help='data or filename')
356 signp
.add_argument('--outfile', '-o', required
=True, help='output filename of signed EFI payload')
357 signp
.set_defaults(func
=cmd_sign
)
359 args
= ap
.parse_args()
360 if hasattr(args
, "func"):
367 a
= [a
[i
:i
+n
] for i
in range(0, len(a
), n
)]
371 return [cs
[0].join(join(t
, *cs
[1:])) for t
in a
] if cs
else a
374 toHex
= lambda c
: '{:02X}'.format(c
)
375 toChr
= lambda c
: chr(c
) if 32 <= c
< 127 else '.'
376 make
= lambda f
, *cs
: join(group(list(map(f
, data
)), 8, 2), *cs
)
377 hs
= make(toHex
, ' ', ' ')
378 cs
= make(toChr
, ' ', '')
379 for i
, (h
, c
) in enumerate(zip(hs
, cs
)):
380 print (' {:010X}: {:48} {:16}'.format(i
* 16, h
, c
))
382 if __name__
== '__main__':