Skip to content

Commit 03fe3db

Browse files
authored
Merge branch 'main' into dependabot/pip/all-pip-dependencies-d47601e1aa
2 parents 09146de + 37d29ea commit 03fe3db

3 files changed

Lines changed: 146 additions & 1 deletion

File tree

.github/workflows/validate-kek-updates.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,11 @@ jobs:
9494
# Parse JSON to check both signature and payload
9595
SIGNATURE_VALID=$(jq -r '.result.valid' "$OUTPUT_JSON")
9696
PAYLOAD_VALID=$(jq -r '.result.payload_hash_valid' "$OUTPUT_JSON")
97+
CONTENT_INFO_WRAPPED=$(jq -r '.result.content_info_wrapped' "$OUTPUT_JSON")
9798
JSON_CONTENT=$(cat "$OUTPUT_JSON")
9899
ALL_JSON="${ALL_JSON}### ${file}\n\`\`\`json\n${JSON_CONTENT}\n\`\`\`\n\n"
99100
100-
if [ "$SIGNATURE_VALID" = "true" ] && [ "$PAYLOAD_VALID" = "true" ]; then
101+
if [ "$SIGNATURE_VALID" = "true" ] && [ "$PAYLOAD_VALID" = "true" ] && [ "$CONTENT_INFO_WRAPPED" = "false" ]; then
101102
echo "✅ **PASS**: \`$file\`" >> $GITHUB_STEP_SUMMARY
102103
echo " - Cryptographic Signature: ✅ VALID" >> $GITHUB_STEP_SUMMARY
103104
echo " - Expected Payload: ✅ True" >> $GITHUB_STEP_SUMMARY
@@ -108,6 +109,16 @@ jobs:
108109
PAYLOAD_HASH=$(jq -r '.result.payload_hash' "$OUTPUT_JSON")
109110
echo " - Payload Hash: \`$PAYLOAD_HASH\`" >> $GITHUB_STEP_SUMMARY
110111
# Don't fail on payload mismatch, just warn
112+
elif [ "$CONTENT_INFO_WRAPPED" = "true" ]; then
113+
echo "⚠️ **WARNING**: \`$file\`" >> $GITHUB_STEP_SUMMARY
114+
echo " - Cryptographic Signature: ✅ VALID" >> $GITHUB_STEP_SUMMARY
115+
echo " - Expected Payload: ✅ True" >> $GITHUB_STEP_SUMMARY
116+
echo " - ContentInfo Wrapper: ⚠️ Detected" >> $GITHUB_STEP_SUMMARY
117+
echo " > **Why this matters:** The PKCS\#7 signature contains an outer \`ContentInfo\` SEQUENCE" >> $GITHUB_STEP_SUMMARY
118+
echo " > wrapping the \`SignedData\`. Older EDK2-based firmware expects raw \`SignedData\` in" >> $GITHUB_STEP_SUMMARY
119+
echo " > \`WIN_CERTIFICATE_UEFI_GUID.CertData\` and will reject the update. Use" >> $GITHUB_STEP_SUMMARY
120+
echo " > \`scripts/strip_content_info.py\` to remove the wrapper before submitting." >> $GITHUB_STEP_SUMMARY
121+
# Don't fail on ContentInfo wrapper, just warn
111122
else
112123
echo "❌ **FAIL**: \`$file\`" >> $GITHUB_STEP_SUMMARY
113124
echo " - Cryptographic Signature: ❌ INVALID" >> $GITHUB_STEP_SUMMARY

scripts/strip_content_info.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# @file
2+
#
3+
# Copyright (c) Microsoft Corporation.
4+
# SPDX-License-Identifier: BSD-2-Clause-Patent
5+
##
6+
"""Strip PKCS#7 ContentInfo wrappers from EFI auth variable signatures.
7+
8+
Some tooling expects the certificate payload to be raw SignedData instead of a
9+
ContentInfo wrapper. This script rewrites an authenticated variable payload by
10+
replacing cert_data with DER-encoded SignedData.
11+
"""
12+
13+
import argparse
14+
import logging
15+
import pathlib
16+
import sys
17+
18+
from edk2toollib.uefi.authenticated_variables_structure_support import EfiVariableAuthentication2
19+
from pyasn1.codec.der.decoder import decode as der_decode
20+
from pyasn1.codec.der.encoder import encode as der_encode
21+
from pyasn1_modules import rfc2315
22+
23+
24+
def pkcs7_get_signed_data_structure(signature: bytes) -> bytes:
25+
"""Return DER-encoded SignedData from a DER PKCS#7 payload.
26+
27+
The input may be either ContentInfo(signedData) or SignedData directly.
28+
"""
29+
try:
30+
content_info, _ = der_decode(signature, asn1Spec=rfc2315.ContentInfo())
31+
content_type = content_info.getComponentByName("contentType")
32+
if content_type != rfc2315.signedData:
33+
raise ValueError("PKCS#7 payload is not signedData content")
34+
35+
signed_data, _ = der_decode(
36+
content_info.getComponentByName("content"),
37+
asn1Spec=rfc2315.SignedData(),
38+
)
39+
logging.info("Found PKCS#7 ContentInfo(signedData); stripping ContentInfo wrapper")
40+
return der_encode(signed_data)
41+
except Exception as content_info_error:
42+
logging.debug("ContentInfo decode failed: %s", content_info_error)
43+
logging.info("Input does not decode as ContentInfo; trying SignedData")
44+
45+
try:
46+
signed_data, _ = der_decode(signature, asn1Spec=rfc2315.SignedData())
47+
logging.info("Input already decodes as SignedData")
48+
return der_encode(signed_data)
49+
except Exception as signed_data_error:
50+
raise ValueError(
51+
"Signature is neither ContentInfo(signedData) nor SignedData"
52+
) from signed_data_error
53+
54+
55+
def strip_content_info(signed_payload: pathlib.Path) -> pathlib.Path:
56+
"""Rewrite signed_payload with cert_data set to raw SignedData.
57+
58+
Returns the path of the rewritten output file.
59+
"""
60+
with open(signed_payload, "rb") as in_file:
61+
auth_var = EfiVariableAuthentication2(decodefs=in_file)
62+
63+
# cert_data contains the PKCS#7 blob carried inside WIN_CERTIFICATE_UEFI_GUID.
64+
signed_data = pkcs7_get_signed_data_structure(auth_var.auth_info.cert_data)
65+
auth_var.auth_info.cert_data = signed_data
66+
67+
out_path = signed_payload.with_name(signed_payload.name + ".stripped")
68+
with open(out_path, "wb") as out_file:
69+
out_file.write(auth_var.encode())
70+
71+
logging.info("Stripped signed payload written to: %s", out_path)
72+
return out_path
73+
74+
75+
def main() -> int:
76+
"""Parse CLI arguments and strip ContentInfo from the provided payload."""
77+
parser = argparse.ArgumentParser(description="Strip ContentInfo from signed payload")
78+
parser.add_argument("signed_payload", type=pathlib.Path, help="Path to signed payload")
79+
args = parser.parse_args()
80+
81+
try:
82+
strip_content_info(args.signed_payload)
83+
except Exception as error:
84+
logging.error("Failed to strip ContentInfo: %s", error)
85+
return 1
86+
87+
return 0
88+
89+
90+
if __name__ == "__main__":
91+
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
92+
sys.exit(main())

scripts/validate_kek.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from datetime import datetime, timezone
1313
from pathlib import Path
1414

15+
from pyasn1.codec.der.decoder import decode as der_decode
16+
from pyasn1_modules import rfc2315
17+
1518
# Import validation functions from auth_var_tool
1619
sys.path.insert(0, str(Path(__file__).parent))
1720
# Import the verify function from auth_var_tool
@@ -27,6 +30,25 @@
2730
EXPECTED_PAYLOAD_HASH = "5b85333c009d7ea55cbb6f11a5c2ff45ee1091a968504c929aed25c84674962f"
2831

2932

33+
def has_content_info_wrapper(cert_data: bytes) -> bool:
34+
"""Return True if cert_data is a PKCS#7 ContentInfo(signedData) envelope.
35+
36+
EDK2 firmware historically expects raw SignedData in WIN_CERTIFICATE_UEFI_GUID.CertData.
37+
A ContentInfo outer SEQUENCE was not supported by EDK2 until recently:
38+
https://github.com/microsoft/mu_tiano_plus/commit/37d3eb026a766b2405daae47e02094c2ec248646
39+
40+
Submitting a file with a ContentInfo wrapper may cause authentication failures on
41+
older firmware.
42+
"""
43+
try:
44+
content_info, remainder = der_decode(cert_data, asn1Spec=rfc2315.ContentInfo())
45+
if remainder:
46+
return False
47+
return content_info.getComponentByName("contentType") == rfc2315.signedData
48+
except Exception:
49+
return False
50+
51+
3052
def validate_single_kek(
3153
kek_file: Path,
3254
quiet: bool = False
@@ -47,6 +69,7 @@ def validate_single_kek(
4769
"path": str(kek_file),
4870
"valid": False,
4971
"payload_hash_valid": False,
72+
"content_info_wrapped": False,
5073
"error": None,
5174
"warnings": [],
5275
"details": {}
@@ -70,6 +93,15 @@ def validate_single_kek(
7093
logging.warning(f" Expected: {EXPECTED_PAYLOAD_HASH}")
7194
logging.warning(f" Got: {payload_hash}")
7295

96+
# Check for ContentInfo wrapper in cert_data
97+
file_result["content_info_wrapped"] = has_content_info_wrapper(auth_var.auth_info.cert_data)
98+
if file_result["content_info_wrapped"]:
99+
warning_msg = (
100+
"cert_data contains a PKCS#7 ContentInfo wrapper. "
101+
)
102+
file_result["warnings"].append(warning_msg)
103+
logging.warning(" [!] ContentInfo wrapper detected in cert_data!")
104+
73105
# Validate the file using auth_var_tool.verify_variable
74106
# Create a namespace object with the required arguments
75107
verify_args = argparse.Namespace(
@@ -183,6 +215,7 @@ def validate_kek_folder(
183215
"path": str(bin_file),
184216
"valid": False,
185217
"payload_hash_valid": False,
218+
"content_info_wrapped": False,
186219
"error": None,
187220
"warnings": [],
188221
"details": {}
@@ -206,6 +239,15 @@ def validate_kek_folder(
206239
logging.warning(f" Expected: {EXPECTED_PAYLOAD_HASH}")
207240
logging.warning(f" Got: {payload_hash}")
208241

242+
# Check for ContentInfo wrapper in cert_data
243+
file_result["content_info_wrapped"] = has_content_info_wrapper(auth_var.auth_info.cert_data)
244+
if file_result["content_info_wrapped"]:
245+
warning_msg = (
246+
"cert_data contains a PKCS#7 ContentInfo wrapper."
247+
)
248+
file_result["warnings"].append(warning_msg)
249+
logging.warning(" [!] ContentInfo wrapper detected in cert_data!")
250+
209251
# Validate the file using auth_var_tool.verify_variable
210252
# Create a namespace object with the required arguments
211253
verify_args = argparse.Namespace(

0 commit comments

Comments
 (0)