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), None).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 ///
158 /// The name is the cryptographically authenticated client identity
159 /// (it can only be read once the context is established). The exact
160 /// string format is *not* guaranteed to be byte-identical across
161 /// platforms: on Unix it is the gssapi display name, while on Windows
162 /// it is the principal name (CNAME) from the Kerberos ticket as
163 /// formatted by SSPI. Both are typically `user@REALM`, but do not rely
164 /// on exact equality (e.g. realm casing) when comparing across OSes.
165 fn client(&mut self) -> Result<String>;
166}
167
168#[cfg(unix)]
169mod unix;
170
171#[cfg(unix)]
172use crate::unix::{
173 ClientCtx as ClientCtxImpl, Cred as CredImpl,
174 PendingClientCtx as PendingClientCtxImpl, PendingServerCtx as PendingServerCtxImpl,
175 ServerCtx as ServerCtxImpl
176};
177
178#[cfg(windows)]
179mod windows;
180
181#[cfg(windows)]
182use crate::windows::{
183 ClientCtx as ClientCtxImpl, PendingClientCtx as PendingClientCtxImpl,
184 PendingServerCtx as PendingServerCtxImpl, ServerCtx as ServerCtxImpl,
185 Cred as CredImpl
186};
187
188pub enum Step<C, T> {
189 Finished(C),
190 Continue(T),
191}
192
193#[derive(Debug)]
194pub struct Cred(CredImpl);
195
196impl K5Cred for Cred {
197 fn server_acquire(flags: AcceptFlags, principal: Option<&str>) -> Result<Cred> {
198 CredImpl::server_acquire(flags, principal).map(Cred)
199 }
200 fn client_acquire(flags: InitiateFlags, principal: Option<&str>) -> Result<Cred> {
201 CredImpl::client_acquire(flags, principal).map(Cred)
202 }
203}
204
205impl From<CredImpl> for Cred {
206 fn from(cred: CredImpl) -> Self {
207 Cred(cred)
208 }
209}
210
211#[cfg(unix)]
212impl From<libgssapi::credential::Cred> for Cred {
213 fn from(value: libgssapi::credential::Cred) -> Self {
214 Cred(CredImpl::from(value))
215 }
216}
217
218#[cfg(unix)]
219impl Into<libgssapi::credential::Cred> for Cred {
220 fn into(self) -> libgssapi::credential::Cred {
221 self.0.into()
222 }
223}
224
225#[cfg(windows)]
226impl Cred {
227 /// Wrap a raw SSPI credential handle, taking ownership of it.
228 ///
229 /// # Safety
230 ///
231 /// `handle` must be a live credential handle obtained from SSPI (e.g.
232 /// from `AcquireCredentialsHandle`) whose ownership is being transferred
233 /// to the returned `Cred`; dropping the `Cred` calls
234 /// `FreeCredentialsHandle` on it. The handle must not be used or freed
235 /// elsewhere. Passing a fabricated, borrowed, or already-freed handle is
236 /// undefined behavior. This mirrors libgssapi's `Cred::from_c`, which is
237 /// likewise unsafe for the same reason.
238 pub unsafe fn from_raw(handle: SecHandle) -> Cred {
239 Cred(CredImpl::from(handle))
240 }
241}
242
243#[cfg(windows)]
244impl Into<SecHandle> for Cred {
245 fn into(self) -> SecHandle {
246 self.0.into()
247 }
248}
249
250/// a partly initialized client context
251pub struct PendingClientCtx(PendingClientCtxImpl);
252
253impl PendingClientCtx {
254 /// Feed the server provided token to the client context,
255 /// performing one step of the initialization. If the
256 /// initialization is complete then return the established context
257 /// and optionally a final token that must be sent to the server,
258 /// otherwise return the pending context and another token to pass
259 /// to the server.
260 pub fn step(
261 self,
262 token: &[u8],
263 ) -> Result<
264 Step<
265 (ClientCtx, Option<impl Deref<Target = [u8]>>),
266 (PendingClientCtx, impl Deref<Target = [u8]>),
267 >,
268 > {
269 Ok(match self.0.step(token)? {
270 Step::Finished((ctx, tok)) => {
271 Step::Finished((ClientCtx(ctx), tok))
272 }
273 Step::Continue((ctx, tok)) => {
274 Step::Continue((PendingClientCtx(ctx), tok))
275 }
276 })
277 }
278}
279
280bitflags! {
281 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
282 pub struct InitiateFlags: u32 {
283 /// Windows only, use the sspi negotiate package instead of
284 /// the Kerberos package. Some Windows servers expect these
285 /// tokens instead of normal gssapi compatible tokens.
286 const NEGOTIATE_TOKEN = 0x1;
287 /// Opt out of mutual authentication. By default the client
288 /// requests mutual authentication and context establishment
289 /// fails unless the mechanism actually grants it; set this flag
290 /// to neither request nor require it.
291 const DISABLE_MUTUAL_AUTH = 0x2;
292 /// Opt out of confidentiality. By default the client requests
293 /// confidentiality, requires that it was granted, and permits
294 /// `wrap`/`wrap_iov` with `encrypt = true`. Set this flag to
295 /// neither request nor require it; `wrap`/`wrap_iov` with
296 /// `encrypt = true` then fail (the channel is integrity-only).
297 const DISABLE_CONFIDENTIALITY = 0x4;
298 }
299}
300
301impl Default for InitiateFlags {
302 /// The secure default: nothing is disabled, so both mutual
303 /// authentication and confidentiality are required.
304 fn default() -> Self {
305 InitiateFlags::empty()
306 }
307}
308
309/// A Kerberos client context
310#[derive(Debug)]
311pub struct ClientCtx(ClientCtxImpl);
312
313impl ClientCtx {
314 /// create a new client context for speaking to
315 /// `target_principal`. If `principal` is `None` then the
316 /// credentials of the user running current process will be
317 /// used. `target_principal` must be the service principal name of
318 /// the service you intend to communicate with. This should be an
319 /// spn as described by GSSAPI, e.g. `service/host@REALM`. If
320 /// present, `channel_bindings` is a service-specific channel
321 /// binding token which will be part of the initial communication
322 /// with the server.
323 ///
324 /// On success a `PendingClientCtx` and a token to be sent to the
325 /// server will be returned. The server and client may generate
326 /// multiple additional tokens, which must be passed to the their
327 /// respective `step` methods until the initialization is
328 /// complete.
329 pub fn new(
330 flags: InitiateFlags,
331 principal: Option<&str>,
332 target_principal: &str,
333 channel_bindings: Option<&[u8]>,
334 ) -> Result<(PendingClientCtx, impl Deref<Target = [u8]>)> {
335 let (pending, token) =
336 ClientCtxImpl::new(flags, principal, target_principal, channel_bindings)?;
337 Ok((PendingClientCtx(pending), token))
338 }
339
340 pub fn new_with_cred(
341 flags: InitiateFlags,
342 cred: Cred,
343 target_principal: &str,
344 channel_bindings: Option<&[u8]>
345 ) -> Result<(PendingClientCtx, impl Deref<Target=[u8]>)> {
346 let (pending, token) =
347 ClientCtxImpl::new_with_cred(flags, cred.0, target_principal, channel_bindings)?;
348 Ok((PendingClientCtx(pending), token))
349 }
350}
351
352impl K5Ctx for ClientCtx {
353 type Buffer = <ClientCtxImpl as K5Ctx>::Buffer;
354 type IOVBuffer = <ClientCtxImpl as K5Ctx>::IOVBuffer;
355
356 fn wrap(&mut self, encrypt: bool, msg: &[u8]) -> Result<Self::Buffer> {
357 K5Ctx::wrap(&mut self.0, encrypt, msg)
358 }
359
360 fn wrap_iov(&mut self, encrypt: bool, msg: BytesMut) -> Result<Self::IOVBuffer> {
361 K5Ctx::wrap_iov(&mut self.0, encrypt, msg)
362 }
363
364 fn unwrap(&mut self, msg: &[u8]) -> Result<Self::Buffer> {
365 K5Ctx::unwrap(&mut self.0, msg)
366 }
367
368 fn unwrap_iov(&mut self, len: usize, msg: &mut BytesMut) -> Result<BytesMut> {
369 K5Ctx::unwrap_iov(&mut self.0, len, msg)
370 }
371
372 fn ttl(&mut self) -> Result<Duration> {
373 K5Ctx::ttl(&mut self.0)
374 }
375}
376
377bitflags! {
378 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
379 pub struct AcceptFlags: u32 {
380 /// Windows only, use the sspi negotiate package instead of
381 /// the Kerberos package. Some Windows clients generate these
382 /// tokens instead of normal gssapi compatible tokens. This
383 /// likely won't be able to parse gssapi tokens, so only use
384 /// this if you know the client will be on windows sending
385 /// negotiate tokens.
386 const NEGOTIATE_TOKEN = 0x1;
387 /// Opt out of requiring mutual authentication. By default
388 /// accepting a context that did not establish mutual
389 /// authentication fails; set this flag to not require it.
390 const DISABLE_MUTUAL_AUTH = 0x2;
391 /// Opt out of confidentiality. By default the acceptor requires
392 /// that confidentiality was granted and permits `wrap`/`wrap_iov`
393 /// with `encrypt = true`. Set this flag to not require it;
394 /// `wrap`/`wrap_iov` with `encrypt = true` then fail.
395 const DISABLE_CONFIDENTIALITY = 0x4;
396 }
397}
398
399impl Default for AcceptFlags {
400 /// The secure default: nothing is disabled, so both mutual
401 /// authentication and confidentiality are required.
402 fn default() -> Self {
403 AcceptFlags::empty()
404 }
405}
406
407pub struct PendingServerCtx(PendingServerCtxImpl);
408
409impl PendingServerCtx {
410 pub fn step(
411 self,
412 token: &[u8],
413 ) -> Result<
414 Step<
415 (ServerCtx, Option<impl Deref<Target = [u8]>>),
416 (PendingServerCtx, impl Deref<Target = [u8]>),
417 >,
418 > {
419 Ok(match self.0.step(token)? {
420 Step::Finished((ctx, tok)) => {
421 Step::Finished((ServerCtx(ctx), tok))
422 }
423 Step::Continue((ctx, tok)) => {
424 Step::Continue((PendingServerCtx(ctx), tok))
425 }
426 })
427 }
428}
429
430/// A Kerberos server context
431#[derive(Debug)]
432pub struct ServerCtx(ServerCtxImpl);
433
434impl ServerCtx {
435 /// Create a new server context for `principal`, which should be
436 /// the service principal name assigned to the service the client
437 /// will be requesting. If it is left as `None` it will use the
438 /// user running the current process. The returned pending context
439 /// must be initiaized by exchanging one or more tokens with the
440 /// client before it can be used.
441 ///
442 /// If present, `channel_bindings` must match the bindings the client
443 /// supplied to `ClientCtx::new`; the mechanism rejects the context
444 /// otherwise. Note that with a `None` acceptor binding the mechanism
445 /// generally accepts whatever the client sent, so to actually enforce
446 /// channel binding the server must pass its own expected bindings here.
447 pub fn new(
448 flags: AcceptFlags,
449 principal: Option<&str>,
450 channel_bindings: Option<&[u8]>,
451 ) -> Result<PendingServerCtx> {
452 Ok(PendingServerCtx(ServerCtxImpl::new(flags, principal, channel_bindings)?))
453 }
454
455 pub fn new_with_cred(
456 flags: AcceptFlags,
457 cred: Cred,
458 channel_bindings: Option<&[u8]>,
459 ) -> Result<PendingServerCtx> {
460 Ok(PendingServerCtx(ServerCtxImpl::new_with_cred(
461 flags,
462 cred.0,
463 channel_bindings,
464 )?))
465 }
466}
467
468impl K5Ctx for ServerCtx {
469 type Buffer = <ServerCtxImpl as K5Ctx>::Buffer;
470 type IOVBuffer = <ServerCtxImpl as K5Ctx>::IOVBuffer;
471
472 fn wrap(&mut self, encrypt: bool, msg: &[u8]) -> Result<Self::Buffer> {
473 K5Ctx::wrap(&mut self.0, encrypt, msg)
474 }
475
476 fn wrap_iov(&mut self, encrypt: bool, msg: BytesMut) -> Result<Self::IOVBuffer> {
477 K5Ctx::wrap_iov(&mut self.0, encrypt, msg)
478 }
479
480 fn unwrap(&mut self, msg: &[u8]) -> Result<Self::Buffer> {
481 K5Ctx::unwrap(&mut self.0, msg)
482 }
483
484 fn unwrap_iov(&mut self, len: usize, msg: &mut BytesMut) -> Result<BytesMut> {
485 K5Ctx::unwrap_iov(&mut self.0, len, msg)
486 }
487
488 fn ttl(&mut self) -> Result<Duration> {
489 K5Ctx::ttl(&mut self.0)
490 }
491}
492
493impl K5ServerCtx for ServerCtx {
494 fn client(&mut self) -> Result<String> {
495 K5ServerCtx::client(&mut self.0)
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 // The flags are opt-out: the empty/default set must never disable a
504 // security property, so the easiest-to-write call is the secure one.
505 #[test]
506 fn secure_by_default() {
507 for f in [InitiateFlags::empty(), InitiateFlags::default()] {
508 assert!(!f.contains(InitiateFlags::DISABLE_MUTUAL_AUTH));
509 assert!(!f.contains(InitiateFlags::DISABLE_CONFIDENTIALITY));
510 }
511 for f in [AcceptFlags::empty(), AcceptFlags::default()] {
512 assert!(!f.contains(AcceptFlags::DISABLE_MUTUAL_AUTH));
513 assert!(!f.contains(AcceptFlags::DISABLE_CONFIDENTIALITY));
514 }
515 }
516}