#sasl #scram #mechanism #response #challenge #client-server

no-std scram-rs

Salted Challenge Response Authentication Mechanism (SCRAM) SASL mechanism, a library which implements SCRAM logic for Rust and C languages

15 unstable releases (5 breaking)

0.15.0 Mar 5, 2025
0.13.2 Sep 14, 2024
0.12.0 Jul 6, 2024
0.11.0 Feb 13, 2024
0.1.2 Mar 27, 2021

#73 in Authentication

Download history 49/week @ 2024-11-20 45/week @ 2024-11-27 58/week @ 2024-12-04 75/week @ 2024-12-11 24/week @ 2024-12-18 33/week @ 2024-12-25 14/week @ 2025-01-01 91/week @ 2025-01-08 77/week @ 2025-01-15 73/week @ 2025-01-22 13/week @ 2025-01-29 66/week @ 2025-02-05 55/week @ 2025-02-12 43/week @ 2025-02-19 263/week @ 2025-02-26 206/week @ 2025-03-05

569 downloads per month

EUPL-1.2

295KB
6K SLoC

Scram-rs

This is NOT an Open Source software! This is Sources Available or Sources Disclosed software or Fairuse software!!! If this is a concern, please don't use this crate.

This crate does not include/use AI generated code! The AI generated code is prohibited.

v 0.15

A SCRAM-SHA1, SCRAM-SHA256, SCRAM-SHA512, SCRAM-SHA256-PLUS client and server and C language bindings.

License:

Sources are available under: EUPL-1.2

Supports:

  • SHA-1 hasher
  • SHA-256 hasher (tested with Postfix Dovecot SASL)
  • SHA-512 hasher
  • Client/Server sync
  • Server Channel Binding 256 untested (user must implement the trait to provide necessary data)
  • Client Channel Binding 256 untested
  • A support of async which allows to integrate it in async code or use with async
  • Client/Server key (custom)
  • Error handling server-error RFC5802 (e=server-error-value)
  • Dynamic server instance i.e store the instance as dyn object instead of the generic struct
  • Initialize the Scram Client/Server with borrowed or consumed instances.
  • NO_STD support (untested)
  • C language bindings

Does not support:

  • authzid (a=)
  • Channel binding SHA-1 which is unsafe.

What is not implemented by design

This crate does not open a remote connection to host for you. It does not contain a code to open a connection to any remote target. This crate contains only a SCRAM-SHA logic. Your program manages the connection itself, reception of the data itself and transmitting it back to client/server on its own. This crated performs only logical operaions on received data and retrns the result to your program. This appreoach inreases a flexibility. This crate also implements a signalling so there is no need to implement a special error handling.

Based on crates:

  • pbkdf2
  • sha2
  • sha-1
  • hmac
  • md-5
  • base64
  • getrandom
  • ring

Features:

By default the following crates: [pbkdf2], [hmac], [sha2], [sha1] are included with this crate and a trait objects are available.

  • use_ring - adds crate: [ring] to the crate and a trait objects becomes available.
  • exclude_sha1 - excludes the sha1 alg.
  • without_async - excludes/masks the async code
  • std - use std lib

Warnings:

  • This crate does not open network connection to anywhere. And must never!
  • This crate has never been audited, only static tests proofs the correctness of its operation.
  • This crate uses unverified cryptography crates. There is no warranty that the operaion of those crates is correct all the time.

Author of this crate is not responsible for anything which may happen.

Issues tracker:

Issues tracket is here

Usage:

see ./examples/ there

Test based benchmarks:

scram_sha256_server() sync tests (DEBUG) on AMD Ryzen 5 7600X 6-Core Processor 5453 MHz

iteration rust-native use_ring
1 30.481112ms 7.813466ms

scram_sha256_works() async tests (DEBUG)

iteration rust-native use_ring
1 30.935835ms 7.913466ms

For usage see ./examples/ For C usage see ./tests/

Examples:

Init:

Generic struct (borrow intances):

  let authdb = AuthDB::new();
  let scramtype = SCRAM_TYPES.get_scramtype("SCRAM-SHA-256").unwrap();

  let mut server = 
      SyncScramServer::<ScramSha256RustNative, &AuthDB, &AuthDB>::new(&authdb, &authdb, ScramNonce::none(), scramtype).unwrap();

Dynamic:

  let authdb = AuthDB::new();
  let authdbcb = AuthDBCb{};
  let scramtype = SCRAM_TYPES.get_scramtype("SCRAM-SHA-256").unwrap();

  let server = 
      SyncScramServer
          ::<ScramSha256RustNative, AuthDB, AuthDBCb>
          ::new(authdb, authdbcb, ScramNonce::none(), scramtype).unwrap();


  let mut server_dyn = server.make_dyn();

Custom (consume the instances):

  let authdb = AuthDB::new();
  let conninst = ConnectionInst::new();
  let scramtype = SCRAM_TYPES.get_scramtype("SCRAM-SHA-256").unwrap();

  let mut server = 
      SyncScramServer
          ::<ScramSha256RustNative, AuthDB, ConnectionInst>
          ::<ScramSha256RustNative, AuthDB, AuthDBCb>
          ::new(authdb, conninst, ScramNonce::none(), scramtype).unwrap();

          .unwrap();

C language bindigns:

gcc test1.c ../../target/debug/libscram_rs.a -o test1
int init_client(const char * usename, const char * password, CApiScramClient ** o_scram_client)
{
    ScramKey * scram_key = NULL;
    CApiScramRuntimeError * err = NULL;

    int32_t res = 
        capi_scram_key(NULL, 0, NULL, 0, &scram_key, &err);

    if (res > 0)
    {
        const char * err_text_dest = capi_scram_error_get_descr(err);

        printf("nonce err text: %s", err_text_dest);

        capi_scram_error_free(err);

        return -1;
    }
    else if (res < 0)
    {
        printf("argument %d is invalid", -res);

        return -1;
    }

    CApiNonce * scram_nonce = NULL;

    res = capi_scram_nonce_none(&scram_nonce, &err);
    
    if (res > 0)
    {
        const char * err_text_dest = capi_scram_error_get_descr(err);

        printf("nonce err text: %s", err_text_dest);

        capi_scram_error_free(err);

        return -1;
    }
    else if (res < 0)
    {
        printf("argument %d is invalid", -res);

        return -1;
    }
    CApiScramClient * scram_client = NULL;

    // init the instance
    res = 
        capi_scram_client_init(RUST_NATIVE, "SCRAM-SHA-256", usename, password, scram_key, scram_nonce, &scram_client, &err);

    if ( res < 0 )
    {
        printf("capi_scram_client_consume() argument %d is invalid", res);
        return -1;
    }
    else if ( res > 0 )
    {
        enum ScramErrorCode err_code = capi_scram_error_get_code(err);
        const char * err_text_dest = capi_scram_error_get_descr(err);

        printf("client init error: err code %i, err text: %s", err_code, err_text_dest);

        capi_scram_error_free(err);

        return -1;
    }    

    *o_scram_client = scram_client;

    return 0;
}

int init_server(struct AuthDBData * some_auth_data, CApiScramServer **o_server)
{
    int res = 0;
    CApiNonce * scram_nonce = NULL;
    CApiScramRuntimeError * err = NULL;

    res = capi_scram_nonce_none(&scram_nonce, &err);
    
    if (res > 0)
    {
        const char * err_text_dest = capi_scram_error_get_descr(err);

        printf("nonce err text: %s", err_text_dest);

        capi_scram_error_free(err);

        return -1;
    }
    else if (res < 0)
    {
        printf("argument %d is invalid", -res);

        return -1;
    }

    CApiScramServer *server = NULL;

    // server instance
    res = 
        capi_scram_server_init(RUST_NATIVE, scram_nonce, (void *)some_auth_data, password_for_user_callback, "SCRAM-SHA-256", &server, &err);

    if ( res < 0 )
    {
        printf("capi_scram_server_init() argument %d is invalid", res);
        return -1;
    }
    else if ( res > 0 )
    {
        const char * err_text_dest = capi_scram_error_get_descr(err);

        printf("response err text: %s", err_text_dest);

        capi_scram_error_free(err);

        return -1;
    }

    *o_server = server;

    return 0;
}

Dependencies

~6–14MB
~272K SLoC