fips-core 0.3.58

Reusable FIPS mesh, endpoint, transport, and protocol library
Documentation
use super::*;

#[cfg(unix)]
#[test]
fn fmp_worker_send_reservation_owns_counter_header_and_cipher() {
    let local = Identity::generate();
    let peer = Identity::generate();
    let (mut sender, mut receiver) =
        make_test_fmp_session_pair(&local, &peer, [0x01; 8], [0x02; 8]);
    let their_index = SessionIndex::new(0xA0B0_C0D0);
    let flags = FLAG_SP | FLAG_CE;
    let payload_len = 32;

    let reservation = reserve_fmp_worker_send(&mut sender, their_index, flags, payload_len)
        .expect("counter reservation should succeed")
        .expect("established session should expose a send cipher");

    assert_eq!(reservation.counter, 0);
    assert_eq!(
        sender.current_send_counter(),
        1,
        "reservation is the only session mutation before worker dispatch"
    );
    assert_eq!(
        reservation.header,
        build_established_header(their_index, reservation.counter, flags, payload_len)
    );

    let plaintext = vec![0x5A; payload_len as usize];
    let mut ciphertext = plaintext.clone();
    reservation
        .cipher
        .seal_in_place_append_tag(
            crate::noise::CipherState::counter_to_nonce(reservation.counter),
            ring::aead::Aad::from(&reservation.header),
            &mut ciphertext,
        )
        .expect("worker-style FMP seal should succeed");
    assert_eq!(
        sender.current_send_counter(),
        1,
        "worker cipher use must not mutate the owning session"
    );
    assert_eq!(
        receiver
            .decrypt_with_replay_check_and_aad(
                &ciphertext,
                reservation.counter,
                &reservation.header,
            )
            .expect("receiver should accept worker-sealed packet"),
        plaintext
    );
}

#[cfg(unix)]
#[tokio::test]
async fn fmp_worker_target_fallback_consumes_one_inline_counter() {
    let mut node = make_node();
    node.encrypt_workers = Some(crate::node::encrypt_worker::EncryptWorkerPool::spawn(1));

    let transport_id = TransportId::new(77);
    let link_id = LinkId::new(88);
    let (packet_tx, _packet_rx) = packet_channel(8);
    let udp = UdpTransport::new(
        transport_id,
        None,
        crate::config::UdpConfig {
            bind_addr: Some("127.0.0.1:0".to_string()),
            ..Default::default()
        },
        packet_tx,
    );
    node.transports
        .insert(transport_id, TransportHandle::Udp(udp));

    let peer_full = Identity::generate();
    let peer_identity = PeerIdentity::from_pubkey_full(peer_full.pubkey_full());
    let peer_addr = *peer_identity.node_addr();
    let remote_addr = TransportAddr::from_string("127.0.0.1:9");
    let peer = make_active_test_peer(
        &node,
        &peer_full,
        peer_identity,
        transport_id,
        link_id,
        remote_addr,
        SessionIndex::new(11),
        SessionIndex::new(12),
    );
    node.peers.insert(peer_addr, peer);

    node.send_encrypted_link_message_with_ce(&peer_addr, b"fallback-inline", false)
        .await
        .expect_err("unstarted UDP transport should fail after inline encryption");

    let session = node
        .peers
        .get(&peer_addr)
        .and_then(|peer| peer.noise_session())
        .expect("peer should keep its session");
    assert_eq!(
        session.current_send_counter(),
        1,
        "worker-target fallback must not consume a worker counter before inline encryption"
    );
}