From d730fc40392e8866588ccacddff25c897f8cb587 Mon Sep 17 00:00:00 2001 From: "Moor.Zhou" Date: Mon, 17 Aug 2026 12:42:26 +0800 Subject: [PATCH] fix: validate Node signing authority precisely --- crates/pinset-core/src/node_trust.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/pinset-core/src/node_trust.rs b/crates/pinset-core/src/node_trust.rs index 1ae05e6..6a47397 100644 --- a/crates/pinset-core/src/node_trust.rs +++ b/crates/pinset-core/src/node_trust.rs @@ -142,10 +142,6 @@ fn trusted_keys() -> Result> { let mut verified = Vec::new(); for key in keys { let key = key?; - key.verify() - .map_err(|source| Error::NodeTrustStoreInvalid { - reason: format!("certificate self-signature verification failed: {source}"), - })?; let fingerprint = fingerprint(&key); if !allowlist.contains(fingerprint.as_str()) || !seen.insert(fingerprint) { return Err(Error::NodeTrustStoreInvalid { @@ -153,6 +149,7 @@ fn trusted_keys() -> Result> { .to_owned(), }); } + verify_signing_authority(&key)?; verified.push(key); } if seen.len() != allowlist.len() { @@ -163,6 +160,22 @@ fn trusted_keys() -> Result> { Ok(verified) } +fn verify_signing_authority(key: &SignedPublicKey) -> Result<()> { + // SAFETY: The pinned fingerprint authenticates the primary key packet. Historical Node.js + // certificates also carry third-party User ID certifications, which are not signing + // authority and cannot all be verified as self-signatures. Subkeys do extend signing + // authority, so every binding (and every signing back-signature required by the library) must + // still verify against the pinned primary key before the certificate is accepted. + for subkey in &key.public_subkeys { + subkey + .verify(&key.primary_key) + .map_err(|source| Error::NodeTrustStoreInvalid { + reason: format!("signing subkey binding verification failed: {source}"), + })?; + } + Ok(()) +} + fn armored_public_key_blocks(input: &str) -> Result> { // INVARIANT: nodejs/release-keys stores each certificate in its own armor block, while the // library's multi-key reader expects one armor payload. Preserve and validate every boundary