Newer
Older
/*
* bsspeke.c - BS-SPEKE over Curve25519
*
* Author: Charles V. Wright <cvwright@futo.org>
*
* Copyright (c) 2022 FUTO Holdings, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

Charles Wright
committed
#include "minimonocypher.h"
typedef enum {
DEBUG,
INFO,
WARN,
ERROR,
FATAL
} debug_level_t;
debug_level_t curr_level = DEBUG;
void debug(debug_level_t level,
const char *msg
) {
//if( level >= curr_level ) {
puts(msg);
//}
}
void print_point(const char *label,
uint8_t point[32]
) {
printf("%8s:\t[", label);
int i = 31;
for(i=31; i >= 0; i--)
printf("%02x", point[i]);
printf("]\n");
}
void
generate_random_bytes(uint8_t *buf, size_t len)
{
#ifdef linux
getrandom(buf, len, 0);
#else
arc4random_buf(buf, len);
#endif
}
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_server_init(bsspeke_server_ctx *ctx,
const char* server_id, const size_t server_id_len
) {
ctx->server_id = (uint8_t *)server_id;
ctx->server_id_len = server_id_len;
}
void
bsspeke_client_generate_message1
(
bsspeke_login_msg1_t *msg1,
bsspeke_client_ctx *client
debug(DEBUG, "Hashing client's password");
// 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_general_init(&hash_ctx, 32, NULL, 0);
// Add the client id, server id, and the password to the hash
(const uint8_t *)(client->password),
client->password_len);
(const uint8_t *)(client->client_id),
client->client_id_len);
(const uint8_t *)(client->server_id),
client->server_id_len);
// Write the digest value into `scalar_hash`
crypto_blake2b_final(&hash_ctx, scalar_hash);
debug(DEBUG, "Mapping password hash onto the curve");
// Now use Elligator to map our scalar hash to a point on the curve
crypto_hidden_to_curve(curve_point, scalar_hash);
print_point("H(pass)", curve_point);
// 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...
debug(DEBUG, "Generating random blind `r`");
//arc4random_buf(client->r, 32);
generate_random_bytes(client->r, 32);
print_point("r", client->r);
debug(DEBUG, "Clamping r");
print_point("r", client->r);
debug(DEBUG, "Multiplying curve point by r");
// 3. Multiply our curve point by r
crypto_x25519_scalarmult(msg1->blind, client->r, curve_point, 256);
print_point("blind", msg1->blind);
debug(DEBUG, "Done");
msg1->client_id = client->client_id;
bsspeke_client_setup_generate_message1
(
bsspeke_setup_msg1_t *msg1,
bsspeke_client_ctx *client
) {
bsspeke_client_generate_message1(msg1, client);
}
void
bsspeke_server_setup_generate_message2
(
bsspeke_setup_msg2_t *msg2,
const bsspeke_setup_msg1_t *msg1,
// uint8_t *salt, const size_t salt_len,
bsspeke_user_info_t *user_info,
bsspeke_server_ctx *server_ctx
)
{
// We're setting up a new account
// So we have to create a new random salt for the user
debug(DEBUG, "Generating new salt");
//arc4random_buf(user_info->salt, user_info->salt_len);
generate_random_bytes(user_info->salt, user_info->salt_len);
print_point("salt", user_info->salt);
// Hash the salt
debug(DEBUG, "Hashing the salt");
uint8_t H_salt[32];
crypto_blake2b_general(H_salt, 32,
NULL, 0,
user_info->salt,
user_info->salt_len);
// Use clamp() to ensure we stay on the curve in the multiply below
crypto_x25519_clamp(H_salt);
print_point("H_salt", H_salt);
// Multiply H(salt) by msg1->blind, save into msg2->blind_salt
debug(DEBUG, "Multiplying H_salt by the user's blind");
crypto_x25519_scalarmult(msg2->blind_salt, H_salt, msg1->blind, 256);
print_point("blndsalt", msg2->blind_salt);
}
int
bsspeke_client_setup_generate_message3
(
bsspeke_setup_msg3_t *msg3,
const bsspeke_setup_msg2_t *msg2,
uint32_t phf_blocks, uint32_t phf_iterations,
bsspeke_client_ctx *client
)
{
// Sanity checks first, before we do any work
if( phf_blocks < BSSPEKE_ARGON2_MIN_PHF_BLOCKS ) {
if( phf_iterations < BSSPEKE_ARGON2_MIN_PHF_ITERATIONS ) {
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
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
debug(DEBUG, "Removing the blind from the oblivious salt");
crypto_x25519_inverse(oblivious_salt, client->r, msg2->blind_salt);
print_point("obv_salt", oblivious_salt);
// Hash the oblivious salt together with the id's to create the salt for the PHF
uint8_t password_hash[64];
debug(DEBUG, "Creating the salt for the PHF");
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->client_id,
client->client_id_len);
crypto_blake2b_update(&hash_ctx,
client->server_id,
client->server_id_len);
crypto_blake2b_final(&hash_ctx, phf_salt);
}
print_point("phf_salt", phf_salt);
debug(DEBUG, "Running the PHF to generate p and v");
void *work_area;
if ((work_area = malloc(phf_blocks * 1024)) == NULL) {
return -1;
}
crypto_argon2i(password_hash, 64, work_area,
phf_blocks, phf_iterations,
client->password, client->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]);
// FIXME Should we clamp() v before we multiply???
crypto_x25519_clamp(v);
print_point("p", p);
print_point("v", v);
// Hash p onto the curve to get this user's base point P
//uint8_t P[32];
debug(DEBUG, "Hashing p onto the curve to get P");
crypto_hidden_to_curve(msg3->P, p);
print_point("P", msg3->P);
// Generate our long-term public key V = v * P
debug(DEBUG, "V = v * P");
crypto_x25519_scalarmult(msg3->V, v, msg3->P, 256);
print_point("V", msg3->V);
// Send our PHF params so the server can store them for us
msg3->phf_blocks = phf_blocks;
msg3->phf_iterations = phf_iterations;
void
bsspeke_server_setup_process_message3
(
bsspeke_setup_msg3_t *msg3,
bsspeke_user_info_t *user_info,
bsspeke_server_ctx *server
)
{
// Setup Message 3 contains stuff that we need to remember.
// The user info struct is where we store stuff that
// needs to persist across sessions.
memcpy(user_info->P, msg3->P, 32);
memcpy(user_info->V, msg3->V, 32);
user_info->phf_blocks = msg3->phf_blocks;
user_info->phf_iterations = msg3->phf_iterations;
}
void
bsspeke_client_login_generate_message1
(
bsspeke_login_msg1_t *msg1,
bsspeke_client_ctx *client
bsspeke_client_generate_message1(msg1, client);
bsspeke_server_login_generate_message2(bsspeke_login_msg2_t *msg2,
const bsspeke_login_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,
const bsspeke_user_info_t *user_info,
// Record the client's id
server->client_id_len = strlen(msg1->client_id);
server->client_id = (uint8_t *)malloc(server->client_id_len + 1);
strncpy(server->client_id, msg1->client_id, server->client_id_len);
printf("Server got msg1 from [%s]\n", (char *)(server->client_id));
crypto_blake2b_general(H_salt, 32,
NULL, 0,
user_info->salt,
user_info->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
debug(DEBUG, "Multiplying H_salt by the user's blind");
crypto_x25519_scalarmult(msg2->blind_salt, H_salt, msg1->blind, 256);
print_point("blndsalt", msg2->blind_salt);
// Generate random ephemeral private key b, save it in ctx->b
debug(DEBUG, "Generating ephemeral private key b");
//arc4random_buf(server->b, 32);
generate_random_bytes(server->b, 32);
crypto_x25519_clamp(server->b);
print_point("b", server->b);
debug(DEBUG, "Using user's base point P");
print_point("P", user_info->P);
debug(DEBUG, "Computing ephemeral public key B = b * P");
crypto_x25519_scalarmult(server->B, server->b, user_info->P, 256);
// Copy the public key into the outgoing message as well
msg2->phf_blocks = user_info->phf_blocks;
msg2->phf_iterations = user_info->phf_iterations;
bsspeke_client_login_generate_message3(bsspeke_login_msg3_t *msg3,
const bsspeke_login_msg2_t *msg2,
bsspeke_client_ctx *client
if( msg2->phf_blocks < BSSPEKE_ARGON2_MIN_PHF_BLOCKS ) {
debug(ERROR, "phf_blocks is too low. Aborting.");
if( msg2->phf_iterations < BSSPEKE_ARGON2_MIN_PHF_ITERATIONS ) {
debug(ERROR, "phf_iterations is too low. Aborting.");
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
debug(DEBUG, "Removing the blind from the oblivious salt");
crypto_x25519_inverse(oblivious_salt, client->r, msg2->blind_salt);
print_point("obv_salt", oblivious_salt);
// Hash the oblivious salt together with the id's to create the salt for the PHF
uint8_t password_hash[64];
debug(DEBUG, "Creating the salt for the PHF");
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->client_id,
client->client_id_len);
client->server_id,
client->server_id_len);
debug(DEBUG, "Running the PHF to generate p and v");
void *work_area;
if ((work_area = malloc(msg2->phf_blocks * 1024)) == NULL) {
debug(ERROR, "Failed to malloc() memory for PHF work area");
return -1;
}
crypto_argon2i(password_hash, 64, work_area,
msg2->phf_blocks, msg2->phf_iterations,
client->password, client->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]);
// In the setup protocol, we clamped v before using it
// So we should do the same here
crypto_x25519_clamp(v);
print_point("p", p);
print_point("v", v);
// Hash p onto the curve to get this user's base point P
uint8_t P[32];
debug(DEBUG, "Hashing p onto the curve to get P");
// Generate a random ephemeral private key a, store it in ctx->a
debug(DEBUG, "Generating ephemeral private key a");
//arc4random_buf(client->a, 32);
generate_random_bytes(client->a, 32);
crypto_x25519_clamp(client->a);
print_point("a", client->a);
// Generate the ephemeral public key A = a * P, store it in msg3->A
debug(DEBUG, "Generating ephemeral public key A = a * P");
crypto_x25519_scalarmult(msg3->A, client->a, P, 256);
print_point("A", msg3->A);
debug(DEBUG, "Computing Diffie-Hellman shared secrets");
crypto_x25519(a_B, client->a, msg2->B);
print_point("a * B", a_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
debug(DEBUG, "Hashing current state to get key K_c");
{
crypto_blake2b_ctx hash_ctx;
crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
crypto_blake2b_update(&hash_ctx,
client->client_id,
client->client_id_len);
client->server_id,
client->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->K_c);
// Hash k and the client modifier to get our verifier, save it in msg3->client_verifier
debug(DEBUG, "Hashing K_c and modifier to get our verifier");
{
crypto_blake2b_ctx hash_ctx;
crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
crypto_blake2b_update(&hash_ctx, client->K_c, 32);
(uint8_t *)BSSPEKE_VERIFY_CLIENT_MODIFIER,
BSSPEKE_VERIFY_CLIENT_MODIFIER_LEN);
crypto_blake2b_final(&hash_ctx, msg3->client_verifier);
}
print_point("client_v", msg3->client_verifier);
bsspeke_server_login_generate_message4(bsspeke_login_msg4_t *msg4,
const bsspeke_login_msg3_t *msg3,
const bsspeke_user_info_t *user_info,
bsspeke_server_ctx *server
) {
// Compute the two Diffie-Hellman shared secrets
debug(DEBUG, "Computing Diffie-Hellman shared secrets");
crypto_x25519(b_A, server->b, msg3->A);
print_point("b * A", b_A);
crypto_x25519(b_V, server->b, user_info->V);
print_point("b * V", b_V);
// Hash everything we've learned so far to generate k, save it in ctx->k
debug(DEBUG, "Hashing state so far to generate K_s");
{
crypto_blake2b_ctx hash_ctx;
crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
crypto_blake2b_update(&hash_ctx,
server->client_id,
server->client_id_len);
server->server_id,
server->server_id_len);
crypto_blake2b_update(&hash_ctx, server->B, 32);
crypto_blake2b_update(&hash_ctx, b_A, 32);
crypto_blake2b_update(&hash_ctx, b_V, 32);
crypto_blake2b_final(&hash_ctx, server->K_s);
// Check that the client's hash is correct
// Compute H( k || VERIFY_CLIENT_MODIFIER )
debug(DEBUG, "Checking client's verifier hash");
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->K_s, 32);
(uint8_t *)BSSPEKE_VERIFY_CLIENT_MODIFIER,
BSSPEKE_VERIFY_CLIENT_MODIFIER_LEN);
crypto_blake2b_final(&hash_ctx, my_client_verifier);
}
print_point("client's", msg3->client_verifier);
print_point("mine", my_client_verifier);
// Compare vs msg3->client_verifier
if( crypto_verify32(msg3->client_verifier, my_client_verifier) != 0 ) {
debug(ERROR, "Client's verifier doesn't match!");
debug(DEBUG, "Client's verifier checks out");
// Compute our own verifier H( k || VERIFY_SERVER_MODIFIER ), save it in msg4->server_verifier
debug(DEBUG, "Computing server verifier hash");
{
crypto_blake2b_ctx hash_ctx;
crypto_blake2b_general_init(&hash_ctx, 32, NULL, 0);
crypto_blake2b_update(&hash_ctx, server->K_s, 32);
(uint8_t *)BSSPEKE_VERIFY_SERVER_MODIFIER,
BSSPEKE_VERIFY_SERVER_MODIFIER_LEN);
crypto_blake2b_final(&hash_ctx, msg4->server_verifier);
}
print_point("server_v", msg4->server_verifier);
// If we made it this far, return success
return 0;
}
int
bsspeke_client_login_verify_message4(const bsspeke_login_msg4_t *msg4,
const bsspeke_client_ctx *client
) {
// Compute our own version of the server's verifier hash
debug(DEBUG, "Verifying hash from the server");
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->K_c, 32);
(uint8_t *)BSSPEKE_VERIFY_SERVER_MODIFIER,
BSSPEKE_VERIFY_SERVER_MODIFIER_LEN);
crypto_blake2b_final(&hash_ctx, my_server_verifier);
}
print_point("mine", my_server_verifier);
print_point("server's", msg4->server_verifier);
// If the hashes don't match, return failure
if( crypto_verify32(msg4->server_verifier, my_server_verifier) != 0 ) {
debug(WARN, "Server's hash doesn't match. Aborting.");
debug(DEBUG, "Server's hash checks out. SUCCESS!");
// Otherwise, return success
return 0;
}