Line data Source code
1 : //! Simple Authentication and Security Layer.
2 : //!
3 : //! RFC: <https://datatracker.ietf.org/doc/html/rfc4422>.
4 : //!
5 : //! Reference implementation:
6 : //! * <https://github.com/postgres/postgres/blob/94226d4506e66d6e7cbf4b391f1e7393c1962841/src/backend/libpq/auth-sasl.c>
7 : //! * <https://github.com/postgres/postgres/blob/94226d4506e66d6e7cbf4b391f1e7393c1962841/src/interfaces/libpq/fe-auth.c>
8 :
9 : mod channel_binding;
10 : mod messages;
11 : mod stream;
12 :
13 : use std::io;
14 :
15 : pub(crate) use channel_binding::ChannelBinding;
16 : pub(crate) use messages::FirstMessage;
17 : pub(crate) use stream::{Outcome, authenticate};
18 : use thiserror::Error;
19 :
20 : use crate::error::{ReportableError, UserFacingError};
21 :
22 : /// Fine-grained auth errors help in writing tests.
23 : #[derive(Error, Debug)]
24 : pub(crate) enum Error {
25 : #[error("Unsupported authentication method: {0}")]
26 : BadAuthMethod(Box<str>),
27 :
28 : #[error("Channel binding failed: {0}")]
29 : ChannelBindingFailed(&'static str),
30 :
31 : #[error("Unsupported channel binding method: {0}")]
32 : ChannelBindingBadMethod(Box<str>),
33 :
34 : #[error("Bad client message: {0}")]
35 : BadClientMessage(&'static str),
36 :
37 : #[error("Internal error: missing digest")]
38 : MissingBinding,
39 :
40 : #[error("could not decode salt: {0}")]
41 : Base64(#[from] base64::DecodeError),
42 :
43 : #[error(transparent)]
44 : Io(#[from] io::Error),
45 : }
46 :
47 : impl UserFacingError for Error {
48 0 : fn to_string_client(&self) -> String {
49 0 : match self {
50 0 : Self::ChannelBindingFailed(m) => (*m).to_string(),
51 0 : Self::ChannelBindingBadMethod(m) => format!("unsupported channel binding method {m}"),
52 0 : _ => "authentication protocol violation".to_string(),
53 : }
54 0 : }
55 : }
56 :
57 : impl ReportableError for Error {
58 0 : fn get_error_kind(&self) -> crate::error::ErrorKind {
59 0 : match self {
60 0 : Error::BadAuthMethod(_) => crate::error::ErrorKind::User,
61 0 : Error::ChannelBindingFailed(_) => crate::error::ErrorKind::User,
62 0 : Error::ChannelBindingBadMethod(_) => crate::error::ErrorKind::User,
63 0 : Error::BadClientMessage(_) => crate::error::ErrorKind::User,
64 0 : Error::MissingBinding => crate::error::ErrorKind::Service,
65 0 : Error::Base64(_) => crate::error::ErrorKind::ControlPlane,
66 0 : Error::Io(_) => crate::error::ErrorKind::ClientDisconnect,
67 : }
68 0 : }
69 : }
70 :
71 : /// A convenient result type for SASL exchange.
72 : pub(crate) type Result<T> = std::result::Result<T, Error>;
73 :
74 : /// A result of one SASL exchange.
75 : #[must_use]
76 : pub(crate) enum Step<T, R> {
77 : /// We should continue exchanging messages.
78 : Continue(T, String),
79 : /// The client has been authenticated successfully.
80 : Success(R, String),
81 : /// Authentication failed (reason attached).
82 : Failure(&'static str),
83 : }
84 :
85 : /// Every SASL mechanism (e.g. [SCRAM](crate::scram)) is expected to implement this trait.
86 : pub(crate) trait Mechanism: Sized {
87 : /// What's produced as a result of successful authentication.
88 : type Output;
89 :
90 : /// Produce a server challenge to be sent to the client.
91 : /// This is how this method is called in PostgreSQL (`libpq/sasl.h`).
92 : fn exchange(self, input: &str) -> Result<Step<Self, Self::Output>>;
93 : }
|