Skip to content

Instantly share code, notes, and snippets.

@chibiegg
Last active July 23, 2024 12:51
Show Gist options
  • Save chibiegg/a78214f4872230f9d8e746666aeac77c to your computer and use it in GitHub Desktop.
Save chibiegg/a78214f4872230f9d8e746666aeac77c to your computer and use it in GitHub Desktop.
SEV-SNP / COCONUT-SVSM Remote Attestation Demo
"""
MIT License
Copyright (c) 2024 chibiegg.
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.
"""
import random
import string
import subprocess
import sys
import tempfile
from fabric import Connection
"""
Requirements:
* `fabric` is required. (Install by `pip install fabric`)
* You can SSH connect to the target using public key authentication.
Note:
* This code has not been carefully considered for vulnerabilities.
* ex: OS Command Injection
"""
def generate_random_string(n):
randlst = [random.choice(string.ascii_letters + string.digits) for i in range(n)]
return "".join(randlst)
def attest_target_over_ssh(target, ek_context="0x81010000"):
print(f"Connecting to {target}")
conn = Connection(target)
result = conn.run("uname -a", hide=True)
print(f"uname -a: {result.stdout}")
# Create Remote dir
ATTESTATION_DIR = "/tmp/attestation"
conn.run(f"sudo mkdir -p {ATTESTATION_DIR}")
conn.run(f"sudo chmod 777 {ATTESTATION_DIR}")
# Create Local dir
localdir = tempfile.TemporaryDirectory("attest").name
print(f"localdir: {localdir}")
### SEV 構成証明 (アテステーションレポート) の取得と検証
# (VM内) アテステーションレポートの取得
result = conn.run(f"sudo snpguest report {ATTESTATION_DIR}/report.bin {ATTESTATION_DIR}/request-file.txt --random -v 2")
conn.get(f"{ATTESTATION_DIR}/report.bin", f"{localdir}/report.bin")
conn.get(f"{ATTESTATION_DIR}/request-file.txt", f"{localdir}/request-file.txt")
# TODO: VMPL0 (SVSM) のアテステーションレポートの取得 (vTPMのEK生成後に実施)
# (アテスター) 検証用証明書の取得とアテステーションレポートの検証
subprocess.check_call(f"mkdir {localdir}/certs", shell=True)
subprocess.check_call(f"snpguest fetch ca -e vcek der milan {localdir}/certs", shell=True)
subprocess.check_call(f"snpguest fetch vcek der milan {localdir}/certs {localdir}/report.bin", shell=True)
subprocess.check_call(f"snpguest verify certs {localdir}/certs", shell=True)
subprocess.check_call(f"snpguest verify attestation {localdir}/certs {localdir}/report.bin", shell=True)
# TODO: アテステーションレポートの内容を確認する必要がある (e.g., ファームウェアのハッシュ値等)
# TODO: VMPL0 (SVSM) のアテステーションレポートの検証
### vTPMを利用したMesuerd Bootの検証
# (VM内) EKの生成とEKpubの取得
result = conn.run(f"sudo tpm2_createek -Q -c {ek_context} -G rsa -u {ATTESTATION_DIR}/ek.pub")
print(f"tpm2_createek: {result.stdout}")
conn.run(f"sudo chmod 644 {ATTESTATION_DIR}/ek.pub")
conn.get(f"{ATTESTATION_DIR}/ek.pub", f"{localdir}/ek.pub")
# (VM内) AKの生成とAKpubの取得
result = conn.run(
f"sudo tpm2_createak --ek-context={ek_context} --ak-context={ATTESTATION_DIR}/ak.ctx --key-algorithm=rsa --hash-algorithm=sha256 --signing-algorithm=rsassa --public={ATTESTATION_DIR}/ak.pub --ak-name={ATTESTATION_DIR}/ak.name",
)
print(f"tpm2_createak: {result.stdout}")
conn.run(f"sudo chmod 644 {ATTESTATION_DIR}/ak.pub")
conn.run(f"sudo chmod 644 {ATTESTATION_DIR}/ak.name")
conn.get(f"{ATTESTATION_DIR}/ak.pub", f"{localdir}/ak.pub")
conn.get(f"{ATTESTATION_DIR}/ak.name", f"{localdir}/ak.name")
# (アテスター) AKとEKの検証のために暗号化されたチャレンジを生成する
aiksecret = generate_random_string(16).encode("ascii")
with open(f"{localdir}/ak_secret", "wb") as f:
f.write(aiksecret)
akname = ""
with open(f"{localdir}/ak.name", "rb") as f:
n = f.read()
akname = n.hex()
print(f"akname: {akname}")
cmd = [
"tpm2_makecredential",
"--tcti",
"none",
f"--public={localdir}/ek.pub",
f"--secret={localdir}/ak_secret",
f"--credential-blob={localdir}/mkcred.out",
f"--name={akname}",
]
cmd = " ".join(cmd)
print(cmd)
subprocess.check_call(cmd, shell=True)
conn.put(f"{localdir}/mkcred.out", f"{ATTESTATION_DIR}/mkcred.out")
# (VM内) アテスターから送り込んだ暗号化されたチャレンジを復号する
conn.run(f"sudo tpm2_startauthsession --policy-session -S session.ctx")
conn.run(f"sudo tpm2_policysecret -S session.ctx -c e")
conn.run(f"sudo tpm2_activatecredential -c {ATTESTATION_DIR}/ak.ctx -C {ek_context} -i {ATTESTATION_DIR}/mkcred.out -o {ATTESTATION_DIR}/actcred.out -P 'session:session.ctx'")
conn.run(f"sudo tpm2_flushcontext session.ctx")
conn.run(f"sudo chmod 644 {ATTESTATION_DIR}/actcred.out")
conn.get(f"{ATTESTATION_DIR}/actcred.out", f"{localdir}/actcred.out")
# (アテスター) 復号されたチャレンジが事前に生成した値と一致するか検証する
with open(f"{localdir}/actcred.out", "rb") as f:
actual = f.read()
print("Expected: {}".format(aiksecret.hex()))
print(" Actual: {}".format(actual.hex()))
if actual != aiksecret:
raise ValueError("Activate credential mismatched")
# (VM内) vTPMによって記録されたPCR値を取得する
nonce = generate_random_string(16).encode("ascii")
with open(f"{localdir}/nonce", "wb") as f:
f.write(nonce)
conn.put(f"{localdir}/nonce", f"{ATTESTATION_DIR}/nonce")
conn.run(f"sudo tpm2_quote --key-context {ATTESTATION_DIR}/ak.ctx --pcr-list sha1:0,1,2,3,4,5,6,7,8,9 --message {ATTESTATION_DIR}/pcr_quote.plain --signature {ATTESTATION_DIR}/pcr_quote.signature --qualification {ATTESTATION_DIR}/nonce --hash-algorithm sha256 --pcr {ATTESTATION_DIR}/pcr.bin")
conn.run(f"sudo chmod 644 {ATTESTATION_DIR}/pcr_quote.plain")
conn.run(f"sudo chmod 644 {ATTESTATION_DIR}/pcr_quote.signature")
conn.run(f"sudo chmod 644 {ATTESTATION_DIR}/pcr.bin")
conn.get(f"{ATTESTATION_DIR}/pcr_quote.plain", f"{localdir}/pcr_quote.plain")
conn.get(f"{ATTESTATION_DIR}/pcr_quote.signature", f"{localdir}/pcr_quote.signature")
conn.get(f"{ATTESTATION_DIR}/pcr.bin", f"{localdir}/pcr.bin")
# (アテスター) vTPMから取得したPCRとPCRの署名値をAKpubを用いて検証する
cmd = [
"tpm2_checkquote",
"-u",
f"{localdir}/ak.pub",
"-m",
f"{localdir}/pcr_quote.plain",
"-s",
f"{localdir}/pcr_quote.signature",
"--qualification",
f"{localdir}/nonce",
]
cmd = " ".join(cmd)
print(cmd)
subprocess.check_call(cmd, shell=True)
# TODO: PCRの値が事前に確認した期待する値になっているか確認する
print("AIK and PCR is verified!!")
# TODO: 全ての検証に成功したらFDEの鍵をVMに送り、OSの起動を継続する
if __name__ == "__main__":
attest_target_over_ssh(sys.argv[1])
Connecting to ubuntu@192.168.122.77
uname -a: Linux guest 6.8.0-coconut-svsm-sevsnp+ #6 SMP PREEMPT_DYNAMIC Mon Jun 10 08:19:20 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
localdir: /tmp/tmpbms63v_cattest
The AMD ARK was self-signed!
The AMD ASK was signed by the AMD ARK!
The VCEK was signed by the AMD ASK!
Reported TCB Boot Loader from certificate matches the attestation report.
Reported TCB TEE from certificate matches the attestation report.
Reported TCB SNP from certificate matches the attestation report.
Reported TCB Microcode from certificate matches the attestation report.
Chip ID from certificate matches the attestation report.
VEK signed the Attestation Report!
tpm2_createek:
loaded-key:
name: 000b3ab69d1b329beb57d6f99a33a770e8c6c97c13ed8f0a5220ecb307db50a9393b
qualified name: 000ba63febdf098d5d61d1b7213937c745cf2cd46f13aa53f3011a65782855bdeaed
tpm2_createak: loaded-key:
name: 000b3ab69d1b329beb57d6f99a33a770e8c6c97c13ed8f0a5220ecb307db50a9393b
qualified name: 000ba63febdf098d5d61d1b7213937c745cf2cd46f13aa53f3011a65782855bdeaed
akname: 000b3ab69d1b329beb57d6f99a33a770e8c6c97c13ed8f0a5220ecb307db50a9393b
tpm2_makecredential --tcti none --public=/tmp/tmpbms63v_cattest/ek.pub --secret=/tmp/tmpbms63v_cattest/ak_secret --credential-blob=/tmp/tmpbms63v_cattest/mkcred.out --name=000b3ab69d1b329beb57d6f99a33a770e8c6c97c13ed8f0a5220ecb307db50a9393b
WARN: Tool optionally uses SAPI. Continuing with tcti=none
837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa
certinfodata:6a4238306b31704557714f77686a7462
Expected: 6a4238306b31704557714f77686a7462
Actual: 6a4238306b31704557714f77686a7462
quoted: ff54434780180022000ba63febdf098d5d61d1b7213937c745cf2cd46f13aa53f3011a65782855bdeaed00107048435a375a6b546e4d5a44707069690000000000151041000000010000000001201706190016363600000001000403ff030000203d2e59b87be259e0a8284795a93d8861664dca61b86e01dc656da70105f3d656
signature:
alg: rsassa
sig: 5567a21b363bc245f5cab7b0100c4fa7d2254dcf240de1d198e4a68fcb5222ebd1f1668c82baebc4543c75de63ae01437b7ee75c77f58c7a68aa5ec6f0888480d15313d933d1a6d98688ed0f210b49878a64d716f7acca084b3b80b32e0ca8ffe1508bd969d62d78cbcfef77e4bd47dfe1ebbea9f3f912295df2bb1beaf35ea5c9f10effa7e5b7cac3ab777b3288dc7986b8bf2e5e100708383c187fd242d40a55aca7cb93d0435f126d97e41c89cec272107dca342dcb4e1279db5ddb7978cc56f4bf12bba42d0bba516dc6ecf12049a039191a474536ed94c3c9da60215b2ef0e665d08dee7c23fbf1bdcc40ef0a12e90bc218007a79a4894bef89b3202d33
pcrs:
sha1:
0 : 0x1A9995D2E91C691AEF5FC31402EF148282A3F3E0
1 : 0x4CCF0FC04FF0D55663537F8C9C365E478E9A927A
2 : 0xB2A83B0EBF2F8374299A5B2BDFC31EA955AD7236
3 : 0xB2A83B0EBF2F8374299A5B2BDFC31EA955AD7236
4 : 0x175F4319FD7AC683BF49F2E7B837630E4FA8603F
5 : 0xAE9CF30F68D5BE8CBB7FF6D898DD66FB3602D738
6 : 0xB2A83B0EBF2F8374299A5B2BDFC31EA955AD7236
7 : 0x1DBFFD353397AFBE91B5F90BFC53AF84D3E6B234
8 : 0xB83C498178CB7C2B3A3E81BEBB37355BE05E61E6
9 : 0x4BB31A8E9322E5F8F69776F1AD79D033956C3A08
calcDigest: 3d2e59b87be259e0a8284795a93d8861664dca61b86e01dc656da70105f3d656
tpm2_checkquote -u /tmp/tmpbms63v_cattest/ak.pub -m /tmp/tmpbms63v_cattest/pcr_quote.plain -s /tmp/tmpbms63v_cattest/pcr_quote.signature --qualification /tmp/tmpbms63v_cattest/nonce
sig: 5567a21b363bc245f5cab7b0100c4fa7d2254dcf240de1d198e4a68fcb5222ebd1f1668c82baebc4543c75de63ae01437b7ee75c77f58c7a68aa5ec6f0888480d15313d933d1a6d98688ed0f210b49878a64d716f7acca084b3b80b32e0ca8ffe1508bd969d62d78cbcfef77e4bd47dfe1ebbea9f3f912295df2bb1beaf35ea5c9f10effa7e5b7cac3ab777b3288dc7986b8bf2e5e100708383c187fd242d40a55aca7cb93d0435f126d97e41c89cec272107dca342dcb4e1279db5ddb7978cc56f4bf12bba42d0bba516dc6ecf12049a039191a474536ed94c3c9da60215b2ef0e665d08dee7c23fbf1bdcc40ef0a12e90bc218007a79a4894bef89b3202d33
AIK and PCR is verified!!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment