Skip to content
Snippets Groups Projects
bsspeke.c 12.82 KiB
/*
 * bsspeke.c - BS-SPEKE over Curve25519
 *
 * Author: Charles V. Wright <cvwright@futo.org>
 * 
 * Copyright (c) 2022 FUTO Holdings, Inc.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "monocypher.h"
#include "bsspeke.h"

void
bsspeke_client_init(bsspeke_client_ctx *ctx,
                    const char* client_id, const size_t client_id_len,
                    const char* server_id, const size_t server_id_len,
                    const char* password, const size_t password_len
) {
    ctx->client_id = (uint8_t *)client_id;
    ctx->client_id_len = client_id_len;

    ctx->server_id = (uint8_t *)server_id;
    ctx->server_id_len = server_id_len;

    ctx->password = (uint8_t *)password;
    ctx->password_len = password_len;
}

void
bsspeke_client_generate_message1(bsspeke_msg1_t *msg1,
                                 bsspeke_client_ctx *client
) {
    // 1. Hash the client's password, client_id, server_id to a point on the curve
    uint8_t scalar_hash[32];
    uint8_t curve_point[32];
    {
        crypto_blake2b_ctx bctx;
        // Give us a 256 bit (32 byte) hash; Don't use a key
        crypto_blake2b_general_init(&bctx, 32, NULL, 0);
        // Add the client id, server id, and the password to the hash
        crypto_blake2b_update(&bctx,
                              (const uint8_t *)(client->password),
                              client->password_len);
        crypto_blake2b_update(&bctx,
                              (const uint8_t *)(client->client_id),
                              client->client_id_len);
        crypto_blake2b_update(&bctx,
                              (const uint8_t *)(client->server_id),
                              client->server_id_len);
        // Write the digest value into `scalar_hash`
        crypto_blake2b_final(&bctx, scalar_hash);
    }
    // Now use Elligator to map our scalar hash to a point on the curve
    crypto_hidden_to_curve(curve_point, scalar_hash);

    // 2. Generate random r
    //    * Actually generate 1/r first, and clamp() it
    //      That way we know it will always lead us back to a point on the curve
    //    * Then use the inverse of 1/r as `r`
    //  FIXME: On second thought, monocypher seems to handle all of this complexity for us.  Let's see what happens if we just do things the straightforward way for now...
    arc4random_buf(client->r, 32);
    crypto_x25519_clamp(client->r);
    // 3. Multiply our curve point by r
    crypto_x25519_scalarmult(msg1->blind, client->r, curve_point, 256);
    return;
}
void
bsspeke_server_init(bsspeke_server_ctx *ctx,
                    const char* server_id
) {
    ctx->server_id = server_id;
}

void
bsspeke_server_generate_message2(bsspeke_msg2_t *msg2,
                                 const bsspeke_msg1_t *msg1,
                                 const uint8_t *salt, const size_t salt_len,
                                 uint8_t P[32], uint8_t V[32],
                                 uint32_t phf_blocks,
                                 uint32_t phf_iterations,
                                 bsspeke_server_ctx *server_ctx
) {
    // Hash the salt
    uint8_t H_salt[32];
    crypto_blake2b_general(H_salt, 32, NULL, 0, salt, salt_len);
    // Use clamp() to ensure we stay on the curve in the multiply below
    crypto_x25519_clamp(H_salt);
    // Multiply H(salt) by msg1->blind, save into msg2->blind_salt
    crypto_x25519_scalarmult(msg2->blind_salt, H_salt, msg1->blind, 256);

    // Generate random ephemeral private key b, save it in ctx->b
    arc4random_buf(server_ctx->b, 32);
    crypto_x25519_clamp(server_ctx->b);

    // Compute public key B = b * P, save it in msg2->B
    crypto_x25519_scalarmult(server_ctx->B, server_ctx->b, P, 256);

    // Copy the public key into the outgoing message as well
    memcpy(msg2->B, server_ctx->B, 32);

    // Copy the PHF params too
    msg2->phf_blocks = phf_blocks;
    msg2->phf_iterations = phf_iterations;

    return;
}

int
bsspeke_client_generate_message3(bsspeke_msg3_t *msg3,
                                 const bsspeke_msg2_t *msg2,
                                 bsspeke_client_ctx *client_ctx
) {
    // Sanity checks first, before we do any work
    if( msg2->phf_blocks < BSSPEKE_MIN_PHF_BLOCKS ) {
        return -1;
    }
    if( msg2->phf_iterations < BSSPEKE_MIN_PHF_ITERATIONS ) {
        return -1;
    }

    uint8_t oblivious_salt[32];
    // Multiply the blinded salt by 1/r to get the oblivious salt
    // Here we rely on Monocypher to do the heavy lifting for us
    crypto_x25519_inverse(oblivious_salt, client_ctx->r, msg2->blind_salt);

    // Hash the oblivious salt together with the id's to create the salt for the PHF
    uint8_t password_hash[64];

    uint8_t phf_salt[32];
    {
        crypto_blake2b_ctx hash_ctx;
        crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
        crypto_blake2b_update(&hash_ctx, oblivious_salt, 32);
        crypto_blake2b_update(&hash_ctx,
                              client_ctx->client_id,
                              client_ctx->client_id_len);
        crypto_blake2b_update(&hash_ctx,
                              client_ctx->server_id,
                              client_ctx->server_id_len);
        crypto_blake2b_final(&hash_ctx, phf_salt);
    }

    void *work_area;
    if ((work_area = malloc(msg2->phf_blocks * 1024)) == NULL) {
        return -1;
    }
    crypto_argon2i(password_hash, 64, work_area,
                   msg2->phf_blocks, msg2->phf_iterations,
                   client_ctx->password, client_ctx->password_len,
                   phf_salt, 32);
    free(work_area);

    // p || v = pwKdf(password, BlindSalt, idC, idS, settings)
    uint8_t *p = &(password_hash[0]);
    uint8_t *v = &(password_hash[32]);

    // Hash p onto the curve to get this user's base point P
    uint8_t P[32];
    crypto_hidden_to_curve(P, p);

    // Generate a random ephemeral private key a, store it in ctx->a
    arc4random_buf(client_ctx->a, 32);
    crypto_x25519_clamp(client_ctx->a);
    // Generate the ephemeral public key A = a * P, store it in msg3->A
    crypto_x25519_scalarmult(msg3->A, client_ctx->a, P, 256);

    // Compute the two Diffie-Hellman shared secrets 
    // DH shared secret from a * B
    uint8_t a_B[32];
    crypto_x25519(a_B, client_ctx->a, msg2->B);
    // DH shared secret from v * B
    uint8_t v_B[32];
    crypto_x25519(v_B, v, msg2->B);

    // Hash everything we know so far to generate our key, save it in ctx->K_c
    {
        crypto_blake2b_ctx hash_ctx;
        crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
        crypto_blake2b_update(&hash_ctx,
                              client_ctx->client_id,
                              client_ctx->client_id_len);
        crypto_blake2b_update(&hash_ctx,
                              client_ctx->server_id,
                              client_ctx->server_id_len);
        crypto_blake2b_update(&hash_ctx, msg3->A, 32);
        crypto_blake2b_update(&hash_ctx, msg2->B, 32);
        crypto_blake2b_update(&hash_ctx, a_B, 32);
        crypto_blake2b_update(&hash_ctx, v_B, 32);
        crypto_blake2b_final(&hash_ctx, client_ctx->K_c);
    }

    // Hash k and the client modifier to get our verifier, save it in msg3->client_verifier
    {
        crypto_blake2b_ctx hash_ctx;
        crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
        crypto_blake2b_update(&hash_ctx, client_ctx->K_c, 32);
        crypto_blake2b_update(&hash_ctx,
                              BSSPEKE_VERIFY_CLIENT_MODIFIER,
                              BSSPEKE_VERIFY_CLIENT_MODIFIER_LEN);
        crypto_blake2b_final(&hash_ctx, msg3->client_verifier);
    }

    return 0;
}

int
bsspeke_server_generate_message4(bsspeke_msg4_t *msg4,
                                 const bsspeke_msg3_t *msg3,
                                 bsspeke_server_ctx *server_ctx
) {
    // Compute the two Diffie-Hellman shared secrets
    // DH shared secret from b * A
    uint8_t b_A[32];
    crypto_x25519(b_A, server_ctx->b, msg3->A);
    // DH shared secret from b * V
    uint8_t b_V[32];
    crypto_x25519(b_V, server_ctx->b, server_ctx->V);

    // Hash everything we've learned so far to generate k, save it in ctx->k
    {
        crypto_blake2b_ctx hash_ctx;
        crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
        crypto_blake2b_update(&hash_ctx,
                              server_ctx->client_id,
                              server_ctx->client_id_len);
        crypto_blake2b_update(&hash_ctx,
                              server_ctx->server_id,
                              server_ctx->server_id_len);
        crypto_blake2b_update(&hash_ctx, msg3->A, 32);
        crypto_blake2b_update(&hash_ctx, server_ctx->B, 32);
        crypto_blake2b_update(&hash_ctx, b_A, 32);
        crypto_blake2b_update(&hash_ctx, b_V, 32);
        crypto_blake2b_final(&hash_ctx, server_ctx->K_s);
    }

    // Check that the client's hash is correct
    // Compute H( k || VERIFY_CLIENT_MODIFIER )
    uint8_t my_client_verifier[32];
    {
        crypto_blake2b_ctx hash_ctx;
        crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
        crypto_blake2b_update(&hash_ctx, server_ctx->K_s, 32);
        crypto_blake2b_update(&hash_ctx,
                              BSSPEKE_VERIFY_CLIENT_MODIFIER,
                              BSSPEKE_VERIFY_CLIENT_MODIFIER_LEN);
        crypto_blake2b_final(&hash_ctx, my_client_verifier);
    }

    // Compare vs msg3->client_verifier
    if( crypto_verify32(msg3->client_verifier, my_client_verifier) != 0 ) {
        return -1;
    }

    // Compute our own verifier H( k || VERIFY_SERVER_MODIFIER ), save it in msg4->server_verifier
    {
        crypto_blake2b_ctx hash_ctx;
        crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
        crypto_blake2b_update(&hash_ctx, server_ctx->K_s, 32);
        crypto_blake2b_update(&hash_ctx,
                              BSSPEKE_VERIFY_SERVER_MODIFIER,
                              BSSPEKE_VERIFY_SERVER_MODIFIER_LEN);
        crypto_blake2b_final(&hash_ctx, msg4->server_verifier);
    }

    // If we made it this far, return success
    return 0;
}

int
bsspeke_client_verify_message4(const bsspeke_msg4_t *msg4,
                               const bsspeke_client_ctx *client_ctx
) {
    // Compute our own version of the server's verifier hash
    uint8_t my_server_verifier[32];
    {
        crypto_blake2b_ctx hash_ctx;
        crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
        crypto_blake2b_update(&hash_ctx, client_ctx->K_c, 32);
        crypto_blake2b_update(&hash_ctx,
                              BSSPEKE_VERIFY_SERVER_MODIFIER,
                              BSSPEKE_VERIFY_SERVER_MODIFIER_LEN);
        crypto_blake2b_final(&hash_ctx, my_server_verifier);
    }

    // If the hashes don't match, return failure
    if( crypto_verify32(msg4->server_verifier, my_server_verifier) != 0 ) {
        return -1;
    }

    // Otherwise, return success
    return 0;
}

int main(int argc, char *argv[])
{
    // Before execution of the protocol
    // Both have:
    //       idS = server identity
    char *server_id;
    // Client has:
    //       idC = client identity
    char *client_id;
    char *password;
    // Server has these for "idC":
    //       salt
    //       settings
    //       P = hashToPoint(p)
    //       V = v * P
    uint8_t salt[32];
    uint8_t P[32];   // curve point
    uint8_t V[32];   // curve point


    // Step 1: Client hashes password, maps to a point on the curve, blinds with a random value
    // C:    r = random()
    // C:    R = r * hashToPoint(H(password, idC, idS))
    // C->S: idC, R
    uint8_t r[32];   // scalar
    uint8_t R[32];   // curve point


    // Step 2: Server generates response with blind salt
    // S: b = random()
    // S: B = b * P
    // S: R' = H(salt) * R
    // C<-S: B, R', settings
    uint8_t b[32];   // scalar
    uint8_t B[32];   // curve point
    uint8_t R_prime[32];  // curve point

    // Step 3: 
    // C:    BlindSalt = (1/r) * R'
    // C:    p || v = pwKdf(password, BlindSalt, idC, idS, settings)
    // C:    P = hashToPoint(p)
    // C:    a = random()
    // C:    A = a * P
    // C:    K_c = H(idC, idS, A, B, a * B, v * B)
    // C:    verifierC = H(K_c, verifyCModifier)
    // C->S: A, verifierC[, encryptedDataC]
    uint8_t blind_salt[32];  // curve point
    uint8_t password_hash[64]; // hash
    uint8_t client_P[32];    // curve point
    uint8_t a[32];           // scalar
    uint8_t A[32];           // curve point
    uint8_t K_c[32];         // hash
    uint8_t verifierC[32];   // hash


    // Step 4:
    //    S: K_s = H(idC, idS, A, B, b * A, b * V)
    //    S: Checks verifierC == H(K_s, verifyCModifier)
    //    S: verifierS = H(K_s, verifySModifier)
    // C<-S: verifierS[, encryptedDataS]
    uint8_t K_s[32];         // hash
    uint8_t server_hash[32]; // hash -- should match verifierC

    
    // Step 5:
    // C:    Checks verifierS == H(K_c, verifySModifier)
    uint8_t client_hash[32]; // hash -- should match verifierS


    return 0;
}