fix(crypto/rustls): pin RA-TLS CertificateVerify to ECDSA P-384/SHA-384#940
fix(crypto/rustls): pin RA-TLS CertificateVerify to ECDSA P-384/SHA-384#940MichalTarnacki wants to merge 1 commit into
Conversation
MichalTarnacki
commented
Jul 6, 2026
| # | Assessed Severity | Bug | Call Chain / Location | Fix | Classification | Disposition Reason |
|---|---|---|---|---|---|---|
| 1 | Low | TLS CertificateVerify sig-scheme not pinned -- supported_verify_schemes() returns full default list | rustls::ClientConnection::process_tls_records -> ::verify_tls13_signature | Override provider.signature_verification_algorithms to only ECDSA_P384_SHA384 and return [ECDSA_NISTP384_SHA384] from supported_verify_schemes(). | weakness | The CertificateVerify signature scheme is not pinned, but the scheme is inseparable from the certificate's key type, and that key is already forced to P-384 by the app-layer RA-TLS callback, which rejects any non-P-384 SPKI before the transcript signature matters. rustls verifies CertificateVerify against the end-entity key, so a peer declaring RSA or P-256 simply fails the key/algorithm mismatch, and there is no weaker P-384 variant to downgrade to (ECDSA_NISTP384_SHA384 is the only one). Most decisively, producing any valid CertificateVerify under any scheme requires the peer's private key: without it no forgery is possible, and with it the attacker is a legitimate peer, so pinning the scheme adds nothing to authentication. No reachable path lets the unpinned scheme grant a new capability, so the residual is purely formal algorithm-agility hygiene. |
The RA-TLS Verifier accepted the full default list of signature schemes in supported_verify_schemes() and did not check the scheme used in the CertificateVerify message, leaving the handshake transcript signature algorithm unpinned. Restrict both the client- and server-side Verifier to ECDSA_NISTP384_SHA384: return only that scheme from supported_verify_schemes(), and reject any other scheme in verify_tls13_signature() with PeerIncompatible before verifying the transcript. This keeps the signature algorithm consistent with the cipher suite and key-exchange group already pinned in crypto_provider(). Signed-off-by: Michal Tarnacki <michal.tarnacki@intel.com> Co-authored-by: GitHub Copilot <noreply@github.com>
|
Where's this requirement coming from? MigTD shall support more algorithms. |
|
According to docs, P384 is only recommended, but should not be enforced |
sgrams
left a comment
There was a problem hiding this comment.
Please provide source of this requirement
|
You are right, from the spec: TDX Migration TD Design Guide Rev 0.9.3 (2025-08-21)
For "digital signature", the permitted options are RSA and ECDSA; P-384 However code already hard-force ECDSA P-384 / SHA-384 even though the spec
fn verify_signature(cert: &Certificate, verified_report: &[u8]) -> CryptoResult<()> {
let public_key = cert
.tbs_certificate
.subject_public_key_info
.subject_public_key
.as_bytes()
.ok_or_else(|| CryptoError::ParseCertificate)?;
let tbs = cert.tbs_certificate.to_der()?;
let signature = cert.signature_value.as_bytes().ok_or_else(|| CryptoError::ParseCertificate)?;
verify_public_key(verified_report, public_key)?;
ecdsa_verify(public_key, &tbs, signature) // <-- P-384 only
}
pub fn ecdsa_verify(public_key: &[u8], data: &[u8], signature: &[u8]) -> Result<()> {
let pk = UnparsedPublicKey::new(&signature::ECDSA_P384_SHA384_ASN1, public_key);
pk.verify(data, signature).map_err(|_e| Error::EcdsaVerify)
}
// ECDSA with SHA-384: 1.2.840.10045.4.3.3
const ECDSA_WITH_SHA384: &[u32] = &[1, 2, 840, 10045, 4, 3, 3];
// Only ECDSA-P384 with SHA384 signature is supported
match oid_arcs.as_slice() {
ECDSA_WITH_SHA384 => ecdsa::ecdsa_verify_with_algorithm(
public_key,
message,
signature,
&ecdsa::ECDSA_P384_SHA384_ASN1,
)
.map_err(|_| Error::SignatureVerification),
_ => {
// Unsupported algorithm
Err(Error::UnsupportedAlgorithm)
}
}
pub fn new() -> Result<Self> {
let rand = SystemRandom::new();
EcdsaKeyPair::generate_pkcs8(&signature::ECDSA_P384_SHA384_ASN1_SIGNING, &rand)
.map(|pk| Self { pk })
.map_err(|_| Error::GenerateKeyPair)
}Requester in src/migtd/src/spdm/spdm_req.rs and let config_info = common::SpdmConfigInfo {
// ...
base_asym_algo: SpdmBaseAsymAlgo::TPM_ALG_ECDSA_ECC_NIST_P384,
base_hash_algo: SpdmBaseHashAlgo::TPM_ALG_SHA_384,
dhe_algo: SpdmDheAlgo::SECP_384_R1,
aead_algo: SpdmAeadAlgo::AES_256_GCM,
req_asym_algo: SpdmReqAsymAlgo::TPM_ALG_ECDSA_ECC_NIST_P384,
// ...
};So this change is only hardening for the existing code, adding fail fast strategy. |
|
Maybe we should close this PR and open issue about adding support for other algorithms? |