fix(core): require certificate-only PEM inputs
Verify Data Plane / gateway (push) Successful in 4m36s

This commit is contained in:
sechmachine
2026-08-12 14:32:48 +07:00
parent dc2cbdf4d7
commit cb94f4ad5d
2 changed files with 156 additions and 24 deletions
+86 -15
View File
@@ -72,6 +72,19 @@ fn capabilities() -> CapabilityProfile {
.expect("literal capability profile is valid")
}
const VALID_CERTIFICATE_PEM: &str = "-----BEGIN CERTIFICATE-----\nAQID\n-----END CERTIFICATE-----";
fn credential_json(certificate_chain_pem: &str, trust_bundle_pem: &str) -> Vec<u8> {
serde_json::to_vec(&serde_json::json!({
"client_device_id": "device",
"device_key_id": "key",
"certificate_chain_pem": certificate_chain_pem,
"trust_bundle_pem": trust_bundle_pem,
"expires_at": "2099-01-01T00:00:00Z",
}))
.expect("literal credential is JSON-encodable")
}
#[test]
fn strict_rc5_dtos_reject_duplicate_trailing_unknown_and_provider_fields() {
assert!(ConnectionManifest::decode(valid_manifest()).is_ok());
@@ -128,15 +141,16 @@ fn rc5_manifest_credential_and_authority_enforce_bounds_and_bindings() {
.validate_at("2026-08-12T00:00:00Z")
.expect("unexpired manifest");
let credential = NativeTunnelCredential::decode(
br#"{"client_device_id":"device","device_key_id":"key","certificate_chain_pem":"certificate","trust_bundle_pem":"root","expires_at":"2099-01-01T00:00:00Z"}"#,
)
let credential = NativeTunnelCredential::decode(&credential_json(
VALID_CERTIFICATE_PEM,
VALID_CERTIFICATE_PEM,
))
.expect("valid credential");
credential
.validate_at("2026-08-12T00:00:00Z")
.expect("unexpired credential");
assert!(NativeTunnelCredential::decode(
br#"{"client_device_id":"device","device_key_id":"key","certificate_chain_pem":"certificate","trust_bundle_pem":"root","client_private_key_pem":"forbidden","expires_at":"2099-01-01T00:00:00Z"}"#,
br#"{"client_device_id":"device","device_key_id":"key","certificate_chain_pem":"-----BEGIN CERTIFICATE-----\nAQID\n-----END CERTIFICATE-----","trust_bundle_pem":"-----BEGIN CERTIFICATE-----\nAQID\n-----END CERTIFICATE-----","client_private_key_pem":"forbidden","expires_at":"2099-01-01T00:00:00Z"}"#,
)
.is_err());
@@ -155,27 +169,84 @@ fn rc5_manifest_credential_and_authority_enforce_bounds_and_bindings() {
#[test]
fn native_tunnel_credential_rejects_private_key_pem_in_certificate_fields() {
for field in ["certificate_chain_pem", "trust_bundle_pem"] {
let credential = r#"{"client_device_id":"device","device_key_id":"key","certificate_chain_pem":"certificate","trust_bundle_pem":"root","expires_at":"2099-01-01T00:00:00Z"}"#
.to_owned()
.replacen(
&format!(r#""{field}":"{}""#, if field == "certificate_chain_pem" { "certificate" } else { "root" }),
&format!(r#""{field}":"-----BEGIN PRIVATE KEY-----\nsecret\n-----END PRIVATE KEY-----""#),
1,
);
let private_key = "-----BEGIN PRIVATE KEY-----\nAQID\n-----END PRIVATE KEY-----";
let credential = if field == "certificate_chain_pem" {
credential_json(private_key, VALID_CERTIFICATE_PEM)
} else {
credential_json(VALID_CERTIFICATE_PEM, private_key)
};
assert!(
NativeTunnelCredential::decode(credential.as_bytes()).is_err(),
NativeTunnelCredential::decode(&credential).is_err(),
"private key armor accepted in {field}"
);
}
}
#[test]
fn native_tunnel_credential_accepts_one_or_more_certificate_blocks() {
let two_certificates = format!("{VALID_CERTIFICATE_PEM}\n\n{VALID_CERTIFICATE_PEM}\n");
assert!(NativeTunnelCredential::decode(&credential_json(
&two_certificates,
VALID_CERTIFICATE_PEM,
))
.is_ok());
}
fn assert_credential_pem_rejected(invalid_values: &[&str]) {
for invalid in invalid_values {
assert!(
NativeTunnelCredential::decode(&credential_json(invalid, VALID_CERTIFICATE_PEM))
.is_err(),
"invalid certificate chain accepted"
);
assert!(
NativeTunnelCredential::decode(&credential_json(VALID_CERTIFICATE_PEM, invalid))
.is_err(),
"invalid trust bundle accepted"
);
}
}
#[test]
fn native_tunnel_credential_rejects_bare_certificate_text() {
assert_credential_pem_rejected(&["certificate"]);
}
#[test]
fn native_tunnel_credential_rejects_non_certificate_pem_labels() {
assert_credential_pem_rejected(&["-----BEGIN PUBLIC KEY-----\nAQID\n-----END PUBLIC KEY-----"]);
}
#[test]
fn native_tunnel_credential_rejects_malformed_or_incomplete_certificate_armor() {
assert_credential_pem_rejected(&[
"-----BEGIN CERTIFICATE-----\nAQID",
"-----BEGIN CERTIFICATE-----\n!!!!\n-----END CERTIFICATE-----",
"-----BEGIN CERTIFICATE-----\nAQI\n-----END CERTIFICATE-----",
"-----BEGIN CERTIFICATE-----\nAQJ=\n-----END CERTIFICATE-----",
]);
}
#[test]
fn native_tunnel_credential_rejects_junk_between_or_after_certificate_blocks() {
let between = format!("{VALID_CERTIFICATE_PEM}\njunk\n{VALID_CERTIFICATE_PEM}");
let after = format!("{VALID_CERTIFICATE_PEM}\njunk");
assert_credential_pem_rejected(&[&between, &after]);
}
#[test]
fn native_tunnel_credential_rejects_empty_certificate_blocks() {
assert_credential_pem_rejected(&["-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"]);
}
#[test]
fn expiry_mismatch_and_capability_escalation_are_rejected() {
let manifest = ConnectionManifest::decode(valid_manifest()).expect("valid manifest");
assert!(manifest.validate_at("2100-01-01T00:00:00Z").is_err());
let credential = NativeTunnelCredential::decode(
br#"{"client_device_id":"device","device_key_id":"key","certificate_chain_pem":"certificate","trust_bundle_pem":"root","expires_at":"2099-01-01T00:00:00Z"}"#,
)
let credential = NativeTunnelCredential::decode(&credential_json(
VALID_CERTIFICATE_PEM,
VALID_CERTIFICATE_PEM,
))
.expect("valid credential");
assert!(credential.validate_at("2099-01-01T00:00:00Z").is_err());