libgssapi 0.11.0

A safe binding to gssapi
Documentation
//! Integration test: mutual auth handshake + wrap/unwrap roundtrip against
//! a real (in-process) MIT krb5 KDC. `apply_env()` returns a guard that
//! serializes the process-wide env it sets, so this runs safely under a
//! plain `cargo test`.

mod common;

use common::TestKdc;

use libgssapi::context::{ClientCtx, CtxFlags, SecurityContext, ServerCtx};
use libgssapi::credential::{Cred, CredUsage};
use libgssapi::name::Name;
use libgssapi::oid::{GSS_MECH_KRB5, GSS_NT_HOSTBASED_SERVICE, GSS_NT_USER_NAME, OidSet};
use libgssapi::util::Buf;

#[test]
fn mutual_auth_handshake_and_wrap() {
    let kdc = TestKdc::new();
    kdc.add_principal_with_password("testuser", "testpass");
    kdc.add_principal_random_key("nfs/test.example.com");
    kdc.export_keytab("nfs/test.example.com");
    kdc.kinit("testuser", "testpass");
    let _env = kdc.apply_env();

    let desired_mechs = OidSet::singleton(GSS_MECH_KRB5).unwrap();

    let target = Name::new(b"nfs@test.example.com", Some(GSS_NT_HOSTBASED_SERVICE)).unwrap();
    let cname = target.canonicalize(Some(GSS_MECH_KRB5)).unwrap();

    let server_cred = Cred::acquire(
        Some(&cname),
        None,
        CredUsage::Accept,
        Some(&desired_mechs),
    )
    .unwrap_or_else(|e| panic!("acquire server cred: {e}"));
    let mut server_ctx = ServerCtx::new(Some(server_cred));

    // Acquire by explicit name, not None: a None initiator name makes Apple's
    // GSS.framework enumerate identities (incl. PKINIT), probing the keychain
    // for certificates and hanging on a secure-input prompt in an interactive
    // session. Naming testuser sends Heimdal straight to its ccache.
    let client_name = Name::new(b"testuser", Some(GSS_NT_USER_NAME))
        .unwrap()
        .canonicalize(Some(GSS_MECH_KRB5))
        .unwrap();
    let client_cred =
        Cred::acquire(Some(&client_name), None, CredUsage::Initiate, Some(&desired_mechs))
            .expect("acquire client cred");
    let mut client_ctx = ClientCtx::new(
        Some(client_cred),
        cname,
        CtxFlags::GSS_C_MUTUAL_FLAG,
        Some(GSS_MECH_KRB5),
    );

    let mut server_tok: Option<Buf> = None;
    let mut steps = 0;
    loop {
        steps += 1;
        assert!(steps < 10, "handshake did not converge");
        let client_out = client_ctx
            .step(server_tok.as_ref().map(|b| &**b), None)
            .unwrap_or_else(|e| panic!("client step {steps}: {e}"));
        match client_out {
            None => break,
            Some(client_tok) => {
                let server_out = server_ctx
                    .step(&client_tok, None)
                    .unwrap_or_else(|e| panic!("server step {steps}: {e}"));
                match server_out {
                    None => break,
                    Some(tok) => server_tok = Some(tok),
                }
            }
        }
    }

    assert!(client_ctx.is_complete());
    assert!(server_ctx.is_complete());

    let secret = b"super secret message";
    let wrapped = client_ctx.wrap(true, secret).expect("wrap");
    let unwrapped = server_ctx.unwrap(&wrapped).expect("unwrap");
    assert_eq!(&*unwrapped, secret);

    // Round-trip in the other direction too.
    let reply = b"and a reply";
    let wrapped = server_ctx.wrap(true, reply).expect("server wrap");
    let unwrapped = client_ctx.unwrap(&wrapped).expect("client unwrap");
    assert_eq!(&*unwrapped, reply);
}