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 NormalizedStrings, 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.

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(())
}

Structs

Contains the server public key, private key and salt. Second step of the server, next is SrpServer.

The final step of authentication. Contains the session key, and reconnect logic.

Creates and contains the username, password verifier, and salt values. First step of the server, next is SrpProof.