Module wow_srp::server [−][src]
Expand description
Contains all functionality related to the server part, including the generation of values for the database.
All arrays are little endian.
See examples/server.rs
for how to interface with real clients.
Generating database values
When signing up a new user you want to take their username and password and
convert them into the values stored in your database.
This is done with the SrpVerifier::from_username_and_password
function.
This allows you to get the username, password verifier and salt values.
The SrpVerifier
page contains specifics.
The SrpVerifier
struct is created using NormalizedString
s, the reasoning is
explained on the normalized_string
page.
This could look something like this:
use wow_srp::server::SrpVerifier; use wow_srp::{error::NormalizedStringError, SALT_LENGTH, PASSWORD_VERIFIER_LENGTH}; use wow_srp::normalized_string::NormalizedString; // Generic function to tie everything together fn get_credentials_and_save_to_database() -> Result<(), NormalizedStringError> { // This would be gotten from the sign up page let username = NormalizedString::new("Alice")?; let password = NormalizedString::new("password123")?; let v = SrpVerifier::from_username_and_password(username, password); // The SrpVerifier is no longer needed after this // See NormalizedString for whether to save raw username or NormalizedString in Database save_values_to_database(v.username(), &v.password_verifier(), &v.salt()); // Return successful Ok(()) } fn save_values_to_database(username: &str, password_verifier: &[u8; PASSWORD_VERIFIER_LENGTH as usize], salt: &[u8; SALT_LENGTH as usize]) { // DB specific stuff here. }
Authenticating on the server
A functional example is included in examples/server.rs
.
This text focuses on a high level overview and important notices.
The Typestate pattern is used
in order to prevent incorrect use.
This means that whenever the next step of computation takes place, you call a function
taking self
, consuming the old object, and returning the new object.
When authenticating players attempting to connect to the server, we start from where we let go with the last example.
- First an
SrpVerifier
is created using the database values, - Then
SrpVerifier::into_proof
is called to convert it into anSrpProof
, - Finally
SrpProof::into_server
is called to convert it into anSrpServer
and a server proof.
The SrpServer
means that the client has been correctly authenticated
and can be sent the realmlist.
The SrpServer
also provides the possibility of authenticating reconnects.
The SrpServer
does NOT provide any rate limiting or time based expiration of the
ability to reconnect, this means that the implementer will need to ensure that clients
do not abuse the functionality.
The state machine goes like this:
SrpVerifier -> SrpProof -> mut SrpServer -|
+ ^ |
server_proof | |
|------|
Where you would keep a mutable SrpServer
around for reconnects and using the session key.
If any of the steps fail before the SrpServer
has been created all progress will be lost.
After the SrpServer
is created, an unlimited amount of reconnection attemps can be made,
and the call to SrpServer::verify_reconnection_attempt
will automatically refresh the
SrpServer::reconnect_challenge_data
.
use wow_srp::server::SrpVerifier; use wow_srp::normalized_string::NormalizedString; use wow_srp::{PASSWORD_VERIFIER_LENGTH, SALT_LENGTH, error::SrpError, error::NormalizedStringError, PublicKey}; fn server() -> Result<(), SrpError> { // Gotten from database let username = NormalizedString::new("A")?; let password_verifier = [ 106, 6, 11, 113, 103, 55, 49, 130, 210, 249, 178, 176, 73, 77, 229, 163, 127, 223, 122, 163, 245, 174, 60, 217, 151, 142, 169, 173, 208, 8, 152, 31, ]; let salt = [ 120, 156, 208, 137, 73, 108, 21, 91, 28, 22, 13, 255, 99, 116, 71, 102, 158, 70, 65, 189, 153, 244, 143, 13, 214, 200, 160, 94, 217, 112, 206, 125, ]; let verifier = SrpVerifier::from_database_values(username, password_verifier, salt); let proof = verifier.into_proof(); // Gotten from client let client_public_key = [ 105, 93, 211, 227, 214, 155, 247, 119, 156, 33, 156, 79, 15, 139, 100, 120, 1, 180, 32, 66, 165, 41, 175, 146, 216, 251, 25, 207, 18, 14, 35, 68, ]; // Can fail on invalid public key. See PublicKey for more info. let client_public_key = PublicKey::from_le_bytes(&client_public_key)?; let client_proof = [ 228, 40, 212, 74, 196, 143, 169, 148, 201, 150, 184, 123, 205, 40, 103, 234, 99, 155, 193, 7, ]; // Can fail on proof comparison which means the password is incorrect. // Send a failure packet and drop the connection. let (mut server, server_proof) = proof.into_server(client_public_key, client_proof)?; // If this passes the client is successfully authenticated. // Send the proof to client to prove that the server also knows the correct password. // Add the server object to e.g. a HashMap of authenticated users. // Client will now send a 'Send Realmlist' packet // Client wants to reconnect let reconnect_data = server.reconnect_challenge_data(); // Send to client // Get back from client let client_data = [ 8, 226, 88, 41, 231, 219, 29, 59, 127, 98, 180, 55, 32, 201, 135, 163, ]; let client_proof = [ 37, 167, 41, 153, 253, 156, 41, 174, 225, 125, 246, 158, 106, 248, 158, 232, 146, 8, 242, 164, ]; // Returns true if the proofs match, client is allowed to reconnect assert!(server.verify_reconnection_attempt(client_data, client_proof)); Ok(()) }