mbox series

[0/3] sev: enable seret injection to a self described area in OVMF

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

Message

James Bottomley Dec. 9, 2020, 5:23 p.m. UTC
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.

James

---

James Bottomley (2):
  pc: add parser for OVMF reset block
  sev: update sev-inject-launch-secret to make gpa optional

Tobin Feldman-Fitzthum (1):
  sev: add sev-inject-launch-secret

 hw/i386/pc_sysfw.c        | 95 +++++++++++++++++++++++++++++++++++++++
 include/hw/i386/pc.h      |  4 ++
 include/monitor/monitor.h |  3 ++
 include/sysemu/sev.h      |  2 +
 monitor/misc.c            | 17 +++++--
 qapi/misc-target.json     | 18 ++++++++
 target/i386/monitor.c     | 27 +++++++++++
 target/i386/sev-stub.c    |  5 +++
 target/i386/sev.c         | 65 +++++++++++++++++++++++++++
 target/i386/trace-events  |  1 +
 10 files changed, 233 insertions(+), 4 deletions(-)

Comments

James Bottomley Dec. 9, 2020, 5:52 p.m. UTC | #1
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')
no-reply@patchew.org Dec. 9, 2020, 7:33 p.m. UTC | #2
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