A safe MIT licensed binding to gssapi
see rfc2744 for more info
gssapi is a huge and complex beast that is also very old (like Computer Chronicles old). The Kerberos 5 mech is covered by an integration test suite (tests/test.sh) that runs the same in-process-KDC handshake and credential tests against all three supported implementations: MIT natively, Heimdal in a podman container, and the Apple GSS framework natively on macOS.
For a simpler cross platform interface to Kerberos 5 see cross-krb5.
The default is empty. None of the optional features build on every
implementation, so the only honest cross-platform default is no features —
each feature does exactly what it says, and enabling one the linked
implementation can't provide is a compile error, not a silent no-op. all
enables everything (MIT only).
iov—wrap_iov/unwrap_iovand theGssIovtypes. MIT + Heimdal (Apple's GSS framework has nogss_wrap_iov).localname—Name::local_name(POSIX local-name mapping). MIT + Heimdal (Apple has nogss_localname).store—Cred::store(store into the default ccache). MIT + Heimdal (Apple has nogss_store_cred).s4u— Kerberos S4U constrained delegation (Cred::impersonate,Cred::store_into, impersonator lookup). MIT only — Heimdal and Apple implement neithergss_acquire_cred_impersonate_namenorgss_store_cred_into.
To enable a feature only where it's available, select it in a target-specific dependency table rather than unconditionally:
[target.'cfg(not(target_os = "macos"))'.dependencies]
libgssapi = { version = "0.10", features = ["iov", "localname", "store"] }The build finds gssapi via pkg-config (preferring MIT over Heimdal),
falling back to searching standard library directories. Two env vars
override this:
LIBGSSAPI_IMPL=mit|heimdal|apple— force the implementation. Handy when both MIT and Heimdal are installed.LIBGSSAPI_PREFIX= colon-separated install prefixes — for installs pkg-config can't find. Each prefix adds<prefix>/includeto the header search and<prefix>/libto the library search.
This is the krb5 example verbatim (libgssapi/examples/krb5.rs); run it
with cargo run --example krb5 -- nfs@host.example.com. See the comment at
the top of that file for how to set up the Kerberos environment it needs.
use std::env::args;
use libgssapi::{
name::Name,
credential::{Cred, CredUsage},
error::Error,
context::{CtxFlags, ClientCtx, ServerCtx, SecurityContext},
util::Buf,
oid::{OidSet, GSS_NT_HOSTBASED_SERVICE, GSS_MECH_KRB5},
};
fn setup_server_ctx(
service_name: &[u8],
desired_mechs: &OidSet
) -> Result<(ServerCtx, Name), Error> {
println!("import name");
let name = Name::new(service_name, Some(GSS_NT_HOSTBASED_SERVICE))?;
let cname = name.canonicalize(Some(GSS_MECH_KRB5))?;
println!("canonicalize name for kerberos 5");
println!("server name: {}, server cname: {}", name, cname);
let server_cred = Cred::acquire(
Some(&cname), None, CredUsage::Accept, Some(desired_mechs)
)?;
println!("acquired server credentials: {:#?}", server_cred.info()?);
Ok((ServerCtx::new(Some(server_cred)), cname))
}
fn setup_client_ctx(
service_name: Name,
desired_mechs: &OidSet
) -> Result<ClientCtx, Error> {
let client_cred = Cred::acquire(
None, None, CredUsage::Initiate, Some(&desired_mechs)
)?;
println!("acquired default client credentials: {:#?}", client_cred.info()?);
Ok(ClientCtx::new(
Some(client_cred), service_name, CtxFlags::GSS_C_MUTUAL_FLAG, Some(GSS_MECH_KRB5)
))
}
fn run(service_name: &[u8]) -> Result<(), Error> {
let desired_mechs = OidSet::singleton(GSS_MECH_KRB5)?;
let (mut server_ctx, cname) = setup_server_ctx(service_name, &desired_mechs)?;
let mut client_ctx = setup_client_ctx(cname, &desired_mechs)?;
let mut server_tok: Option<Buf> = None;
loop {
match client_ctx.step(server_tok.as_ref().map(|b| &**b), None)? {
None => break,
Some(client_tok) => match server_ctx.step(&*client_tok, None)? {
None => break,
Some(tok) => { server_tok = Some(tok); }
}
}
}
println!("security context initialized successfully");
println!("client ctx info: {:#?}", client_ctx.info()?);
println!("server ctx info: {:#?}", server_ctx.info()?);
let secret_msg = client_ctx.wrap(true, b"super secret message")?;
let decoded_msg = server_ctx.unwrap(&*secret_msg)?;
println!("the decrypted message is: '{}'", String::from_utf8_lossy(&*decoded_msg));
Ok(())
}
fn main() {
let args = args().collect::<Vec<_>>();
if args.len() != 2 {
println!("usage: {}: <service@host>", args[0]);
} else {
match run(&args[1].as_bytes()) {
Ok(()) => (),
Err(e) => println!("{}", e),
}
}
}