# Manual SEV-SNP Verification with OpenSSL

This guide explains how to verify SEV-SNP attestation reports using only OpenSSL, without relying on `snpguest`. This is useful for understanding the verification process or when snpguest is not available.

## Prerequisites

- OpenSSL 1.1.1+ (with ECDSA P-384 support)
- Python 3 (for byte manipulation)
- curl (for fetching certificates)
- xxd (for hex dumps)

## Overview

The verification process involves:
1. Extracting the signature and signed data from the report
2. Converting AMD's signature format to DER format
3. Extracting the public key from VCEK
4. Verifying the signature with OpenSSL

## Step 1: Understand Report Structure

The SEV-SNP attestation report is 1184 bytes (0x4A0):

```
Offset    Size    Field
------    ----    -----
0x000     672     Signed data
0x2A0     144     Signature (R || S)
0x330     336     Reserved
```

The signature is ECDSA P-384, stored as:
- R: 72 bytes, little-endian (padded from 48 bytes)
- S: 72 bytes, little-endian (padded from 48 bytes)

## Step 2: Extract Components

```bash
# Create working directory
WORK_DIR=$(mktemp -d)
REPORT="report.bin"

# Extract signed data (first 672 bytes)
dd if="$REPORT" of="$WORK_DIR/signed_data.bin" bs=1 count=672

# Extract signature (144 bytes at offset 672)
dd if="$REPORT" of="$WORK_DIR/sig_raw.bin" bs=1 skip=672 count=144

# Extract R and S components
dd if="$WORK_DIR/sig_raw.bin" of="$WORK_DIR/r_le.bin" bs=1 count=72
dd if="$WORK_DIR/sig_raw.bin" of="$WORK_DIR/s_le.bin" bs=1 skip=72 count=72
```

## Step 3: Convert Signature to DER Format

AMD stores signatures in little-endian format, but OpenSSL expects big-endian DER format.

```python
#!/usr/bin/env python3
# convert_signature.py

import sys

def le_to_be_48(data):
    """Convert 72-byte LE to 48-byte BE (P-384 integer)"""
    # Reverse for big-endian
    be_data = data[::-1]
    # Strip leading zeros, keep 48 bytes
    first_nonzero = next((i for i, b in enumerate(be_data) if b != 0), len(be_data))
    trimmed = be_data[first_nonzero:]
    # Pad or truncate to 48 bytes
    if len(trimmed) < 48:
        trimmed = b'\x00' * (48 - len(trimmed)) + trimmed
    return trimmed[-48:]

def to_der_int(data):
    """Convert bytes to DER INTEGER"""
    # If high bit set, prepend 0x00
    if data[0] & 0x80:
        data = b'\x00' + data
    return bytes([0x02, len(data)]) + data

# Read R and S
with open(sys.argv[1], 'rb') as f:
    r_le = f.read()
with open(sys.argv[2], 'rb') as f:
    s_le = f.read()

# Convert to big-endian
r_be = le_to_be_48(r_le)
s_be = le_to_be_48(s_le)

# Create DER sequence
r_der = to_der_int(r_be)
s_der = to_der_int(s_be)
seq = r_der + s_der
sig_der = bytes([0x30, len(seq)]) + seq

# Write DER signature
with open(sys.argv[3], 'wb') as f:
    f.write(sig_der)

print(f"DER signature: {len(sig_der)} bytes")
```

Run the conversion:
```bash
python3 convert_signature.py "$WORK_DIR/r_le.bin" "$WORK_DIR/s_le.bin" "$WORK_DIR/signature.der"
```

## Step 4: Extract VCEK Public Key

```bash
# Extract public key from VCEK certificate
openssl x509 -in certs/vcek.pem -pubkey -noout > "$WORK_DIR/vcek_pub.pem"

# Verify it's an EC key
openssl ec -in "$WORK_DIR/vcek_pub.pem" -pubin -text -noout
```

Expected output should show "ASN1 OID: secp384r1" indicating P-384 curve.

## Step 5: Verify Signature

```bash
# Verify the signature
openssl dgst -sha384 \
    -verify "$WORK_DIR/vcek_pub.pem" \
    -signature "$WORK_DIR/signature.der" \
    "$WORK_DIR/signed_data.bin"
```

If successful, you'll see:
```
Verified OK
```

If it fails:
```
Verified NOK
```

## Complete Script

Here's a complete script combining all steps:

```bash
#!/bin/bash
# manual-verify.sh - Manually verify SEV-SNP report with OpenSSL

set -euo pipefail

REPORT="${1:?Usage: $0 <report.bin> <vcek.pem>}"
VCEK="${2:?Usage: $0 <report.bin> <vcek.pem>}"

WORK_DIR=$(mktemp -d)
trap "rm -rf $WORK_DIR" EXIT

echo "Extracting report components..."

# Extract signed data and signature
dd if="$REPORT" of="$WORK_DIR/signed.bin" bs=1 count=672 2>/dev/null
dd if="$REPORT" of="$WORK_DIR/r_le.bin" bs=1 skip=672 count=72 2>/dev/null
dd if="$REPORT" of="$WORK_DIR/s_le.bin" bs=1 skip=744 count=72 2>/dev/null

echo "Converting signature format..."

# Convert signature (Python inline)
python3 - "$WORK_DIR" <<'EOF'
import sys
work = sys.argv[1]

def convert(le_path, size=48):
    with open(le_path, 'rb') as f:
        data = f.read()[::-1]  # reverse for BE
    i = next((i for i, b in enumerate(data) if b), len(data))
    trimmed = data[i:] or b'\x00'
    return trimmed.zfill(size)[-size:].replace(b'\x00', b'', len(trimmed)-size) if len(trimmed) > size else (b'\x00'*(size-len(trimmed)) + trimmed)

def to_der_int(data):
    data = bytes(data)
    if data[0] & 0x80:
        data = b'\x00' + data
    return bytes([0x02, len(data)]) + data

r = bytearray(open(f'{work}/r_le.bin', 'rb').read())[::-1]
s = bytearray(open(f'{work}/s_le.bin', 'rb').read())[::-1]

# Strip leading zeros, keep 48 bytes
for arr in [r, s]:
    while len(arr) > 48 and arr[0] == 0:
        arr.pop(0)
    while len(arr) < 48:
        arr.insert(0, 0)

r_der = to_der_int(bytes(r[-48:]))
s_der = to_der_int(bytes(s[-48:]))
seq = r_der + s_der
sig = bytes([0x30, len(seq)]) + seq

open(f'{work}/sig.der', 'wb').write(sig)
EOF

echo "Extracting VCEK public key..."
openssl x509 -in "$VCEK" -pubkey -noout > "$WORK_DIR/pub.pem"

echo "Verifying signature..."
if openssl dgst -sha384 -verify "$WORK_DIR/pub.pem" \
    -signature "$WORK_DIR/sig.der" "$WORK_DIR/signed.bin"; then
    echo ""
    echo "✓ Report signature is VALID"
    exit 0
else
    echo ""
    echo "✗ Report signature is INVALID"
    exit 1
fi
```

## Verifying Certificate Chain Manually

### Verify ARK is Self-Signed

```bash
openssl verify -CAfile certs/ark.pem certs/ark.pem
# Expected: certs/ark.pem: OK
```

### Verify ASK is Signed by ARK

```bash
openssl verify -CAfile certs/ark.pem certs/ask.pem
# Expected: certs/ask.pem: OK
```

### Verify VCEK is Signed by ASK

```bash
# Create CA bundle
cat certs/ark.pem certs/ask.pem > certs/ca_bundle.pem

openssl verify -CAfile certs/ca_bundle.pem certs/vcek.pem
# Expected: certs/vcek.pem: OK
```

## Extracting Report Fields

To manually extract and display report fields:

```bash
# Version (offset 0, 4 bytes, little-endian)
echo -n "Version: "
xxd -p -s 0 -l 4 report.bin | fold -w2 | tac | tr -d '\n'
echo

# Policy (offset 8, 8 bytes)
echo -n "Policy: 0x"
xxd -p -s 8 -l 8 report.bin | fold -w2 | tac | tr -d '\n'
echo

# REPORT_DATA (offset 0x50, 64 bytes)
echo "Report Data:"
xxd -p -s 80 -l 64 report.bin | fold -w64

# MEASUREMENT (offset 0x90, 48 bytes)
echo "Measurement:"
xxd -p -s 144 -l 48 report.bin | tr -d '\n'
echo

# CHIP_ID (offset 0x1A0, 64 bytes)
echo "Chip ID:"
xxd -p -s 416 -l 64 report.bin | tr -d '\n'
echo
```

## Security Considerations

When performing manual verification:

1. **Always verify the full certificate chain** before trusting the VCEK
2. **Check certificate validity dates** using `openssl x509 -dates`
3. **Verify REPORT_DATA** matches your challenge/nonce
4. **Check POLICY.DEBUG bit** (bit 3) is not set
5. **Validate TCB versions** against your security requirements

## References

- [AMD SEV-SNP ABI Specification](https://www.amd.com/system/files/TechDocs/56860.pdf)
- [OpenSSL ECDSA Documentation](https://www.openssl.org/docs/man1.1.1/man3/ECDSA_sign.html)
- [RFC 5480 - ECC Subject Public Key Information](https://tools.ietf.org/html/rfc5480)
