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}