krb5/
krb5.rs

1/*
2An example program demonstrating mutual authentication and encryption
3between a server and a client using the Kerberos v5 gssapi mech. In
4order to run this example you must have a working kerberos
5environment, more specifically,
6
7* a valid krb5.conf
8* a working KDC for your realm
9* a service principal for the server e.g. nfs/server.example.com@EXAMPLE.COM
10* a keytab containing the service principal's key and that is readable by the user 
11  you want to run the example as. e.g. if not running as root set the environment 
12  variable KRB5_KTNAME=FILE:/path/to/keytab
13* a valid TGT from your KDC e.g. klist should print at least something like,
14
15Ticket cache: FILE:/tmp/krb5cc_1000_Ooxj5E
16Default principal: user@EXAMPLE.COM
17
18Valid starting       Expires              Service principal
1903/17/2020 18:10:05  03/18/2020 04:10:05  krbtgt/EXAMPLE.COM@EXAMPLE.COM
20	renew until 03/18/2020 18:10:05
21
22if it doesn't then you need to run kinit to renew your TGT.
23
24a successful run will look like,
25
26KRB5_KTNAME=FILE:/path/to/krb5.keytab cargo run --example krb5 nfs@host.example.com
27import name
28canonicalize name for kerberos 5
29server name: nfs@host.example.com, server cname: nfs/host.example.com@
30acquired server credentials
31acquired default client credentials
32security context initialized successfully
33client ctx info: CtxInfo {
34    source_name: user@EXAMPLE.COM,
35    target_name: nfs/host.example.com@,
36    lifetime: 35923,
37    mechanism: GSS_MECH_KRB5,
38    flags: GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG | GSS_C_TRANS_FLAG,
39    local: true,
40    open: true,
41}
42server ctx info: CtxInfo {
43    source_name: user@EXAMPLE.COM,
44    target_name: nfs/host.example.com@EXAMPLE.COM,
45    lifetime: 36223,
46    mechanism: GSS_MECH_KRB5,
47    flags: GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG | GSS_C_PROT_READY_FLAG | GSS_C_TRANS_FLAG,
48    local: false,
49    open: true,
50}
51the decrypted message is: 'super secret message'
52
53Depending on which implementation of gssapi you have the error
54messages it produces may not be very helpful (well, probably none of
55them actually produce helpful error messages). For example, if you
56can't read the services' keytab this is what MIT Kerberos will produce,
57
58KRB5_KTNAME=FILE:/path/to/unreadable/krb5.keytab cargo run --example krb5 nfs@host.example.com
59import name
60canonicalize name for kerberos 5
61server name: nfs@host.example.com, server cname: nfs/host.example.com@
62gssapi major error Unspecified GSS failure.  Minor code may provide more information
63gssapi minor error The routine must be called again to complete its function
64gssapi minor error The token's validity period has expired
65gssapi minor error A later token has already been processed
66
67Yep, that's pretty helpful. Thanks gssapi!
68
69When using MIT Kerberos you can get some better info by setting
70KRB5_TRACE=/dev/stderr (or whatever file you want the trace written
71to). I'm sure a similar thing exists for Heimdal, but I don't know what
72it is.
73
74*/
75
76use std::env::args;
77use libgssapi::{
78    name::Name,
79    credential::{Cred, CredUsage},
80    error::Error,
81    context::{CtxFlags, ClientCtx, ServerCtx, SecurityContext},
82    util::Buf,
83    oid::{OidSet, GSS_NT_HOSTBASED_SERVICE, GSS_MECH_KRB5},
84};
85
86fn setup_server_ctx(
87    service_name: &[u8],
88    desired_mechs: &OidSet
89) -> Result<(ServerCtx, Name), Error> {
90    println!("import name");
91    let name = Name::new(service_name, Some(&GSS_NT_HOSTBASED_SERVICE))?;
92    let cname = name.canonicalize(Some(&GSS_MECH_KRB5))?;
93    println!("canonicalize name for kerberos 5");
94    println!("server name: {}, server cname: {}", name, cname);
95    let server_cred = Cred::acquire(
96        Some(&cname), None, CredUsage::Accept, Some(desired_mechs)
97    )?;
98    println!("acquired server credentials: {:#?}", server_cred.info()?);
99    Ok((ServerCtx::new(Some(server_cred)), cname))
100}
101
102fn setup_client_ctx(
103    service_name: Name,
104    desired_mechs: &OidSet
105) -> Result<ClientCtx, Error> {
106    let client_cred = Cred::acquire(
107        None, None, CredUsage::Initiate, Some(&desired_mechs)
108    )?;
109    println!("acquired default client credentials: {:#?}", client_cred.info()?);
110    Ok(ClientCtx::new(
111        Some(client_cred), service_name, CtxFlags::GSS_C_MUTUAL_FLAG, Some(&GSS_MECH_KRB5)
112    ))
113}
114
115fn run(service_name: &[u8]) -> Result<(), Error> {
116    let desired_mechs = {
117        let mut s = OidSet::new()?;
118        s.add(&GSS_MECH_KRB5)?;
119        s
120    };
121    let (mut server_ctx, cname) = setup_server_ctx(service_name, &desired_mechs)?;
122    let mut client_ctx = setup_client_ctx(cname, &desired_mechs)?;
123    let mut server_tok: Option<Buf> = None;
124    loop {
125        match client_ctx.step(server_tok.as_ref().map(|b| &**b), None)? {
126            None => break,
127            Some(client_tok) => match server_ctx.step(&*client_tok)? {
128                None => break,
129                Some(tok) => { server_tok = Some(tok); }
130            }
131        }
132    }
133    println!("security context initialized successfully");
134    println!("client ctx info: {:#?}", client_ctx.info()?);
135    println!("server ctx info: {:#?}", server_ctx.info()?);
136    let secret_msg = client_ctx.wrap(true, b"super secret message")?;
137    let decoded_msg = server_ctx.unwrap(&*secret_msg)?;
138    println!("the decrypted message is: '{}'", String::from_utf8_lossy(&*decoded_msg));
139    Ok(())
140}
141
142fn main() {
143    let args = args().collect::<Vec<_>>();
144    if args.len() != 2 {
145        println!("usage: {}: <service@host>", args[0]);
146    } else {
147        match run(&args[1].as_bytes()) {
148            Ok(()) => (),
149            Err(e) => println!("{}", e),
150        }
151    }
152}