Message ID | 20201209172334.14100-1-jejb@linux.ibm.com (mailing list archive) |
---|---|
Headers | show |
Series | sev: enable seret injection to a self described area in OVMF | expand |
On Wed, 2020-12-09 at 09:23 -0800, James Bottomley wrote: > This patch series includes one from Tobin that has already been > posted > and reviewed: > > https://lore.kernel.org/qemu-devel/20201027170303.47550-1-tobin@linux.ibm.com/ > > I'm adding it here because it's a required precursor, but it can be > dropped from this series if it's already being upstreamed elsewhere. > > The remaining two patches introduce a parser for the optional OVMF > description table which is placed just below the reset vector (the > format of the table is described in the patch itself) and also adds a > hook to pull out the description of the SEV secret area location and > use it in place of the sev-inject-launch-secret gpa. For those who want to try this at home (assuming you have a SEV capable AMD system), you need the amd sev-tool: https://github.com/AMDESE/sev-tool/ To build the launch bundle (which contains the TIK and TEK key pair). Once you have that, you need to pass in both the launch bundle and the -S flag to get QEMU to pause before starting the VM to allow measurement and secret injection. I'm using the python script below to interact with the paused VM, verify the measurement, inject the secret and resume the VM. The below python script uses qmp.py from the qemu git repository, so you'll have to adjust your path to it. James --- #!/usr/bin/python3 ## # Python script to inject a secret disk password into a paused SEV VM # (to pause the VM start with -S option) # # This assumes you've already created the launch bundle using sev-tool # from https://github.com/AMDESE/sev-tool.git # # sev-tool --generate_launch_blob # # creates several files, the only one this script needs is the TIK and TEK # keys which are stored in tmp_tk.bin # # Once TIK/TEK are known, the script will probe the VM for the sev # parameters needed to calculate the launch measure, retrieve the launch # measure and verify against the measure calculated from the OVMF hash # and if that matches create the secret bundle and inject it # # Tables and chapters refer to the amd 55766.pdf document # # https://www.amd.com/system/files/TechDocs/55766_SEV-KM_API_Specification.pdf ## import sys import os import base64 import hmac import hashlib from argparse import ArgumentParser from uuid import UUID from Crypto.Cipher import AES from Crypto.Util import Counter from git.qemu.python.qemu import qmp if __name__ == "__main__": parser = ArgumentParser(description='Inject secret into SEV') parser.add_argument('--tiktek-file', help='file where sev-tool stored the TIK/TEK combination, defaults to tmp_tk.bin', default='tmp_tk.bin') parser.add_argument('--passwd', help='Disk Password', required=True) parser.add_argument('--ovmf-hash', help='hash of OVMF firmware blob in hex') parser.add_argument('--ovmf-file', help='location of OVMF file to calculate hash from') parser.add_argument('--socket', help='Socket to connect to QMP on, defaults to localhost:6550', default='localhost:6550') args = parser.parse_args() if (args.ovmf_file): fh = open (args.ovmf_file, 'rb') h = hashlib.sha256(fh.read()) ovmf_hash = h.digest() elif (args.ovmf_hash): ovmf_hash = bytearray.fromhex(args.ovmf_hash) else: parser.error('one of --ovmf-hash or -ovmf-file must be specified') if (args.socket[0] == '/'): socket = args.socket elif (':' in args.socket): s = args.socket.split(':') socket = (s[0], int(s[1])) else: parse.error('--socket must be <host>:<port> or /path/to/unix') fh=open(args.tiktek_file, 'rb') tiktek=bytearray(fh.read()) fh.close() ## # tiktek file is just two binary aes128 keys ## TEK=tiktek[0:16] TIK=tiktek[16:32] disk_secret = args.passwd Qmp = qmp.QEMUMonitorProtocol(address=socket); Qmp.connect() caps = Qmp.command('query-sev') print('SEV query found API={api-major}.{api-minor} build={build-id} policy={policy}'.format(**caps)) h = hmac.new(TIK, digestmod='sha256'); ## # calculated per section 6.5.2 ## h.update(bytes([0x04])) h.update(caps['api-major'].to_bytes(1,byteorder='little')) h.update(caps['api-minor'].to_bytes(1,byteorder='little')) h.update(caps['build-id'].to_bytes(1,byteorder='little')) h.update(caps['policy'].to_bytes(4,byteorder='little')) h.update(ovmf_hash) print('\nGetting Launch Measurement') meas = Qmp.command('query-sev-launch-measure') launch_measure = base64.b64decode(meas['data']) ## # returned data per Table 52. LAUNCH_MEASURE Measurement Buffer ## nonce = launch_measure[32:48] h.update(nonce) measure = launch_measure[0:32] print('Measure: ', measure.hex()) print('should be: ', h.digest().hex()) print('') if (measure != h.digest()): sys.exit('Measurement doesn\'t match') print('Measurement matches, Injecting Secret') ## # construct the secret table: two guids + 4 byte lengths plus string # and zero terminator # # Secret layout is guid, len (4 bytes), data # with len being the length from start of guid to end of data # # The table header covers the entire table then each entry covers # only its local data # # our current table has the header guid with total table length # followed by the secret guid with the zero terminated secret ## # total length of table: header plus one entry with trailing \0 l = 16 + 4 + 16 + 4 + len(disk_secret) + 1 secret = bytearray(l); secret[0:16] = UUID('{1e74f542-71dd-4d66-963e-ef4287ff173b}').bytes_le secret[16:20] = len(secret).to_bytes(4, byteorder='little') secret[20:36] = UUID('{736869e5-84f0-4973-92ec-06879ce3da0b}').bytes_le secret[36:40] = (16 + 4 + len(disk_secret) + 1).to_bytes(4, byteorder='little') secret[40:40+len(disk_secret)] = disk_secret.encode() ## # encrypt the secret table with the TEK in ctr mode using a random IV ## IV=os.urandom(16) # -EKNUCKLEHEADS in python crypto don't understand CTR mode e = AES.new(TEK, AES.MODE_CTR, counter=Counter.new(128,initial_value=int.from_bytes(IV, byteorder='big'))); encrypted_secret = e.encrypt(bytes(secret)) ## # ultimately needs to be an argument, but there's only # compressed and no real use case ## FLAGS = 0 ## # Table 55. LAUNCH_SECRET Packet Header Buffer ## header=bytearray(52); header[0:4]=FLAGS.to_bytes(4,byteorder='little') header[4:20]=IV h = hmac.new(TIK, digestmod='sha256'); h.update(bytes([0x01])) # FLAGS || IV h.update(header[0:20]) h.update(l.to_bytes(4, byteorder='little')) h.update(l.to_bytes(4, byteorder='little')) h.update(encrypted_secret) h.update(measure) header[20:52]=h.digest() Qmp.command('sev-inject-launch-secret', **{'packet-header': base64.b64encode(header).decode(), 'secret': base64.b64encode(encrypted_secret).decode() }) print('\nSecret Injection Successful, starting VM') Qmp.command('cont')
Patchew URL: https://patchew.org/QEMU/20201209172334.14100-1-jejb@linux.ibm.com/ Hi, This series seems to have some coding style problems. See output below for more information: Type: series Message-id: 20201209172334.14100-1-jejb@linux.ibm.com Subject: [PATCH 0/3] sev: enable seret injection to a self described area in OVMF === TEST SCRIPT BEGIN === #!/bin/bash git rev-parse base > /dev/null || exit 0 git config --local diff.renamelimit 0 git config --local diff.renames True git config --local diff.algorithm histogram ./scripts/checkpatch.pl --mailback base.. === TEST SCRIPT END === Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384 From https://github.com/patchew-project/qemu * [new tag] patchew/20201209172334.14100-1-jejb@linux.ibm.com -> patchew/20201209172334.14100-1-jejb@linux.ibm.com * [new tag] patchew/20201209173536.1437351-1-groug@kaod.org -> patchew/20201209173536.1437351-1-groug@kaod.org - [tag update] patchew/cover.1607467819.git.alistair.francis@wdc.com -> patchew/cover.1607467819.git.alistair.francis@wdc.com Switched to a new branch 'test' f51e90d sev: update sev-inject-launch-secret to make gpa optional 4f524bc pc: add parser for OVMF reset block 915ed52 sev: add sev-inject-launch-secret === OUTPUT BEGIN === 1/3 Checking commit 915ed52b80aa (sev: add sev-inject-launch-secret) 2/3 Checking commit 4f524bc2ee18 (pc: add parser for OVMF reset block) ERROR: braces {} are necessary for all arms of this statement #50: FILE: hw/i386/pc_sysfw.c:148: + if (ovmf_table) [...] ERROR: braces {} are necessary for all arms of this statement #61: FILE: hw/i386/pc_sysfw.c:159: + if (!qemu_uuid_is_equal((QemuUUID *)ptr, &guid)) [...] ERROR: braces {} are necessary for all arms of this statement #68: FILE: hw/i386/pc_sysfw.c:166: + if (tot_len <= 0) [...] ERROR: braces {} are necessary for all arms of this statement #90: FILE: hw/i386/pc_sysfw.c:188: + if (qemu_uuid_parse(entry, &entry_guid) < 0) [...] ERROR: braces {} are necessary for all arms of this statement #93: FILE: hw/i386/pc_sysfw.c:191: + if (!ptr) [...] ERROR: braces {} are necessary for all arms of this statement #112: FILE: hw/i386/pc_sysfw.c:210: + if (!len) [...] ERROR: braces {} are necessary for all arms of this statement #118: FILE: hw/i386/pc_sysfw.c:216: + if (data) [...] ERROR: braces {} are necessary for all arms of this statement #120: FILE: hw/i386/pc_sysfw.c:218: + if (data_len) [...] total: 8 errors, 0 warnings, 123 lines checked Patch 2/3 has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS. 3/3 Checking commit f51e90dbc914 (sev: update sev-inject-launch-secret to make gpa optional) WARNING: line over 80 characters #67: FILE: target/i386/monitor.c:750: + error_setg(errp, "SEV: no secret area found in OVMF, gpa must be specified."); total: 0 errors, 1 warnings, 44 lines checked Patch 3/3 has style problems, please review. If any of these errors are false positives report them to the maintainer, see CHECKPATCH in MAINTAINERS. === OUTPUT END === Test command exited with code: 1 The full log is available at http://patchew.org/logs/20201209172334.14100-1-jejb@linux.ibm.com/testing.checkpatch/?type=message. --- Email generated automatically by Patchew [https://patchew.org/]. Please send your feedback to patchew-devel@redhat.com