diff --git a/easytier-core/src/peers/conn/peer_conn.rs b/easytier-core/src/peers/conn/peer_conn.rs index ee829bce..a49c4937 100644 --- a/easytier-core/src/peers/conn/peer_conn.rs +++ b/easytier-core/src/peers/conn/peer_conn.rs @@ -687,7 +687,8 @@ impl PeerConn { /// | Client role | Server role | Typical credential condition | Client auth level | Server auth level | Client sees server type | Server sees client type | /// | --- | --- | --- | --- | --- | --- | --- | /// | Admin | Admin | same network_secret, proof verified | NetworkSecretConfirmed | NetworkSecretConfirmed | Admin | Admin | - /// | Credential | Admin | client pubkey is trusted by admin | EncryptedUnauthenticated | PeerVerified | Admin | Credential | + /// | Credential | Admin | admin key is pinned and client key is trusted | PeerVerified | PeerVerified | Admin | Credential | + /// | Credential | Admin | client pubkey is trusted, admin key is not pinned | EncryptedUnauthenticated | PeerVerified | Admin | Credential | /// | Credential | Admin | client pubkey is unknown | handshake may fail | handshake reject | unknown | unknown | /// | Admin | SharedNode | pinned key match | PeerVerified | EncryptedUnauthenticated | SharedNode | Admin | /// | Admin | SharedNode | local has no pinned key requirement | EncryptedUnauthenticated | EncryptedUnauthenticated | SharedNode | Admin | @@ -696,8 +697,7 @@ impl PeerConn { /// /// Logic (in priority order): /// 1. **NetworkSecretConfirmed**: proof verification succeeds - /// 2. **PeerVerified**: pinned_pubkey matches and is in trusted list - /// (if no network_secret, pinned_pubkey must be in trusted list) + /// 2. **PeerVerified**: pinned_pubkey matches /// 3. **PeerVerified**: pubkey is in trusted list /// 4. **EncryptedUnauthenticated**: initiator without network_secret /// 5. **Reject**: none of the above @@ -727,16 +727,6 @@ impl PeerConn { "pinned remote static pubkey mismatch".to_owned(), )); } - // If no network_secret, pinned key must be in trusted list - if !has_network_secret - && !self - .context - .is_pubkey_trusted(remote_pubkey, remote_network_name) - { - return Err(Error::WaitRespError( - "pinned pubkey not in trusted list".to_owned(), - )); - } return Ok(SecureAuthLevel::PeerVerified); } diff --git a/easytier/src/tests/credential_tests.rs b/easytier/src/tests/credential_tests.rs index 91780929..4e9cef29 100644 --- a/easytier/src/tests/credential_tests.rs +++ b/easytier/src/tests/credential_tests.rs @@ -12,7 +12,7 @@ use easytier_core::process_runtime::CoreProcessRuntime; use crate::{ common::{ - config::{ConfigLoader, NetworkIdentity, TomlConfigLoader}, + config::{ConfigLoader, NetworkIdentity, PeerConfig, TomlConfigLoader}, global_ctx::GlobalCtxEvent, }, instance::test_instance::TestInstance as Instance, @@ -763,17 +763,24 @@ async fn wait_stable_single_visible_peer_on_admins( } } -/// Test 1: Basic credential node connectivity +/// Test 1: Basic credential node connectivity with and without an admin key pin /// Topology: Admin ← Credential /// Verifies that a credential node can connect to an admin node and appears in routes +#[rstest] +#[case(false)] +#[case(true)] #[tokio::test] #[serial_test::serial] -async fn credential_basic_connectivity() { +async fn credential_basic_connectivity(#[case] pin_admin: bool) { prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); // Create admin node let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_public_key = admin_config + .get_secure_mode() + .and_then(|config| config.local_public_key) + .unwrap(); let mut admin_inst = Instance::new_with_process_runtime(admin_config, process_runtime.clone()); admin_inst.run().await.unwrap(); @@ -786,11 +793,19 @@ async fn credential_basic_connectivity() { "fd00::2/64", ) .await; + if pin_admin { + cred_config.set_peers(vec![PeerConfig { + uri: "tcp://10.1.1.1:11010".parse().unwrap(), + peer_public_key: Some(admin_public_key), + }]); + } let mut cred_inst = Instance::new_with_process_runtime(cred_config, process_runtime.clone()); cred_inst.run().await.unwrap(); // Credential connects to admin - cred_inst.add_connector_url("tcp://10.1.1.1:11010".parse().unwrap()); + if !pin_admin { + cred_inst.add_connector_url("tcp://10.1.1.1:11010".parse().unwrap()); + } let cred_peer_id = cred_inst.peer_id(); let admin_peer_id = admin_inst.peer_id(); @@ -853,6 +868,66 @@ async fn credential_basic_connectivity() { drop_insts(vec![admin_inst, cred_inst]).await; } +/// A credential node must reject an admin whose Noise key does not match its pin. +#[tokio::test] +#[serial_test::serial] +async fn credential_rejects_incorrect_admin_pin() { + prepare_credential_network(); + let process_runtime = CoreProcessRuntime::new(); + + let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_public_key = admin_config + .get_secure_mode() + .and_then(|config| config.local_public_key) + .unwrap(); + let mut admin_inst = Instance::new_with_process_runtime(admin_config, process_runtime.clone()); + admin_inst.run().await.unwrap(); + + let cred_config = create_credential_config( + &admin_inst, + "cred", + Some("ns_c1"), + "10.144.144.2", + "fd00::2/64", + ) + .await; + let incorrect_admin_public_key = generate_secure_mode_config().local_public_key.unwrap(); + assert_ne!(incorrect_admin_public_key, admin_public_key); + cred_config.set_peers(vec![PeerConfig { + uri: "tcp://10.1.1.1:11010".parse().unwrap(), + peer_public_key: Some(incorrect_admin_public_key), + }]); + + let mut cred_inst = Instance::new_with_process_runtime(cred_config, process_runtime.clone()); + cred_inst.run().await.unwrap(); + + let admin_peer_id = admin_inst.peer_id(); + let cred_peer_id = cred_inst.peer_id(); + for _ in 0..5 { + let admin_peers = admin_inst.get_core_instance().connected_peers().await; + let cred_peers = cred_inst.get_core_instance().connected_peers().await; + let admin_routes = admin_inst.get_core_instance().route_snapshots().await; + let cred_routes = cred_inst.get_core_instance().route_snapshots().await; + + assert!(!admin_peers.contains(&cred_peer_id)); + assert!(!cred_peers.contains(&admin_peer_id)); + assert!( + !admin_routes + .iter() + .any(|route| route.peer_id == cred_peer_id) + ); + assert!( + !cred_routes + .iter() + .any(|route| route.peer_id == admin_peer_id) + ); + + tokio::time::sleep(Duration::from_secs(1)).await; + } + + drop_insts(vec![admin_inst, cred_inst]).await; +} + /// Test 5-6: Credential relay capability with allow_relay parameter /// Topology: Admin ← Credential_A, Admin ← Credential_B, Admin ← Credential_C(listener, allow_relay) /// Verifies routing behavior based on allow_relay flag: