cross_krb5/
lib.rs

1//! # Cross Platform Kerberos 5 Interface
2//!
3//! cross-krb5 is a safe interface for Kerberos 5 services on Windows
4//! and Unix. It provides most of the flexibility of using gssapi and
5//! sspi directly with a unified cross platform api.
6//!
7//! As well as providing a uniform API, services using cross-krb5
8//! should interoperate across all the supported OSes transparently,
9//! and should interoperate with other services assuming they are not
10//! platform specific.
11//!
12//! # Example
13//! ```no_run
14//! use bytes::Bytes;
15//! use cross_krb5::{AcceptFlags, ClientCtx, InitiateFlags, K5Ctx, Step, ServerCtx};
16//! use std::{env::args, process::exit, sync::mpsc, thread};
17//!
18//! enum Msg {
19//!     Token(Bytes),
20//!     Msg(Bytes),
21//! }
22//!
23//! fn server(spn: String, input: mpsc::Receiver<Msg>, output: mpsc::Sender<Msg>) {
24//!     let mut server = ServerCtx::new(AcceptFlags::empty(), Some(&spn)).expect("new");
25//!     let mut server = loop {
26//!         let token = match input.recv().expect("expected data") {
27//!             Msg::Msg(_) => panic!("server not finished initializing"),
28//!             Msg::Token(t) => t,
29//!         };
30//!         match server.step(&*token).expect("step") {
31//!             Step::Finished((ctx, token)) => {
32//!                 if let Some(token) = token {
33//!                     output.send(Msg::Token(Bytes::copy_from_slice(&*token))).expect("send");
34//!                 }
35//!                 break ctx
36//!             },
37//!             Step::Continue((ctx, token)) => {
38//!                 output.send(Msg::Token(Bytes::copy_from_slice(&*token))).expect("send");
39//!                 server = ctx;
40//!             }
41//!         }
42//!     };
43//!     match input.recv().expect("expected data msg") {
44//!         Msg::Token(_) => panic!("unexpected extra token"),
45//!         Msg::Msg(secret_msg) => println!(
46//!             "{}",
47//!             String::from_utf8_lossy(&server.unwrap(&*secret_msg).expect("unwrap"))
48//!         ),
49//!     }
50//! }
51//!
52//! fn client(spn: &str, input: mpsc::Receiver<Msg>, output: mpsc::Sender<Msg>) {
53//!     let (mut client, token) =
54//!         ClientCtx::new(InitiateFlags::empty(), None, spn, None).expect("new");
55//!     output.send(Msg::Token(Bytes::copy_from_slice(&*token))).expect("send");
56//!     let mut client = loop {
57//!         let token = match input.recv().expect("expected data") {
58//!             Msg::Msg(_) => panic!("client not finished initializing"),
59//!             Msg::Token(t) => t,
60//!         };
61//!         match client.step(&*token).expect("step") {
62//!             Step::Finished((ctx, token)) => {
63//!                 if let Some(token) = token {
64//!                     output.send(Msg::Token(Bytes::copy_from_slice(&*token))).expect("send");
65//!                 }
66//!                 break ctx
67//!             },
68//!             Step::Continue((ctx, token)) => {
69//!                 output.send(Msg::Token(Bytes::copy_from_slice(&*token))).expect("send");
70//!                 client = ctx;
71//!             }
72//!         }
73//!     };
74//!     let msg = client.wrap(true, b"super secret message").expect("wrap");
75//!     output.send(Msg::Msg(Bytes::copy_from_slice(&*msg))).expect("send");
76//! }
77//!
78//! fn main() {
79//!     let args = args().collect::<Vec<_>>();
80//!     if args.len() != 2 {
81//!         println!("usage: {}: <service/host@REALM>", args[0]);
82//!         exit(1);
83//!     }
84//!     let spn = String::from(&args[1]);
85//!     let (server_snd, server_recv) = mpsc::channel();
86//!     let (client_snd, client_recv) = mpsc::channel();
87//!     thread::spawn(move || server(spn, server_recv, client_snd));
88//!     client(&args[1], client_recv, server_snd);
89//! }
90//! ```
91
92#[macro_use]
93extern crate bitflags;
94use anyhow::Result;
95use bytes::{Buf, BytesMut};
96use std::{ops::Deref, time::Duration};
97#[cfg(windows)]
98use ::windows::Win32::Security::Credentials::SecHandle;
99
100pub trait K5Cred: Sized {
101    fn server_acquire(
102        _flags: AcceptFlags,
103        principal: Option<&str>,
104    ) -> Result<Self>;
105
106    fn client_acquire(
107        _flags: InitiateFlags,
108        principal: Option<&str>,
109    ) -> Result<Self>;
110}
111
112pub trait K5Ctx {
113    type Buffer: Deref<Target = [u8]> + Send + Sync;
114    type IOVBuffer: Buf + Send + Sync;
115
116    /// Wrap the specified message for sending to the other side. If
117    /// `encrypt` is true then the contents will be encrypted. Even if
118    /// `encrypt` is false the integrity of the contents are
119    /// protected, if the message is altered in transit the other side
120    /// will know.
121    fn wrap(&mut self, encrypt: bool, msg: &[u8]) -> Result<Self::Buffer>;
122
123    /// Wrap data in place using the underlying wrap_iov facility. If
124    /// `encrypt` is true then the contents of `data` will be
125    /// encrypted in place. The returned buffer is NOT contiguous, and
126    /// as such you must use some kind of `writev` implementation to
127    /// properly send it. You can use tokio's `write_buf` directly, or
128    /// you can extract the iovecs for a direct call to `writev` using
129    /// `bytes::Buf::chunks_vectored`.
130    ///
131    /// If feature `iov` isn't enabled (it's in the default set)
132    /// then the underlying functionaly will be emulated, and there
133    /// will be no performance gain. `iov` is currently not
134    /// available on Mac OS, and compilation will fail if you try to
135    /// enable it. On OSes where it is supported using
136    /// wrap_iov/unwrap_iov is generally in the neighborhood of 2x to
137    /// 3x faster than wrap/unwrap.
138    fn wrap_iov(&mut self, encrypt: bool, msg: BytesMut) -> Result<Self::IOVBuffer>;
139
140    /// Unwrap the specified message returning it's decrypted and
141    /// verified contents
142    fn unwrap(&mut self, msg: &[u8]) -> Result<Self::Buffer>;
143
144    /// Unwrap in place the message at the beginning of the specified
145    /// `BytesMut` and then split it off and return it. This won't
146    /// copy or allocate, it just looks that way because the bytes
147    /// crate is awesome.
148    fn unwrap_iov(&mut self, len: usize, msg: &mut BytesMut) -> Result<BytesMut>;
149
150    /// Return the remaining time this session has to live
151    fn ttl(&mut self) -> Result<Duration>;
152}
153
154pub trait K5ServerCtx: K5Ctx {
155    /// Return the user principal name of the client context
156    /// associated with this server context.
157    fn client(&mut self) -> Result<String>;
158}
159
160#[cfg(unix)]
161mod unix;
162
163#[cfg(unix)]
164use crate::unix::{
165    ClientCtx as ClientCtxImpl, Cred as CredImpl,
166    PendingClientCtx as PendingClientCtxImpl, PendingServerCtx as PendingServerCtxImpl,
167    ServerCtx as ServerCtxImpl
168};
169
170#[cfg(windows)]
171mod windows;
172
173#[cfg(windows)]
174use crate::windows::{
175    ClientCtx as ClientCtxImpl, PendingClientCtx as PendingClientCtxImpl,
176    PendingServerCtx as PendingServerCtxImpl, ServerCtx as ServerCtxImpl,
177    Cred as CredImpl
178};
179
180pub enum Step<C, T> {
181    Finished(C),
182    Continue(T),
183}
184
185#[derive(Debug)]
186pub struct Cred(CredImpl);
187
188impl K5Cred for Cred {
189    fn server_acquire(flags: AcceptFlags, principal: Option<&str>) -> Result<Cred> {
190        CredImpl::server_acquire(flags, principal).map(Cred)
191    }
192    fn client_acquire(flags: InitiateFlags, principal: Option<&str>) -> Result<Cred> {
193        CredImpl::client_acquire(flags, principal).map(Cred)
194    }
195}
196
197impl From<CredImpl> for Cred {
198    fn from(cred: CredImpl) -> Self {
199        Cred(cred)
200    }
201}
202
203#[cfg(unix)]
204impl From<libgssapi::credential::Cred> for Cred {
205    fn from(value: libgssapi::credential::Cred) -> Self {
206        Cred(CredImpl::from(value))
207    }
208}
209
210#[cfg(unix)]
211impl Into<libgssapi::credential::Cred> for Cred {
212    fn into(self) -> libgssapi::credential::Cred {
213        self.0.into()
214    }
215}
216
217#[cfg(windows)]
218impl From<SecHandle> for Cred {
219    fn from(value: SecHandle) -> Self {
220        Cred(CredImpl::from(value))
221    }
222}
223
224#[cfg(windows)]
225impl Into<SecHandle> for Cred {
226    fn into(self) -> SecHandle {
227        self.0.into()
228    }
229}
230
231/// a partly initialized client context
232pub struct PendingClientCtx(PendingClientCtxImpl);
233
234impl PendingClientCtx {
235    /// Feed the server provided token to the client context,
236    /// performing one step of the initialization. If the
237    /// initialization is complete then return the established context
238    /// and optionally a final token that must be sent to the server,
239    /// otherwise return the pending context and another token to pass
240    /// to the server.
241    pub fn step(
242        self,
243        token: &[u8],
244    ) -> Result<
245        Step<
246            (ClientCtx, Option<impl Deref<Target = [u8]>>),
247            (PendingClientCtx, impl Deref<Target = [u8]>),
248        >,
249    > {
250        Ok(match self.0.step(token)? {
251            Step::Finished((ctx, tok)) => {
252                Step::Finished((ClientCtx(ctx), tok))
253            }
254            Step::Continue((ctx, tok)) => {
255                Step::Continue((PendingClientCtx(ctx), tok))
256            }
257        })
258    }
259}
260
261bitflags! {
262    pub struct InitiateFlags: u32 {
263        /// Windows only, use the sspi negotiate package instead of
264        /// the Kerberos package. Some Windows servers expect these
265        /// tokens instead of normal gssapi compatible tokens.
266        const NEGOTIATE_TOKEN = 0x1;
267    }
268}
269
270/// A Kerberos client context
271#[derive(Debug)]
272pub struct ClientCtx(ClientCtxImpl);
273
274impl ClientCtx {
275    /// create a new client context for speaking to
276    /// `target_principal`. If `principal` is `None` then the
277    /// credentials of the user running current process will be
278    /// used. `target_principal` must be the service principal name of
279    /// the service you intend to communicate with. This should be an
280    /// spn as described by GSSAPI, e.g. `service/host@REALM`. If
281    /// present, `channel_bindings` is a service-specific channel
282    /// binding token which will be part of the initial communication
283    /// with the server.
284    ///
285    /// On success a `PendingClientCtx` and a token to be sent to the
286    /// server will be returned. The server and client may generate
287    /// multiple additional tokens, which must be passed to the their
288    /// respective `step` methods until the initialization is
289    /// complete.
290    pub fn new(
291        flags: InitiateFlags,
292        principal: Option<&str>,
293        target_principal: &str,
294        channel_bindings: Option<&[u8]>,
295    ) -> Result<(PendingClientCtx, impl Deref<Target = [u8]>)> {
296        let (pending, token) =
297            ClientCtxImpl::new(flags, principal, target_principal, channel_bindings)?;
298        Ok((PendingClientCtx(pending), token))
299    }
300
301    pub fn new_with_cred(
302        cred: Cred,
303        target_principal: &str,
304        channel_bindings: Option<&[u8]>
305    ) -> Result<(PendingClientCtx, impl Deref<Target=[u8]>)> {
306        let (pending, token) =
307            ClientCtxImpl::new_with_cred(cred.0, target_principal, channel_bindings)?;
308        Ok((PendingClientCtx(pending), token))
309    }
310}
311
312impl K5Ctx for ClientCtx {
313    type Buffer = <ClientCtxImpl as K5Ctx>::Buffer;
314    type IOVBuffer = <ClientCtxImpl as K5Ctx>::IOVBuffer;
315
316    fn wrap(&mut self, encrypt: bool, msg: &[u8]) -> Result<Self::Buffer> {
317        K5Ctx::wrap(&mut self.0, encrypt, msg)
318    }
319
320    fn wrap_iov(&mut self, encrypt: bool, msg: BytesMut) -> Result<Self::IOVBuffer> {
321        K5Ctx::wrap_iov(&mut self.0, encrypt, msg)
322    }
323
324    fn unwrap(&mut self, msg: &[u8]) -> Result<Self::Buffer> {
325        K5Ctx::unwrap(&mut self.0, msg)
326    }
327
328    fn unwrap_iov(&mut self, len: usize, msg: &mut BytesMut) -> Result<BytesMut> {
329        K5Ctx::unwrap_iov(&mut self.0, len, msg)
330    }
331
332    fn ttl(&mut self) -> Result<Duration> {
333        K5Ctx::ttl(&mut self.0)
334    }
335}
336
337bitflags! {
338    pub struct AcceptFlags: u32 {
339        /// Windows only, use the sspi negotiate package instead of
340        /// the Kerberos package. Some Windows clients generate these
341        /// tokens instead of normal gssapi compatible tokens. This
342        /// likely won't be able to parse gssapi tokens, so only use
343        /// this if you know the client will be on windows sending
344        /// negotiate tokens.
345        const NEGOTIATE_TOKEN = 0x1;
346    }
347}
348
349pub struct PendingServerCtx(PendingServerCtxImpl);
350
351impl PendingServerCtx {
352    pub fn step(
353        self,
354        token: &[u8],
355    ) -> Result<
356        Step<
357            (ServerCtx, Option<impl Deref<Target = [u8]>>),
358            (PendingServerCtx, impl Deref<Target = [u8]>),
359        >,
360    > {
361        Ok(match self.0.step(token)? {
362            Step::Finished((ctx, tok)) => {
363                Step::Finished((ServerCtx(ctx), tok))
364            }
365            Step::Continue((ctx, tok)) => {
366                Step::Continue((PendingServerCtx(ctx), tok))
367            }
368        })
369    }
370}
371
372/// A Kerberos server context
373#[derive(Debug)]
374pub struct ServerCtx(ServerCtxImpl);
375
376impl ServerCtx {
377    /// Create a new server context for `principal`, which should be
378    /// the service principal name assigned to the service the client
379    /// will be requesting. If it is left as `None` it will use the
380    /// user running the current process. The returned pending context
381    /// must be initiaized by exchanging one or more tokens with the
382    /// client before it can be used.
383    pub fn new(flags: AcceptFlags, principal: Option<&str>) -> Result<PendingServerCtx> {
384        Ok(PendingServerCtx(ServerCtxImpl::new(flags, principal)?))
385    }
386
387    pub fn new_with_cred(cred: Cred) -> Result<PendingServerCtx> {
388        Ok(PendingServerCtx(ServerCtxImpl::new_with_cred(cred.0)?))
389    }
390}
391
392impl K5Ctx for ServerCtx {
393    type Buffer = <ServerCtxImpl as K5Ctx>::Buffer;
394    type IOVBuffer = <ServerCtxImpl as K5Ctx>::IOVBuffer;
395
396    fn wrap(&mut self, encrypt: bool, msg: &[u8]) -> Result<Self::Buffer> {
397        K5Ctx::wrap(&mut self.0, encrypt, msg)
398    }
399
400    fn wrap_iov(&mut self, encrypt: bool, msg: BytesMut) -> Result<Self::IOVBuffer> {
401        K5Ctx::wrap_iov(&mut self.0, encrypt, msg)
402    }
403
404    fn unwrap(&mut self, msg: &[u8]) -> Result<Self::Buffer> {
405        K5Ctx::unwrap(&mut self.0, msg)
406    }
407
408    fn unwrap_iov(&mut self, len: usize, msg: &mut BytesMut) -> Result<BytesMut> {
409        K5Ctx::unwrap_iov(&mut self.0, len, msg)
410    }
411
412    fn ttl(&mut self) -> Result<Duration> {
413        K5Ctx::ttl(&mut self.0)
414    }
415}
416
417impl K5ServerCtx for ServerCtx {
418    fn client(&mut self) -> Result<String> {
419        K5ServerCtx::client(&mut self.0)
420    }
421}