spec: Use RHEL style ovmf-vars-generator bundle

RHEL does not bundle the source archive, but just a copy of the
script. Use that model and the ovmf-vars-generator syntax RHEL uses.

This part of an effort to make it easier to share edk2 packaging
across Fedora and RHEL

Signed-off-by: Cole Robinson <crobinso@redhat.com>
This commit is contained in:
Cole Robinson 2021-07-15 13:49:13 -04:00
parent 55d0676cf5
commit 7bc8400912
4 changed files with 377 additions and 75 deletions

21
LICENSE.qosb Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Patrick Uiterwijk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

135
edk2.spec
View File

@ -3,17 +3,14 @@
%define TOOLCHAIN GCC5
%define OPENSSL_VER 1.1.1g
%global qosb_version 20200228-gitc3e16b3
%global softfloat_version 20180726-gitb64af41
# Enable this to skip secureboot enrollment, if problems pop up
%global skip_enroll 0
%define qosb_testing 0
%ifarch x86_64
%define qosb_testing 1
%endif
%define qemu_binary /usr/bin/qemu-system-x86_64
%if 0%{?fedora:1}
%define cross 1
%endif
@ -53,21 +50,21 @@ URL: http://www.tianocore.org
Source0: edk2-%{GITCOMMIT}.tar.xz
Source1: ovmf-whitepaper-c770f8c.txt
Source2: openssl-rhel-bdd048e929dcfcf2f046d74e812e0e3d5fc58504.tar.xz
#Source3: https://github.com/puiterwijk/qemu-ovmf-secureboot/archive/v{qosb_version}/qemu-ovmf-secureboot-{qosb_version}.tar.gz
Source3: qemu-ovmf-secureboot-%{qosb_version}.tar.xz
Source4: softfloat-%{softfloat_version}.tar.xz
Source5: RedHatSecureBootPkKek1.pem
Source11: build-iso.sh
Source3: ovmf-vars-generator
Source4: LICENSE.qosb
Source5: RedHatSecureBootPkKek1.pem
# Fedora-specific JSON "descriptor files"
Source14: 40-edk2-ovmf-x64-sb-enrolled.json
Source15: 50-edk2-ovmf-x64-sb.json
Source16: 60-edk2-ovmf-x64.json
Source17: 40-edk2-ovmf-ia32-sb-enrolled.json
Source18: 50-edk2-ovmf-ia32-sb.json
Source19: 60-edk2-ovmf-ia32.json
Source20: 70-edk2-aarch64-verbose.json
Source21: 70-edk2-arm-verbose.json
# Fedora specific sources
Source50: softfloat-%{softfloat_version}.tar.xz
Source51: build-iso.sh
Source52: 40-edk2-ovmf-x64-sb-enrolled.json
Source53: 50-edk2-ovmf-x64-sb.json
Source54: 60-edk2-ovmf-x64.json
Source55: 40-edk2-ovmf-ia32-sb-enrolled.json
Source56: 50-edk2-ovmf-ia32-sb.json
Source57: 60-edk2-ovmf-ia32.json
Source58: 70-edk2-aarch64-verbose.json
Source59: 70-edk2-arm-verbose.json
# non-upstream patches
Patch0008: 0008-BaseTools-do-not-build-BrotliCompress-RH-only.patch
@ -126,18 +123,16 @@ BuildRequires: findutils
# These are for QOSB
BuildRequires: python3-requests
BuildRequires: qemu-system-x86
%if %{?qosb_testing}
# This is used for testing the enrollment: builds are run in a chroot, lacking
# a kernel. The testing is only performed on x86_64 for now, but we can't make
# the BuildRequires only on a specific arch, as that'd come through in the SRPM
# NOTE: The actual enrollment needs to happen in all builds for all architectures,
# because OVMF is built as noarch, which means that koji enforces that the build
# results don't actually differ per arch, and then it picks a random arches' build
# for the actual RPM.
BuildRequires: kernel-core
%endif
BuildRequires: make
%if %{qosb_testing}
# For verifying SB enablement in the above variable store template, we need a
# guest kernel that prints "Secure boot enabled".
BuildRequires: kernel-core >= 4.18.0-161
BuildRequires: rpmdevtools
%endif
%description
EDK II is a development code base for creating UEFI drivers, applications
and firmware images.
@ -239,26 +234,25 @@ git config am.keepcr true
# -D is passed to %%setup to not delete the existing archive dir
%autosetup -T -D -n edk2-%{GITCOMMIT} -S git_am
# copy whitepaper into place
cp -a -- %{SOURCE1} .
cp -a -- %{SOURCE1} %{SOURCE3} .
# extract openssl into place
tar -C CryptoPkg/Library/OpensslLib -a -f %{SOURCE2} -x
# extract softfloat into place
tar -xf %{SOURCE4} --strip-components=1 --directory ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3/
tar -xf %{SOURCE50} --strip-components=1 --directory ArmPkg/Library/ArmSoftFloatLib/berkeley-softfloat-3/
# Extract QOSB
tar -xf %{SOURCE3}
mv qemu-ovmf-secureboot-%{qosb_version}/README.md README.qosb
mv qemu-ovmf-secureboot-%{qosb_version}/LICENSE LICENSE.qosb
# Extract OEM string from the RH cert, as described here
# https://bugzilla.tianocore.org/show_bug.cgi?id=1747#c2
# Format the Red Hat-issued certificate that is to be enrolled as both Platform
# Key and first Key Exchange Key, as an SMBIOS OEM String. This means stripping
# the PEM header and footer, and prepending the textual representation of the
# GUID that identifies this particular OEM String to "EnrollDefaultKeys.efi",
# plus the separator ":". For details, see
# <https://bugzilla.tianocore.org/show_bug.cgi?id=1747> comments 2, 7, 14.
sed \
-e 's/^-----BEGIN CERTIFICATE-----$/4e32566d-8e9e-4f52-81d3-5bb9715f9727:/' \
-e '/^-----END CERTIFICATE-----$/d' \
%{_sourcedir}/RedHatSecureBootPkKek1.pem \
| tr -d '\n' \
> PkKek1.oemstr
%{SOURCE5} \
> PkKek1.oemstr
cp -a -- %{SOURCE4} .
%build
@ -325,20 +319,15 @@ cp Build/Ovmf3264/*/X64/Shell.efi ovmf/
cp Build/Ovmf3264/*/X64/EnrollDefaultKeys.efi ovmf
sh %{_sourcedir}/build-iso.sh ovmf/
%if !%{skip_enroll}
python3 qemu-ovmf-secureboot-%{qosb_version}/ovmf-vars-generator \
--qemu-binary /usr/bin/qemu-system-x86_64 \
--ovmf-binary ovmf/OVMF_CODE.secboot.fd \
# Enroll the default certificates in a separate variable store template.
%{__python3} ovmf-vars-generator --verbose --verbose \
--qemu-binary %{qemu_binary} \
--ovmf-binary ovmf/OVMF_CODE.secboot.fd \
--ovmf-template-vars ovmf/OVMF_VARS.fd \
--uefi-shell-iso ovmf/UefiShell.iso \
--oem-string "$(< PkKek1.oemstr)" \
--uefi-shell-iso ovmf/UefiShell.iso \
--oem-string "$(< PkKek1.oemstr)" \
--skip-testing \
ovmf/OVMF_VARS.secboot.fd
%else
# This isn't going to actually give secureboot, but makes json files happy
# if we need to test disabling ovmf-vars-generator
cp ovmf/OVMF_VARS.fd ovmf/OVMF_VARS.secboot.fd
%endif
%endif
@ -386,25 +375,25 @@ dd of="arm/vars-template-pflash.raw" if="/dev/zero" bs=1M count=64
%check
%if 0%{?build_ovmf_x64:1}
%if 0%{?qosb_testing}
%if !%{skip_enroll}
KERNELPATH="$(find /lib/modules -name vmlinuz | head -1)"
python3 qemu-ovmf-secureboot-%{qosb_version}/ovmf-vars-generator \
--qemu-binary /usr/bin/qemu-system-x86_64 \
--ovmf-binary ovmf/OVMF_CODE.secboot.fd \
--ovmf-template-vars ovmf/OVMF_VARS.fd \
--uefi-shell-iso ovmf/UefiShell.iso \
--skip-enrollment \
--print-output \
--no-download \
-vv \
--kernel-path "$KERNELPATH" \
ovmf/OVMF_VARS.secboot.fd
%endif
%endif
%endif
%if %{qosb_testing}
# Of the installed host kernels, boot the one with the highest Version-Release
# under OVMF, and check if it prints "Secure boot enabled".
KERNEL_PKG=$(rpm -q kernel-core | rpmdev-sort | tail -n 1)
KERNEL_IMG=$(rpm -q -l $KERNEL_PKG | egrep '^/lib/modules/[^/]+/vmlinuz$')
%{__python3} ovmf-vars-generator --verbose --verbose \
--qemu-binary %{qemu_binary} \
--ovmf-binary ovmf/OVMF_CODE.secboot.fd \
--ovmf-template-vars ovmf/OVMF_VARS.fd \
--uefi-shell-iso ovmf/UefiShell.iso \
--kernel-path $KERNEL_IMG \
--skip-enrollment \
--no-download \
ovmf/OVMF_VARS.secboot.fd
# endif qosb_testing
%endif
%install
@ -490,8 +479,7 @@ done
%py_byte_compile %{python3} %{buildroot}%{_datadir}/edk2/Python
%endif
install qemu-ovmf-secureboot-%{qosb_version}/ovmf-vars-generator %{buildroot}%{_bindir}
install -p ovmf-vars-generator %{buildroot}%{_bindir}
%files tools
@ -533,7 +521,6 @@ install qemu-ovmf-secureboot-%{qosb_version}/ovmf-vars-generator %{buildroot}%{_
%files qosb
%license LICENSE.qosb
%doc README.qosb
%{_bindir}/ovmf-vars-generator
%if 0%{?build_ovmf_x64:1}

295
ovmf-vars-generator Executable file
View File

@ -0,0 +1,295 @@
#!/bin/python3
# Copyright (C) 2017 Red Hat
# Authors:
# - Patrick Uiterwijk <puiterwijk@redhat.com>
# - Kashyap Chamarthy <kchamart@redhat.com>
#
# Licensed under MIT License, for full text see LICENSE
#
# Purpose: Launch a QEMU guest and enroll ithe UEFI keys into an OVMF
# variables ("VARS") file. Then boot a Linux kernel with QEMU.
# Finally, perform a check to verify if Secure Boot
# is enabled.
from __future__ import print_function
import argparse
import os
import logging
import tempfile
import shutil
import string
import subprocess
def strip_special(line):
return ''.join([c for c in str(line) if c in string.printable])
def generate_qemu_cmd(args, readonly, *extra_args):
if args.disable_smm:
machinetype = 'pc'
else:
machinetype = 'q35,smm=on'
machinetype += ',accel=%s' % ('kvm' if args.enable_kvm else 'tcg')
if args.oem_string is None:
oemstrings = []
else:
oemstring_values = [
",value=" + s.replace(",", ",,") for s in args.oem_string ]
oemstrings = [
'-smbios',
"type=11" + ''.join(oemstring_values) ]
return [
args.qemu_binary,
'-machine', machinetype,
'-display', 'none',
'-no-user-config',
'-nodefaults',
'-m', '768',
'-smp', '2,sockets=2,cores=1,threads=1',
'-chardev', 'pty,id=charserial1',
'-device', 'isa-serial,chardev=charserial1,id=serial1',
'-global', 'driver=cfi.pflash01,property=secure,value=%s' % (
'off' if args.disable_smm else 'on'),
'-drive',
'file=%s,if=pflash,format=raw,unit=0,readonly=on' % (
args.ovmf_binary),
'-drive',
'file=%s,if=pflash,format=raw,unit=1,readonly=%s' % (
args.out_temp, 'on' if readonly else 'off'),
'-serial', 'stdio'] + oemstrings + list(extra_args)
def download(url, target, suffix, no_download):
istemp = False
if target and os.path.exists(target):
return target, istemp
if not target:
temped = tempfile.mkstemp(prefix='qosb.', suffix='.%s' % suffix)
os.close(temped[0])
target = temped[1]
istemp = True
if no_download:
raise Exception('%s did not exist, but downloading was disabled' %
target)
import requests
logging.debug('Downloading %s to %s', url, target)
r = requests.get(url, stream=True)
with open(target, 'wb') as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
return target, istemp
def enroll_keys(args):
shutil.copy(args.ovmf_template_vars, args.out_temp)
logging.info('Starting enrollment')
cmd = generate_qemu_cmd(
args,
False,
'-drive',
'file=%s,format=raw,if=none,media=cdrom,id=drive-cd1,'
'readonly=on' % args.uefi_shell_iso,
'-device',
'ide-cd,drive=drive-cd1,id=cd1,'
'bootindex=1')
p = subprocess.Popen(cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
logging.info('Performing enrollment')
# Wait until the UEFI shell starts (first line is printed)
read = p.stdout.readline()
if b'char device redirected' in read:
read = p.stdout.readline()
# Skip passed QEMU warnings, like the following one we see in Ubuntu:
# qemu-system-x86_64: warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
while b'qemu-system-x86_64: warning:' in read:
read = p.stdout.readline()
if args.print_output:
print(strip_special(read), end='')
print()
# Send the escape char to enter the UEFI shell early
p.stdin.write(b'\x1b')
p.stdin.flush()
# And then run the following three commands from the UEFI shell:
# change into the first file system device; install the default
# keys and certificates, and reboot
p.stdin.write(b'fs0:\r\n')
p.stdin.write(b'EnrollDefaultKeys.efi\r\n')
p.stdin.write(b'reset -s\r\n')
p.stdin.flush()
while True:
read = p.stdout.readline()
if args.print_output:
print('OUT: %s' % strip_special(read), end='')
print()
if b'info: success' in read:
break
p.wait()
if args.print_output:
print(strip_special(p.stdout.read()), end='')
logging.info('Finished enrollment')
def test_keys(args):
logging.info('Grabbing test kernel')
kernel, kerneltemp = download(args.kernel_url, args.kernel_path,
'kernel', args.no_download)
logging.info('Starting verification')
try:
cmd = generate_qemu_cmd(
args,
True,
'-append', 'console=tty0 console=ttyS0,115200n8',
'-kernel', kernel)
p = subprocess.Popen(cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
logging.info('Performing verification')
while True:
read = p.stdout.readline()
if args.print_output:
print('OUT: %s' % strip_special(read), end='')
print()
if b'Secure boot disabled' in read:
raise Exception('Secure Boot was disabled')
elif b'Secure boot enabled' in read:
logging.info('Confirmed: Secure Boot is enabled')
break
elif b'Kernel is locked down from EFI secure boot' in read:
logging.info('Confirmed: Secure Boot is enabled')
break
p.kill()
if args.print_output:
print(strip_special(p.stdout.read()), end='')
logging.info('Finished verification')
finally:
if kerneltemp:
os.remove(kernel)
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('output', help='Filename for output vars file')
parser.add_argument('--out-temp', help=argparse.SUPPRESS)
parser.add_argument('--force', help='Overwrite existing output file',
action='store_true')
parser.add_argument('--print-output', help='Print the QEMU guest output',
action='store_true')
parser.add_argument('--verbose', '-v', help='Increase verbosity',
action='count')
parser.add_argument('--quiet', '-q', help='Decrease verbosity',
action='count')
parser.add_argument('--qemu-binary', help='QEMU binary path',
default='/usr/bin/qemu-system-x86_64')
parser.add_argument('--enable-kvm', help='Enable KVM acceleration',
action='store_true')
parser.add_argument('--ovmf-binary', help='OVMF secureboot code file',
default='/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd')
parser.add_argument('--ovmf-template-vars', help='OVMF empty vars file',
default='/usr/share/edk2/ovmf/OVMF_VARS.fd')
parser.add_argument('--uefi-shell-iso', help='Path to uefi shell iso',
default='/usr/share/edk2/ovmf/UefiShell.iso')
parser.add_argument('--skip-enrollment',
help='Skip enrollment, only test', action='store_true')
parser.add_argument('--skip-testing',
help='Skip testing generated "VARS" file',
action='store_true')
parser.add_argument('--kernel-path',
help='Specify a consistent path for kernel')
parser.add_argument('--no-download', action='store_true',
help='Never download a kernel')
parser.add_argument('--fedora-version',
help='Fedora version to get kernel for checking',
default='27')
parser.add_argument('--kernel-url', help='Kernel URL',
default='https://download.fedoraproject.org/pub/fedora'
'/linux/releases/%(version)s/Everything/x86_64'
'/os/images/pxeboot/vmlinuz')
parser.add_argument('--disable-smm',
help=('Don\'t restrict varstore pflash writes to '
'guest code that executes in SMM. Use this '
'option only if your OVMF binary doesn\'t have '
'the edk2 SMM driver stack built into it '
'(possibly because your QEMU binary lacks SMM '
'emulation). Note that without restricting '
'varstore pflash writes to guest code that '
'executes in SMM, a malicious guest kernel, '
'used for testing, could undermine Secure '
'Boot.'),
action='store_true')
parser.add_argument('--oem-string',
help=('Pass the argument to the guest as a string in '
'the SMBIOS Type 11 (OEM Strings) table. '
'Multiple occurrences of this option are '
'collected into a single SMBIOS Type 11 table. '
'A pure ASCII string argument is strongly '
'suggested.'),
action='append')
args = parser.parse_args()
args.kernel_url = args.kernel_url % {'version': args.fedora_version}
validate_args(args)
return args
def validate_args(args):
if (os.path.exists(args.output)
and not args.force
and not args.skip_enrollment):
raise Exception('%s already exists' % args.output)
if args.skip_enrollment and not os.path.exists(args.output):
raise Exception('%s does not yet exist' % args.output)
verbosity = (args.verbose or 1) - (args.quiet or 0)
if verbosity >= 2:
logging.basicConfig(level=logging.DEBUG)
elif verbosity == 1:
logging.basicConfig(level=logging.INFO)
elif verbosity < 0:
logging.basicConfig(level=logging.ERROR)
else:
logging.basicConfig(level=logging.WARN)
if args.skip_enrollment:
args.out_temp = args.output
else:
temped = tempfile.mkstemp(prefix='qosb.', suffix='.vars')
os.close(temped[0])
args.out_temp = temped[1]
logging.debug('Temp output: %s', args.out_temp)
def move_to_dest(args):
shutil.copy(args.out_temp, args.output)
os.remove(args.out_temp)
def main():
args = parse_args()
if not args.skip_enrollment:
enroll_keys(args)
if not args.skip_testing:
test_keys(args)
if not args.skip_enrollment:
move_to_dest(args)
if args.skip_testing:
logging.info('Created %s' % args.output)
else:
logging.info('Created and verified %s' % args.output)
else:
logging.info('Verified %s', args.output)
if __name__ == '__main__':
main()

View File

@ -1,5 +1,4 @@
SHA512 (softfloat-20180726-gitb64af41.tar.xz) = f079debd1bfcc0fe64329a8947b0689ef49246793edcdd28a2879f6550c652b0cf0f53ac4f6f5ab61ac4f7933972e0019d0ab63eb9931b6884c2909f3a5ead30
SHA512 (qemu-ovmf-secureboot-20200228-gitc3e16b3.tar.xz) = 123889b9277adda472035f72e4836b6fe8e0cd8e2e87d28400bbc846ea1308378fc7aae413d463e0c1bfda096d85e51be100eb8d7dfb0738707c3412f2855711
SHA512 (openssl-rhel-a75722161d20fd632f8875585d3aa066ec5fea93.tar.xz) = f88284054e33921fbebfad973abe83b83528e88b4b07b21b0c4999c07e7cb95270817144122209f4bf992deb96684d9fd2e871c63aa563cf39d14b087decf907
SHA512 (edk2-e1999b264f1f.tar.xz) = 323e28ae205e0aa670674e1bd39845f942331348da34a3c48a9ec7da1d3e617b0abf1c52347c818ea84e7218b74760cbeb4ba761656919748f799b984ab04e6a
SHA512 (openssl-rhel-bdd048e929dcfcf2f046d74e812e0e3d5fc58504.tar.xz) = d6c0be28cefaa7993f479aecf45f5523e10aab388a92c3e4add55d6ff8483cc01fba367be62f6e9a23bfad7b46986befd3eca0d0e89b984049719645e37d52df