Skip to main content

pg_proto/
integrations.rs

1//! Integration boundaries for platform-specific GSSAPI, Kerberos, and SSPI engines.
2
3use std::future::Future;
4
5use bytes::Bytes;
6
7/// Upgrades a transport after the typed GSSENC negotiation has accepted it.
8///
9/// Implementations can wrap MIT Kerberos, Heimdal, Windows SSPI, or a remote
10/// credential service without making `pg-proto` select or configure that stack.
11pub trait GssEncUpgrade<Stream> {
12    /// Transport produced after GSS encryption negotiation and handshake.
13    type SecuredStream;
14    /// Platform-specific negotiation or transport error.
15    type Error;
16
17    /// Performs the platform-specific encrypted transport handshake.
18    fn upgrade(
19        self,
20        stream: Stream,
21    ) -> impl Future<Output = Result<Self::SecuredStream, Self::Error>>;
22}
23
24/// One output from a recursive GSSAPI, Kerberos, or SSPI token engine.
25#[derive(Clone, Eq, PartialEq)]
26pub enum TokenStep {
27    /// Send this token and wait for another peer token.
28    Continue(Bytes),
29    /// Authentication completed, optionally with a final token to send.
30    Complete(Option<Bytes>),
31}
32
33impl std::fmt::Debug for TokenStep {
34    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            Self::Continue(token) => formatter
37                .debug_tuple("Continue")
38                .field(&format_args!("[REDACTED; {} bytes]", token.len()))
39                .finish(),
40            Self::Complete(token) => formatter
41                .debug_tuple("Complete")
42                .field(&token.as_ref().map(Bytes::len))
43                .finish(),
44        }
45    }
46}
47
48/// Platform-neutral token exchange consumed by the typed authentication loop.
49pub trait TokenAuthEngine {
50    /// Platform-specific credential, mechanism, or verification error.
51    type Error;
52
53    /// Produces the first token, if the selected mechanism requires one.
54    ///
55    /// # Errors
56    ///
57    /// Returns a platform-specific credential or mechanism error.
58    fn initial(&mut self) -> Result<TokenStep, Self::Error>;
59
60    /// Processes one peer token and either continues or completes authentication.
61    ///
62    /// # Errors
63    ///
64    /// Returns a platform-specific validation or credential error.
65    fn step(&mut self, peer_token: &[u8]) -> Result<TokenStep, Self::Error>;
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    struct ExampleEngine(bool);
73
74    impl TokenAuthEngine for ExampleEngine {
75        type Error = std::convert::Infallible;
76
77        fn initial(&mut self) -> Result<TokenStep, Self::Error> {
78            Ok(TokenStep::Continue(Bytes::from_static(b"initial")))
79        }
80
81        fn step(&mut self, peer_token: &[u8]) -> Result<TokenStep, Self::Error> {
82            self.0 = true;
83            Ok(TokenStep::Complete(Some(Bytes::copy_from_slice(
84                peer_token,
85            ))))
86        }
87    }
88
89    #[test]
90    fn recursive_token_engine_is_not_coupled_to_platform_credentials() {
91        let mut engine = ExampleEngine(false);
92        assert!(matches!(engine.initial().unwrap(), TokenStep::Continue(_)));
93        assert_eq!(
94            engine.step(b"challenge").unwrap(),
95            TokenStep::Complete(Some(Bytes::from_static(b"challenge")))
96        );
97        assert!(engine.0);
98    }
99}