Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/validate-kek-updates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,11 @@ jobs:
# Parse JSON to check both signature and payload
SIGNATURE_VALID=$(jq -r '.result.valid' "$OUTPUT_JSON")
PAYLOAD_VALID=$(jq -r '.result.payload_hash_valid' "$OUTPUT_JSON")
CONTENT_INFO_WRAPPED=$(jq -r '.result.content_info_wrapped' "$OUTPUT_JSON")
JSON_CONTENT=$(cat "$OUTPUT_JSON")
ALL_JSON="${ALL_JSON}### ${file}\n\`\`\`json\n${JSON_CONTENT}\n\`\`\`\n\n"

if [ "$SIGNATURE_VALID" = "true" ] && [ "$PAYLOAD_VALID" = "true" ]; then
if [ "$SIGNATURE_VALID" = "true" ] && [ "$PAYLOAD_VALID" = "true" ] && [ "$CONTENT_INFO_WRAPPED" = "false" ]; then
echo "✅ **PASS**: \`$file\`" >> $GITHUB_STEP_SUMMARY
echo " - Cryptographic Signature: ✅ VALID" >> $GITHUB_STEP_SUMMARY
echo " - Expected Payload: ✅ True" >> $GITHUB_STEP_SUMMARY
Expand All @@ -108,6 +109,16 @@ jobs:
PAYLOAD_HASH=$(jq -r '.result.payload_hash' "$OUTPUT_JSON")
echo " - Payload Hash: \`$PAYLOAD_HASH\`" >> $GITHUB_STEP_SUMMARY
# Don't fail on payload mismatch, just warn
elif [ "$CONTENT_INFO_WRAPPED" = "true" ]; then
echo "⚠️ **WARNING**: \`$file\`" >> $GITHUB_STEP_SUMMARY
echo " - Cryptographic Signature: ✅ VALID" >> $GITHUB_STEP_SUMMARY
echo " - Expected Payload: ✅ True" >> $GITHUB_STEP_SUMMARY
echo " - ContentInfo Wrapper: ⚠️ Detected" >> $GITHUB_STEP_SUMMARY
echo " > **Why this matters:** The PKCS\#7 signature contains an outer \`ContentInfo\` SEQUENCE" >> $GITHUB_STEP_SUMMARY
echo " > wrapping the \`SignedData\`. Older EDK2-based firmware expects raw \`SignedData\` in" >> $GITHUB_STEP_SUMMARY
echo " > \`WIN_CERTIFICATE_UEFI_GUID.CertData\` and will reject the update. Use" >> $GITHUB_STEP_SUMMARY
echo " > \`scripts/strip_content_info.py\` to remove the wrapper before submitting." >> $GITHUB_STEP_SUMMARY
# Don't fail on ContentInfo wrapper, just warn
else
echo "❌ **FAIL**: \`$file\`" >> $GITHUB_STEP_SUMMARY
echo " - Cryptographic Signature: ❌ INVALID" >> $GITHUB_STEP_SUMMARY
Expand Down
92 changes: 92 additions & 0 deletions scripts/strip_content_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# @file
#
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: BSD-2-Clause-Patent
##
"""Strip PKCS#7 ContentInfo wrappers from EFI auth variable signatures.

Some tooling expects the certificate payload to be raw SignedData instead of a
ContentInfo wrapper. This script rewrites an authenticated variable payload by
replacing cert_data with DER-encoded SignedData.
"""

import argparse
import logging
import pathlib
import sys

from edk2toollib.uefi.authenticated_variables_structure_support import EfiVariableAuthentication2
from pyasn1.codec.der.decoder import decode as der_decode
from pyasn1.codec.der.encoder import encode as der_encode
from pyasn1_modules import rfc2315


def pkcs7_get_signed_data_structure(signature: bytes) -> bytes:
"""Return DER-encoded SignedData from a DER PKCS#7 payload.

The input may be either ContentInfo(signedData) or SignedData directly.
"""
try:
content_info, _ = der_decode(signature, asn1Spec=rfc2315.ContentInfo())
content_type = content_info.getComponentByName("contentType")
if content_type != rfc2315.signedData:
raise ValueError("PKCS#7 payload is not signedData content")

signed_data, _ = der_decode(
content_info.getComponentByName("content"),
asn1Spec=rfc2315.SignedData(),
)
logging.info("Found PKCS#7 ContentInfo(signedData); stripping ContentInfo wrapper")
return der_encode(signed_data)
except Exception as content_info_error:
logging.debug("ContentInfo decode failed: %s", content_info_error)
logging.info("Input does not decode as ContentInfo; trying SignedData")

try:
signed_data, _ = der_decode(signature, asn1Spec=rfc2315.SignedData())
logging.info("Input already decodes as SignedData")
return der_encode(signed_data)
except Exception as signed_data_error:
raise ValueError(
"Signature is neither ContentInfo(signedData) nor SignedData"
) from signed_data_error


def strip_content_info(signed_payload: pathlib.Path) -> pathlib.Path:
"""Rewrite signed_payload with cert_data set to raw SignedData.

Returns the path of the rewritten output file.
"""
with open(signed_payload, "rb") as in_file:
auth_var = EfiVariableAuthentication2(decodefs=in_file)

# cert_data contains the PKCS#7 blob carried inside WIN_CERTIFICATE_UEFI_GUID.
signed_data = pkcs7_get_signed_data_structure(auth_var.auth_info.cert_data)
auth_var.auth_info.cert_data = signed_data

out_path = signed_payload.with_name(signed_payload.name + ".stripped")
with open(out_path, "wb") as out_file:
out_file.write(auth_var.encode())

logging.info("Stripped signed payload written to: %s", out_path)
return out_path


def main() -> int:
"""Parse CLI arguments and strip ContentInfo from the provided payload."""
parser = argparse.ArgumentParser(description="Strip ContentInfo from signed payload")
parser.add_argument("signed_payload", type=pathlib.Path, help="Path to signed payload")
args = parser.parse_args()

try:
strip_content_info(args.signed_payload)
except Exception as error:
logging.error("Failed to strip ContentInfo: %s", error)
return 1

return 0


if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
sys.exit(main())
42 changes: 42 additions & 0 deletions scripts/validate_kek.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
from datetime import datetime, timezone
from pathlib import Path

from pyasn1.codec.der.decoder import decode as der_decode
from pyasn1_modules import rfc2315

# Import validation functions from auth_var_tool
sys.path.insert(0, str(Path(__file__).parent))
# Import the verify function from auth_var_tool
Expand All @@ -27,6 +30,25 @@
EXPECTED_PAYLOAD_HASH = "5b85333c009d7ea55cbb6f11a5c2ff45ee1091a968504c929aed25c84674962f"


def has_content_info_wrapper(cert_data: bytes) -> bool:
"""Return True if cert_data is a PKCS#7 ContentInfo(signedData) envelope.

EDK2 firmware historically expects raw SignedData in WIN_CERTIFICATE_UEFI_GUID.CertData.
A ContentInfo outer SEQUENCE was not supported by EDK2 until recently:
https://github.com/microsoft/mu_tiano_plus/commit/37d3eb026a766b2405daae47e02094c2ec248646

Submitting a file with a ContentInfo wrapper may cause authentication failures on
older firmware.
"""
try:
content_info, remainder = der_decode(cert_data, asn1Spec=rfc2315.ContentInfo())
if remainder:
return False
return content_info.getComponentByName("contentType") == rfc2315.signedData
except Exception:
return False


def validate_single_kek(
kek_file: Path,
quiet: bool = False
Expand All @@ -47,6 +69,7 @@ def validate_single_kek(
"path": str(kek_file),
"valid": False,
"payload_hash_valid": False,
"content_info_wrapped": False,
"error": None,
"warnings": [],
"details": {}
Expand All @@ -70,6 +93,15 @@ def validate_single_kek(
logging.warning(f" Expected: {EXPECTED_PAYLOAD_HASH}")
logging.warning(f" Got: {payload_hash}")

# Check for ContentInfo wrapper in cert_data
file_result["content_info_wrapped"] = has_content_info_wrapper(auth_var.auth_info.cert_data)
if file_result["content_info_wrapped"]:
warning_msg = (
"cert_data contains a PKCS#7 ContentInfo wrapper. "
)
file_result["warnings"].append(warning_msg)
logging.warning(" [!] ContentInfo wrapper detected in cert_data!")

# Validate the file using auth_var_tool.verify_variable
# Create a namespace object with the required arguments
verify_args = argparse.Namespace(
Expand Down Expand Up @@ -183,6 +215,7 @@ def validate_kek_folder(
"path": str(bin_file),
"valid": False,
"payload_hash_valid": False,
"content_info_wrapped": False,
"error": None,
"warnings": [],
"details": {}
Expand All @@ -206,6 +239,15 @@ def validate_kek_folder(
logging.warning(f" Expected: {EXPECTED_PAYLOAD_HASH}")
logging.warning(f" Got: {payload_hash}")

# Check for ContentInfo wrapper in cert_data
file_result["content_info_wrapped"] = has_content_info_wrapper(auth_var.auth_info.cert_data)
if file_result["content_info_wrapped"]:
warning_msg = (
"cert_data contains a PKCS#7 ContentInfo wrapper."
)
file_result["warnings"].append(warning_msg)
logging.warning(" [!] ContentInfo wrapper detected in cert_data!")

# Validate the file using auth_var_tool.verify_variable
# Create a namespace object with the required arguments
verify_args = argparse.Namespace(
Expand Down
Loading