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>
#include "monocypher.h"
#include "bsspeke.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
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`");
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");
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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
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_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(salt, salt_len);
print_point("salt", salt);
// Hash the salt
debug(DEBUG, "Hashing 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);
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_MIN_PHF_BLOCKS ) {
return -1;
}
if( 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
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);
return 0;
}
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,
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
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);
crypto_x25519_clamp(server->b);
print_point("b", server->b);
debug(DEBUG, "Using client's base point P");
print_point("P", P);
debug(DEBUG, "Computing ephemeral public key B = b * P");
crypto_x25519_scalarmult(server->B, server->b, P, 256);
print_point("B", server->B);
// Copy the public key into the outgoing message as well
// Copy the PHF params too
msg2->phf_blocks = phf_blocks;
msg2->phf_iterations = phf_iterations;
return;
}
bsspeke_client_login_generate_message3(bsspeke_login_msg3_t *msg3,
const bsspeke_login_msg2_t *msg2,
bsspeke_client_ctx *client
) {
// Sanity checks first, before we do any work
if( msg2->phf_blocks < BSSPEKE_MIN_PHF_BLOCKS ) {
debug(ERROR, "phf_blocks is too low. Aborting.");
return -1;
}
if( msg2->phf_iterations < BSSPEKE_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);
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,
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
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,
(uint8_t *)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,
(uint8_t *)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_login_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,
(uint8_t *)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;
}