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}