Skip to content

Commit a9bcb26

Browse files
author
Thomas Bahn
committed
Rename client entry point and reexport them
The entry point for the client implementation `client::ClientFirst` is now called `client::ScramClient`, similar to the `server` module. The entry points `client::ScramClient` and `server::ScramServer` are now reexported at the crate root, as well as other often used structs.
1 parent 843811e commit a9bcb26

File tree

5 files changed

+109
-36
lines changed

5 files changed

+109
-36
lines changed

README.md

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
# Salted Challenge Response Authentication Mechanism (SCRAM)
22

3-
This implementation currently provides a client for the SCRAM-SHA-256 mechanism according to
3+
This implementation provides a client and a server for the SCRAM-SHA-256 mechanism according to
44
RFC5802 and RFC7677. It doesn't support channel-binding.
55

66
[Read the documentation.](https://tomprogrammer.github.io/scram/scram/index.html)
77

88
# Limitations
99

10-
There is no server-side implementation of the SCRAM mechanism and no SHA-1 support. If you like to contribute or maintain them I appreciate that.
10+
The mandatory SCRAM-SHA-1 authentication mechanism is currently not implemented. This is also true
11+
for the *-PLUS variants, because channel-binding is not supported by this library. If you like to
12+
contribute or maintain them I appreciate that.
1113

1214
# Usage
1315

16+
## Client
17+
1418
A typical usage scenario is shown below. For a detailed explanation of the methods please
1519
consider their documentation. In productive code you should replace the unwrapping by proper
1620
error handling.
@@ -25,15 +29,15 @@ and `handle_server_final` on the different types advances the SCRAM handshake st
2529
Computing client messages never fails but processing server messages can result in failure.
2630

2731
```rust
28-
use scram::ClientFirst;
32+
use scram::ScramClient;
2933

3034
// This function represents your I/O implementation.
3135
fn send_and_receive(message: &str) -> String {
3236
unimplemented!()
3337
}
3438

3539
// Create a SCRAM state from the credentials.
36-
let scram = ClientFirst::new("user", "password", None).unwrap();
40+
let scram = ScramClient::new("user", "password", None).unwrap();
3741

3842
// Get the client message and reassign the SCRAM state.
3943
let (scram, client_first) = scram.client_first();
@@ -55,3 +59,68 @@ let server_final = send_and_receive(&client_final);
5559
// wasn't successful.
5660
let () = scram.handle_server_final(&server_final).unwrap();
5761
```
62+
63+
## Server
64+
65+
The server is created to respond to incoming challenges from a client. A typical usage pattern,
66+
with a default provider is shown below. In production, you would implement an AuthenticationProvider
67+
that could look up user credentials based on a username
68+
69+
The server and the client exchange four messages using the SCRAM mechanism. There is a rust type for
70+
each one of them. Calling the methods `handle_client_first()`, `server_first()`,
71+
`handle_client_final()` and `server_final()` on the different types advances the SCRAM handshake
72+
step by step. Computing server messages never fails (unless the source of randomness for the nonce
73+
fails), but processing client messages can result in failure.
74+
75+
The final step will not return an error if authentication failed, but will return an
76+
`AuthenticationStatus` which you can use to determine if authentication was successful or not.
77+
78+
```rust
79+
use scram::{ScramServer, AuthenticationStatus, AuthenticationProvider, PasswordInfo};
80+
81+
// Create a dummy authentication provider
82+
struct ExampleProvider;
83+
impl AuthenticationProvider for ExampleProvider {
84+
// Here you would look up password information for the the given username
85+
fn get_password_for(&self, username: &str) -> Option<PasswordInfo> {
86+
unimplemented!()
87+
}
88+
89+
}
90+
// These functions represent your I/O implementation.
91+
# #[allow(unused_variables)]
92+
fn receive() -> String {
93+
unimplemented!()
94+
}
95+
# #[allow(unused_variables)]
96+
fn send(message: &str) {
97+
unimplemented!()
98+
}
99+
100+
// Create a new ScramServer using the example authenication provider
101+
let scram_server = ScramServer::new(ExampleProvider{});
102+
103+
// Receive a message from the client
104+
let client_first = receive();
105+
106+
// Create a SCRAM state from the client's first message
107+
let scram_server = scram_server.handle_client_first(&client_first).unwrap();
108+
// Craft a response to the client's message and advance the SCRAM state
109+
// We could use our own source of randomness here, with `server_first_with_rng()`
110+
let (scram_server, server_first) = scram_server.server_first().unwrap();
111+
// Send our message to the client and read the response
112+
send(&server_first);
113+
let client_final = receive();
114+
115+
// Process the client's challenge and re-assign the SCRAM state. This could fail if the
116+
// message was poorly formatted
117+
let scram_server = scram_server.handle_client_final(&client_final).unwrap();
118+
119+
// Prepare the final message and get the authentication status
120+
let(status, server_final) = scram_server.server_final();
121+
// Send our final message to the client
122+
send(&server_final);
123+
124+
// Check if the client successfully authenticated
125+
assert_eq!(status, AuthenticationStatus::Authenticated);
126+
```

src/client.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ use utils::{hash_password, find_proofs};
1212
use error::{Error, Kind, Field};
1313
use NONCE_LENGTH;
1414

15+
#[deprecated(since = "0.2.0", note = "Please use `ScramClient` instead. (exported at crate root)")]
16+
pub type ClientFirst<'a> = ScramClient<'a>;
17+
1518
/// Parses a `server_first_message` returning a (none, salt, iterations) tuple if successful.
1619
fn parse_server_first(data: &str) -> Result<(&str, Vec<u8>, u16), Error> {
1720
if data.len() < 2 {
@@ -72,14 +75,14 @@ fn parse_server_final(data: &str) -> Result<Vec<u8>, Error> {
7275

7376
/// The initial state of the SCRAM mechanism. It's the entry point for a SCRAM handshake.
7477
#[derive(Debug)]
75-
pub struct ClientFirst<'a> {
78+
pub struct ScramClient<'a> {
7679
gs2header: Cow<'static, str>,
7780
password: &'a str,
7881
nonce: String,
7982
authcid: &'a str,
8083
}
8184

82-
impl<'a> ClientFirst<'a> {
85+
impl<'a> ScramClient<'a> {
8386
/// Constructs an initial state for the SCRAM mechanism using the provided credentials.
8487
///
8588
/// # Arguments
@@ -127,7 +130,7 @@ impl<'a> ClientFirst<'a> {
127130
})
128131
.collect();
129132

130-
ClientFirst {
133+
ScramClient {
131134
gs2header: gs2header,
132135
password: password,
133136
authcid: authcid,

src/lib.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! # Salted Challenge Response Authentication Mechanism (SCRAM)
22
//!
3-
//! This implementation currently provides a client for the SCRAM-SHA-256 mechanism according to
4-
//! RFC5802 and RFC7677. It doesn't support channel-binding.
3+
//! This implementation currently provides a client and a server for the SCRAM-SHA-256 mechanism
4+
//! according to RFC5802 and RFC7677. It doesn't support channel-binding.
55
//!
66
//! # Usage
77
//!
@@ -25,7 +25,7 @@
2525
//! processing server messages can result in failure.
2626
//!
2727
//! ``` rust,no_run
28-
//! use scram::client::ClientFirst;
28+
//! use scram::ScramClient;
2929
//!
3030
//! // This function represents your I/O implementation.
3131
//! # #[allow(unused_variables)]
@@ -34,7 +34,7 @@
3434
//! }
3535
//!
3636
//! // Create a SCRAM state from the credentials.
37-
//! let scram = ClientFirst::new("user", "password", None).unwrap();
37+
//! let scram = ScramClient::new("user", "password", None).unwrap();
3838
//!
3939
//! // Get the client message and reassign the SCRAM state.
4040
//! let (scram, client_first) = scram.client_first();
@@ -78,7 +78,7 @@
7878
//! if authentication was successful or not.
7979
//!
8080
//! ```rust,no_run
81-
//! use scram::server::{ScramServer, AuthenticationStatus, AuthenticationProvider, PasswordInfo};
81+
//! use scram::{ScramServer, AuthenticationStatus, AuthenticationProvider, PasswordInfo};
8282
//!
8383
//! // Create a dummy authentication provider
8484
//! struct ExampleProvider;
@@ -139,5 +139,7 @@ mod error;
139139
pub mod client;
140140
pub mod server;
141141

142+
pub use client::ScramClient;
142143
pub use error::{Error, Kind, Field};
144+
pub use server::{ScramServer, AuthenticationProvider, PasswordInfo, AuthenticationStatus};
143145
pub use utils::hash_password;

src/server.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub struct PasswordInfo {
2828
iterations: u16,
2929
}
3030

31-
/// The status of authenication after the final client message has been received by the server.
31+
/// The status of authentication after the final client message has been received by the server.
3232
#[derive(Clone, Copy, PartialEq, Debug)]
3333
pub enum AuthenticationStatus {
3434
/// The client has correctly authenticated, and has been authorized.
@@ -308,7 +308,6 @@ impl<'a, P: AuthenticationProvider> ClientFinal<'a, P> {
308308

309309
let server_signature_string = format!("v={}", base64::encode(server_signature.as_ref()));
310310
Ok(Some(server_signature_string))
311-
312311
}
313312
}
314313

tests/client_server.rs

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ impl server::AuthenticationProvider for TestProvider {
4646

4747
#[test]
4848
fn test_simple_success() {
49-
let scram_client = client::ClientFirst::new("user", "password", None).unwrap();
50-
let scram_server = server::ScramServer::new(TestProvider::new());
49+
let scram_client = ScramClient::new("user", "password", None).unwrap();
50+
let scram_server = ScramServer::new(TestProvider::new());
5151

5252
let (scram_client, client_first) = scram_client.client_first();
5353

@@ -62,13 +62,13 @@ fn test_simple_success() {
6262

6363
scram_client.handle_server_final(&server_final).unwrap();
6464

65-
assert_eq!(status, server::AuthenticationStatus::Authenticated);
65+
assert_eq!(status, AuthenticationStatus::Authenticated);
6666
}
6767

6868
#[test]
6969
fn test_bad_password() {
70-
let scram_client = client::ClientFirst::new("user", "badpassword", None).unwrap();
71-
let scram_server = server::ScramServer::new(TestProvider::new());
70+
let scram_client = ScramClient::new("user", "badpassword", None).unwrap();
71+
let scram_server = ScramServer::new(TestProvider::new());
7272

7373
let (scram_client, client_first) = scram_client.client_first();
7474

@@ -81,14 +81,14 @@ fn test_bad_password() {
8181
let scram_server = scram_server.handle_client_final(&client_final).unwrap();
8282
let (status, server_final) = scram_server.server_final();
8383

84-
assert_eq!(status, server::AuthenticationStatus::NotAuthenticated);
84+
assert_eq!(status, AuthenticationStatus::NotAuthenticated);
8585
assert!(scram_client.handle_server_final(&server_final).is_err());
8686
}
8787

8888
#[test]
8989
fn test_authorize_different() {
90-
let scram_client = client::ClientFirst::new("admin", "admin_password", Some("user")).unwrap();
91-
let scram_server = server::ScramServer::new(TestProvider::new());
90+
let scram_client = ScramClient::new("admin", "admin_password", Some("user")).unwrap();
91+
let scram_server = ScramServer::new(TestProvider::new());
9292

9393
let (scram_client, client_first) = scram_client.client_first();
9494

@@ -103,13 +103,13 @@ fn test_authorize_different() {
103103

104104
scram_client.handle_server_final(&server_final).unwrap();
105105

106-
assert_eq!(status, server::AuthenticationStatus::Authenticated);
106+
assert_eq!(status, AuthenticationStatus::Authenticated);
107107
}
108108

109109
#[test]
110110
fn test_authorize_fail() {
111-
let scram_client = client::ClientFirst::new("user", "password", Some("admin")).unwrap();
112-
let scram_server = server::ScramServer::new(TestProvider::new());
111+
let scram_client = ScramClient::new("user", "password", Some("admin")).unwrap();
112+
let scram_server = ScramServer::new(TestProvider::new());
113113

114114
let (scram_client, client_first) = scram_client.client_first();
115115

@@ -122,15 +122,15 @@ fn test_authorize_fail() {
122122
let scram_server = scram_server.handle_client_final(&client_final).unwrap();
123123
let (status, server_final) = scram_server.server_final();
124124

125-
assert_eq!(status, server::AuthenticationStatus::NotAuthorized);
125+
assert_eq!(status, AuthenticationStatus::NotAuthorized);
126126
assert!(scram_client.handle_server_final(&server_final).is_err());
127127
}
128128

129129
#[test]
130130
fn test_authorize_non_existent() {
131-
let scram_client = client::ClientFirst::new("admin", "admin_password", Some("nonexistent"))
131+
let scram_client = ScramClient::new("admin", "admin_password", Some("nonexistent"))
132132
.unwrap();
133-
let scram_server = server::ScramServer::new(TestProvider::new());
133+
let scram_server = ScramServer::new(TestProvider::new());
134134

135135
let (scram_client, client_first) = scram_client.client_first();
136136

@@ -143,14 +143,14 @@ fn test_authorize_non_existent() {
143143
let scram_server = scram_server.handle_client_final(&client_final).unwrap();
144144
let (status, server_final) = scram_server.server_final();
145145

146-
assert_eq!(status, server::AuthenticationStatus::NotAuthorized);
146+
assert_eq!(status, AuthenticationStatus::NotAuthorized);
147147
assert!(scram_client.handle_server_final(&server_final).is_err());
148148
}
149149

150150
#[test]
151151
fn test_invalid_user() {
152-
let scram_client = client::ClientFirst::new("nobody", "password", None).unwrap();
153-
let scram_server = server::ScramServer::new(TestProvider::new());
152+
let scram_client = ScramClient::new("nobody", "password", None).unwrap();
153+
let scram_server = ScramServer::new(TestProvider::new());
154154

155155
let (_, client_first) = scram_client.client_first();
156156

@@ -159,8 +159,8 @@ fn test_invalid_user() {
159159

160160
#[test]
161161
fn test_empty_username() {
162-
let scram_client = client::ClientFirst::new("", "password", None).unwrap();
163-
let scram_server = server::ScramServer::new(TestProvider::new());
162+
let scram_client = ScramClient::new("", "password", None).unwrap();
163+
let scram_server = ScramServer::new(TestProvider::new());
164164

165165
let (_, client_first) = scram_client.client_first();
166166

@@ -169,8 +169,8 @@ fn test_empty_username() {
169169

170170
#[test]
171171
fn test_empty_password() {
172-
let scram_client = client::ClientFirst::new("user", "", None).unwrap();
173-
let scram_server = server::ScramServer::new(TestProvider::new());
172+
let scram_client = ScramClient::new("user", "", None).unwrap();
173+
let scram_server = ScramServer::new(TestProvider::new());
174174

175175
let (scram_client, client_first) = scram_client.client_first();
176176

@@ -183,6 +183,6 @@ fn test_empty_password() {
183183
let scram_server = scram_server.handle_client_final(&client_final).unwrap();
184184
let (status, server_final) = scram_server.server_final();
185185

186-
assert_eq!(status, server::AuthenticationStatus::NotAuthenticated);
186+
assert_eq!(status, AuthenticationStatus::NotAuthenticated);
187187
assert!(scram_client.handle_server_final(&server_final).is_err());
188188
}

0 commit comments

Comments
 (0)