[airplay] Fix rare pairing bug due to incorrect SRP padding (ref #1280)

Update pair_ap to version 0.11, which it fixes an issue where some randomized
values of SRP "a" are short enough that they require padding.
This commit is contained in:
ejurgensen 2021-12-04 23:05:33 +01:00
parent 1418bfb245
commit 088e26c1f0
7 changed files with 1707 additions and 476 deletions

View File

@ -2721,7 +2721,7 @@ payload_make_pair_setup1(struct evrtsp_request *req, struct airplay_session *rs,
snprintf(device_id_hex, sizeof(device_id_hex), "%016" PRIX64, airplay_device_id); snprintf(device_id_hex, sizeof(device_id_hex), "%016" PRIX64, airplay_device_id);
rs->pair_setup_ctx = pair_setup_new(rs->pair_type, pin, device_id_hex); rs->pair_setup_ctx = pair_setup_new(rs->pair_type, pin, NULL, NULL, device_id_hex);
if (!rs->pair_setup_ctx) if (!rs->pair_setup_ctx)
{ {
DPRINTF(E_LOG, L_AIRPLAY, "Out of memory for verification setup context\n"); DPRINTF(E_LOG, L_AIRPLAY, "Out of memory for verification setup context\n");
@ -2757,7 +2757,7 @@ payload_make_pair_verify1(struct evrtsp_request *req, struct airplay_session *rs
snprintf(device_id_hex, sizeof(device_id_hex), "%016" PRIX64, airplay_device_id); snprintf(device_id_hex, sizeof(device_id_hex), "%016" PRIX64, airplay_device_id);
rs->pair_verify_ctx = pair_verify_new(rs->pair_type, device->auth_key, device_id_hex); rs->pair_verify_ctx = pair_verify_new(rs->pair_type, device->auth_key, NULL, NULL, device_id_hex);
if (!rs->pair_verify_ctx) if (!rs->pair_verify_ctx)
{ {
DPRINTF(E_LOG, L_AIRPLAY, "Out of memory for verification verify context\n"); DPRINTF(E_LOG, L_AIRPLAY, "Out of memory for verification verify context\n");
@ -3179,8 +3179,7 @@ static enum airplay_seq_type
response_handler_pair_setup2(struct evrtsp_request *req, struct airplay_session *rs) response_handler_pair_setup2(struct evrtsp_request *req, struct airplay_session *rs)
{ {
enum airplay_seq_type seq_type; enum airplay_seq_type seq_type;
const uint8_t *shared_secret; struct pair_result *result;
size_t shared_secret_len;
int ret; int ret;
seq_type = response_handler_pair_generic(2, req, rs); seq_type = response_handler_pair_generic(2, req, rs);
@ -3190,14 +3189,14 @@ response_handler_pair_setup2(struct evrtsp_request *req, struct airplay_session
if (rs->pair_type != PAIR_CLIENT_HOMEKIT_TRANSIENT) if (rs->pair_type != PAIR_CLIENT_HOMEKIT_TRANSIENT)
return seq_type; return seq_type;
ret = pair_setup_result(NULL, &shared_secret, &shared_secret_len, rs->pair_setup_ctx); ret = pair_setup_result(NULL, &result, rs->pair_setup_ctx);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_AIRPLAY, "Transient setup result error: %s\n", pair_setup_errmsg(rs->pair_setup_ctx)); DPRINTF(E_LOG, L_AIRPLAY, "Transient setup result error: %s\n", pair_setup_errmsg(rs->pair_setup_ctx));
goto error; goto error;
} }
ret = session_cipher_setup(rs, shared_secret, shared_secret_len); ret = session_cipher_setup(rs, result->shared_secret, result->shared_secret_len);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_AIRPLAY, "Pair transient error setting up encryption for '%s'\n", rs->devname); DPRINTF(E_LOG, L_AIRPLAY, "Pair transient error setting up encryption for '%s'\n", rs->devname);
@ -3223,7 +3222,7 @@ response_handler_pair_setup3(struct evrtsp_request *req, struct airplay_session
if (seq_type != AIRPLAY_SEQ_CONTINUE) if (seq_type != AIRPLAY_SEQ_CONTINUE)
return seq_type; return seq_type;
ret = pair_setup_result(&authorization_key, NULL, NULL, rs->pair_setup_ctx); ret = pair_setup_result(&authorization_key, NULL, rs->pair_setup_ctx);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_AIRPLAY, "Pair setup result error: %s\n", pair_setup_errmsg(rs->pair_setup_ctx)); DPRINTF(E_LOG, L_AIRPLAY, "Pair setup result error: %s\n", pair_setup_errmsg(rs->pair_setup_ctx));
@ -3278,22 +3277,21 @@ response_handler_pair_verify2(struct evrtsp_request *req, struct airplay_session
{ {
struct output_device *device; struct output_device *device;
enum airplay_seq_type seq_type; enum airplay_seq_type seq_type;
const uint8_t *shared_secret; struct pair_result *result;
size_t shared_secret_len;
int ret; int ret;
seq_type = response_handler_pair_generic(5, req, rs); seq_type = response_handler_pair_generic(5, req, rs);
if (seq_type != AIRPLAY_SEQ_CONTINUE) if (seq_type != AIRPLAY_SEQ_CONTINUE)
goto error; goto error;
ret = pair_verify_result(&shared_secret, &shared_secret_len, rs->pair_verify_ctx); ret = pair_verify_result(&result, rs->pair_verify_ctx);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_AIRPLAY, "Pair verify result error: %s\n", pair_verify_errmsg(rs->pair_verify_ctx)); DPRINTF(E_LOG, L_AIRPLAY, "Pair verify result error: %s\n", pair_verify_errmsg(rs->pair_verify_ctx));
goto error; goto error;
} }
ret = session_cipher_setup(rs, shared_secret, shared_secret_len); ret = session_cipher_setup(rs, result->shared_secret, result->shared_secret_len);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_AIRPLAY, "Pair verify error setting up encryption for '%s'\n", rs->devname); DPRINTF(E_LOG, L_AIRPLAY, "Pair verify error setting up encryption for '%s'\n", rs->devname);

View File

@ -3846,7 +3846,7 @@ raop_pair_verify(struct raop_session *rs)
if (!device) if (!device)
goto error; goto error;
CHECK_NULL(L_RAOP, rs->pair_verify_ctx = pair_verify_new(PAIR_CLIENT_FRUIT, device->auth_key, NULL)); CHECK_NULL(L_RAOP, rs->pair_verify_ctx = pair_verify_new(PAIR_CLIENT_FRUIT, device->auth_key, NULL, NULL, NULL));
ret = raop_pair_request_send(4, rs, raop_cb_pair_verify_step1); ret = raop_pair_request_send(4, rs, raop_cb_pair_verify_step1);
if (ret < 0) if (ret < 0)
@ -3872,7 +3872,7 @@ raop_cb_pair_setup_step3(struct evrtsp_request *req, void *arg)
if (ret < 0) if (ret < 0)
goto out; goto out;
ret = pair_setup_result(&authorization_key, NULL, NULL, rs->pair_setup_ctx); ret = pair_setup_result(&authorization_key, NULL, rs->pair_setup_ctx);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_RAOP, "Verification setup result error: %s\n", pair_setup_errmsg(rs->pair_setup_ctx)); DPRINTF(E_LOG, L_RAOP, "Verification setup result error: %s\n", pair_setup_errmsg(rs->pair_setup_ctx));
@ -3955,7 +3955,7 @@ raop_pair_setup(struct raop_session *rs, const char *pin)
{ {
int ret; int ret;
rs->pair_setup_ctx = pair_setup_new(PAIR_CLIENT_FRUIT, pin, NULL); rs->pair_setup_ctx = pair_setup_new(PAIR_CLIENT_FRUIT, pin, NULL, NULL, NULL);
if (!rs->pair_setup_ctx) if (!rs->pair_setup_ctx)
{ {
DPRINTF(E_LOG, L_RAOP, "Out of memory for verification setup context\n"); DPRINTF(E_LOG, L_RAOP, "Out of memory for verification setup context\n");

View File

@ -1,6 +1,13 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
#include <sodium.h> #include <sodium.h>
#include "pair.h"
#define RETURN_ERROR(s, m) \
do { handle->status = (s); handle->errmsg = (m); goto error; } while(0)
struct SRPUser; struct SRPUser;
struct SRPVerifier; struct SRPVerifier;
@ -8,8 +15,14 @@ struct pair_client_setup_context
{ {
struct SRPUser *user; struct SRPUser *user;
char pin[4]; uint8_t pin[4];
char device_id[17]; // Incl. zero term char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
pair_cb add_cb;
void *add_cb_arg;
uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
uint8_t private_key[crypto_sign_SECRETKEYBYTES];
const uint8_t *pkA; const uint8_t *pkA;
int pkA_len; int pkA_len;
@ -25,8 +38,6 @@ struct pair_client_setup_context
uint8_t *salt; uint8_t *salt;
uint64_t salt_len; uint64_t salt_len;
uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
uint8_t private_key[crypto_sign_SECRETKEYBYTES];
// We don't actually use the server's epk and authtag for anything // We don't actually use the server's epk and authtag for anything
uint8_t *epk; uint8_t *epk;
@ -39,8 +50,16 @@ struct pair_server_setup_context
{ {
struct SRPVerifier *verifier; struct SRPVerifier *verifier;
char pin[4]; uint8_t pin[4];
char device_id[17]; // Incl. zero term char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
pair_cb add_cb;
void *add_cb_arg;
uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
uint8_t private_key[crypto_sign_SECRETKEYBYTES];
bool is_transient;
uint8_t *pkA; uint8_t *pkA;
uint64_t pkA_len; uint64_t pkA_len;
@ -64,13 +83,24 @@ struct pair_server_setup_context
int salt_len; int salt_len;
}; };
enum pair_status
{
PAIR_STATUS_IN_PROGRESS,
PAIR_STATUS_COMPLETED,
PAIR_STATUS_AUTH_FAILED,
PAIR_STATUS_INVALID,
};
struct pair_setup_context struct pair_setup_context
{ {
struct pair_definition *type; struct pair_definition *type;
int setup_is_completed; enum pair_status status;
const char *errmsg; const char *errmsg;
struct pair_result result;
char result_str[256]; // Holds the hex string version of the keys that pair_verify_new() needs
// Hex-formatet concatenation of public + private, 0-terminated // Hex-formatet concatenation of public + private, 0-terminated
char auth_key[2 * (crypto_sign_PUBLICKEYBYTES + crypto_sign_SECRETKEYBYTES) + 1]; char auth_key[2 * (crypto_sign_PUBLICKEYBYTES + crypto_sign_SECRETKEYBYTES) + 1];
@ -83,30 +113,59 @@ struct pair_setup_context
struct pair_client_verify_context struct pair_client_verify_context
{ {
char device_id[17]; // Incl. zero term char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
uint8_t server_eph_public_key[32]; // These are the keys that were registered with the server in pair-setup
uint8_t server_public_key[64]; uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES]; // 32
uint8_t client_private_key[crypto_sign_SECRETKEYBYTES]; // 64
uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES]; bool verify_server_signature;
uint8_t client_private_key[crypto_sign_SECRETKEYBYTES]; uint8_t server_fruit_public_key[64]; // Not sure why it has this length in fruit mode
uint8_t server_public_key[crypto_sign_PUBLICKEYBYTES]; // 32
uint8_t client_eph_public_key[32]; // For establishing the shared secret for encrypted communication
uint8_t client_eph_private_key[32]; uint8_t client_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32
uint8_t client_eph_private_key[crypto_box_SECRETKEYBYTES]; // 32
uint8_t shared_secret[32]; uint8_t server_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32
uint8_t shared_secret[crypto_scalarmult_BYTES]; // 32
};
struct pair_server_verify_context
{
char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
// Same keys as used for pair-setup, derived from device_id
uint8_t server_public_key[crypto_sign_PUBLICKEYBYTES]; // 32
uint8_t server_private_key[crypto_sign_SECRETKEYBYTES]; // 64
bool verify_client_signature;
pair_cb get_cb;
void *get_cb_arg;
// For establishing the shared secret for encrypted communication
uint8_t server_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32
uint8_t server_eph_private_key[crypto_box_SECRETKEYBYTES]; // 32
uint8_t client_eph_public_key[crypto_box_PUBLICKEYBYTES]; // 32
uint8_t shared_secret[crypto_scalarmult_BYTES]; // 32
}; };
struct pair_verify_context struct pair_verify_context
{ {
struct pair_definition *type; struct pair_definition *type;
int verify_is_completed; enum pair_status status;
const char *errmsg; const char *errmsg;
struct pair_result result;
union pair_verify_union union pair_verify_union
{ {
struct pair_client_verify_context client; struct pair_client_verify_context client;
struct pair_server_verify_context server;
} vctx; } vctx;
}; };
@ -129,38 +188,49 @@ struct pair_cipher_context
struct pair_definition struct pair_definition
{ {
int (*pair_setup_new)(struct pair_setup_context *sctx, const char *pin, const char *device_id); int (*pair_setup_new)(struct pair_setup_context *sctx, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id);
void (*pair_setup_free)(struct pair_setup_context *sctx); void (*pair_setup_free)(struct pair_setup_context *sctx);
int (*pair_setup_result)(const uint8_t **key, size_t *key_len, struct pair_setup_context *sctx); int (*pair_setup_result)(struct pair_setup_context *sctx);
uint8_t *(*pair_setup_request1)(size_t *len, struct pair_setup_context *sctx); uint8_t *(*pair_setup_request1)(size_t *len, struct pair_setup_context *sctx);
uint8_t *(*pair_setup_request2)(size_t *len, struct pair_setup_context *sctx); uint8_t *(*pair_setup_request2)(size_t *len, struct pair_setup_context *sctx);
uint8_t *(*pair_setup_request3)(size_t *len, struct pair_setup_context *sctx); uint8_t *(*pair_setup_request3)(size_t *len, struct pair_setup_context *sctx);
int (*pair_setup_response1)(struct pair_setup_context *sctx, const uint8_t *data, size_t data_len); int (*pair_setup_response1)(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
int (*pair_setup_response2)(struct pair_setup_context *sctx, const uint8_t *data, size_t data_len); int (*pair_setup_response2)(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
int (*pair_setup_response3)(struct pair_setup_context *sctx, const uint8_t *data, size_t data_len); int (*pair_setup_response3)(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
int (*pair_verify_new)(struct pair_verify_context *vctx, const char *hexkey, const char *device_id); int (*pair_verify_new)(struct pair_verify_context *vctx, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id);
void (*pair_verify_free)(struct pair_verify_context *vctx); void (*pair_verify_free)(struct pair_verify_context *vctx);
int (*pair_verify_result)(const uint8_t **key, size_t *key_len, struct pair_verify_context *vctx); int (*pair_verify_result)(struct pair_verify_context *vctx);
uint8_t *(*pair_verify_request1)(size_t *len, struct pair_verify_context *vctx); uint8_t *(*pair_verify_request1)(size_t *len, struct pair_verify_context *vctx);
uint8_t *(*pair_verify_request2)(size_t *len, struct pair_verify_context *vctx); uint8_t *(*pair_verify_request2)(size_t *len, struct pair_verify_context *vctx);
int (*pair_verify_response1)(struct pair_verify_context *vctx, const uint8_t *data, size_t data_len); int (*pair_verify_response1)(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len);
int (*pair_verify_response2)(struct pair_verify_context *vctx, const uint8_t *data, size_t data_len); int (*pair_verify_response2)(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len);
int (*pair_add)(uint8_t **out, size_t *out_len, pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len);
int (*pair_remove)(uint8_t **out, size_t *out_len, pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len);
int (*pair_list)(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, const uint8_t *in, size_t in_len);
struct pair_cipher_context *(*pair_cipher_new)(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len); struct pair_cipher_context *(*pair_cipher_new)(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len);
void (*pair_cipher_free)(struct pair_cipher_context *cctx); void (*pair_cipher_free)(struct pair_cipher_context *cctx);
ssize_t (*pair_encrypt)(uint8_t **ciphertext, size_t *ciphertext_len, uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx); ssize_t (*pair_encrypt)(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx);
ssize_t (*pair_decrypt)(uint8_t **plaintext, size_t *plaintext_len, uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx); ssize_t (*pair_decrypt)(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx);
int (*pair_state_get)(const char **errmsg, const uint8_t *data, size_t data_len); int (*pair_state_get)(const char **errmsg, const uint8_t *in, size_t in_len);
void (*pair_public_key_get)(uint8_t server_public_key[32], const char *device_id);
}; };
/* ----------------------------- INITIALIZATION ---------------------------- */
bool
is_initialized(void);
/* -------------------- GCRYPT AND OPENSSL COMPABILITY --------------------- */ /* -------------------- GCRYPT AND OPENSSL COMPABILITY --------------------- */
/* partly borrowed from ffmpeg (rtmpdh.c) */ /* partly borrowed from ffmpeg (rtmpdh.c) */
@ -297,7 +367,7 @@ int
hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len); hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len);
bnum bnum
H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2); H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2, int padded_len);
bnum bnum
H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes); H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes);
@ -314,4 +384,7 @@ hash_num(enum hash_alg alg, const bnum n, unsigned char *dest);
#ifdef DEBUG_PAIR #ifdef DEBUG_PAIR
void void
hexdump(const char *msg, uint8_t *mem, size_t len); hexdump(const char *msg, uint8_t *mem, size_t len);
void
bnum_dump(const char *msg, bnum n);
#endif #endif

View File

@ -25,6 +25,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h> // for isprint() #include <ctype.h> // for isprint()
#include <assert.h>
#include <sodium.h> #include <sodium.h>
@ -34,16 +35,37 @@
extern struct pair_definition pair_client_fruit; extern struct pair_definition pair_client_fruit;
extern struct pair_definition pair_client_homekit_normal; extern struct pair_definition pair_client_homekit_normal;
extern struct pair_definition pair_client_homekit_transient; extern struct pair_definition pair_client_homekit_transient;
extern struct pair_definition pair_server_homekit_transient; extern struct pair_definition pair_server_homekit;
// Must be in sync with enum pair_type // Must be in sync with enum pair_type
static struct pair_definition *pair[] = { static struct pair_definition *pair[] = {
&pair_client_fruit, &pair_client_fruit,
&pair_client_homekit_normal, &pair_client_homekit_normal,
&pair_client_homekit_transient, &pair_client_homekit_transient,
&pair_server_homekit_transient, &pair_server_homekit,
}; };
/* ------------------------------ INITIALIZATION ---------------------------- */
bool
is_initialized(void)
{
if (sodium_init() == -1)
return false;
#if CONFIG_GCRYPT
// According to libgcrypt documentation: "It is important that these
// initialization steps are not done by a library but by the actual
// application. A library using Libgcrypt might want to check for finished
// initialization using:"
if (!gcry_control (GCRYCTL_INITIALIZATION_FINISHED_P))
return false;
#endif
return true;
}
/* -------------------------- SHARED HASHING HELPERS ------------------------ */ /* -------------------------- SHARED HASHING HELPERS ------------------------ */
int int
@ -167,24 +189,27 @@ hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_le
return hash_final(alg, &ctx, md); return hash_final(alg, &ctx, md);
} }
// See rfc5054 PAD()
bnum bnum
H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2) H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2, int padded_len)
{ {
bnum bn; bnum bn;
unsigned char *bin; unsigned char *bin;
unsigned char buff[SHA512_DIGEST_LENGTH]; unsigned char buff[SHA512_DIGEST_LENGTH];
int len_n1 = bnum_num_bytes(n1); int len_n1 = bnum_num_bytes(n1);
int len_n2 = bnum_num_bytes(n2); int len_n2 = bnum_num_bytes(n2);
int nbytes = 2 * len_n1; int nbytes = 2 * padded_len;
int offset_n1 = padded_len - len_n1;
int offset_n2 = nbytes - len_n2;
if ((len_n2 < 1) || (len_n2 > len_n1)) assert(len_n1 <= padded_len);
return 0; assert(len_n2 <= padded_len);
bin = calloc( 1, nbytes ); bin = calloc(1, nbytes);
bnum_bn2bin(n1, bin, len_n1); bnum_bn2bin(n1, bin + offset_n1, len_n1);
bnum_bn2bin(n2, bin + nbytes - len_n2, len_n2); bnum_bn2bin(n2, bin + offset_n2, len_n2);
hash( alg, bin, nbytes, buff ); hash(alg, bin, nbytes, buff);
free(bin); free(bin);
bnum_bin2bn(bn, buff, hash_length(alg)); bnum_bin2bn(bn, buff, hash_length(alg));
return bn; return bn;
@ -200,8 +225,8 @@ H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes)
unsigned char *bin = malloc(nbytes); unsigned char *bin = malloc(nbytes);
bnum_bn2bin(n, bin, len_n); bnum_bn2bin(n, bin, len_n);
memcpy( bin + len_n, bytes, len_bytes ); memcpy(bin + len_n, bytes, len_bytes);
hash( alg, bin, nbytes, buff ); hash(alg, bin, nbytes, buff);
free(bin); free(bin);
bnum_bin2bn(bn, buff, hash_length(alg)); bnum_bin2bn(bn, buff, hash_length(alg));
return bn; return bn;
@ -268,13 +293,23 @@ hexdump(const char *msg, uint8_t *mem, size_t len)
} }
} }
} }
void
bnum_dump(const char *msg, bnum n)
{
int len_n = bnum_num_bytes(n);
uint8_t *bin = calloc(1, len_n);
bnum_bn2bin(n, bin, len_n);
hexdump(msg, bin, len_n);
free(bin);
}
#endif #endif
/* ----------------------------------- API -----------------------------------*/ /* ----------------------------------- API -----------------------------------*/
struct pair_setup_context * struct pair_setup_context *
pair_setup_new(enum pair_type type, const char *pin, const char *device_id) pair_setup_new(enum pair_type type, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id)
{ {
struct pair_setup_context *sctx; struct pair_setup_context *sctx;
@ -287,7 +322,7 @@ pair_setup_new(enum pair_type type, const char *pin, const char *device_id)
sctx->type = pair[type]; sctx->type = pair[type];
if (pair[type]->pair_setup_new(sctx, pin, device_id) < 0) if (pair[type]->pair_setup_new(sctx, pin, add_cb, cb_arg, device_id) < 0)
{ {
free(sctx); free(sctx);
return NULL; return NULL;
@ -314,6 +349,76 @@ pair_setup_errmsg(struct pair_setup_context *sctx)
return sctx->errmsg; return sctx->errmsg;
} }
int
pair_setup(uint8_t **out, size_t *out_len, struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
{
int state;
int ret = -1;
if (!sctx->type->pair_state_get)
{
sctx->errmsg = "Getting pair state unsupported";
return -1;
}
*out = NULL;
*out_len = 0;
state = sctx->type->pair_state_get(&sctx->errmsg, in, in_len);
if (state < 0)
return -1;
switch (state)
{
case 0:
*out = pair_setup_request1(out_len, sctx);
break;
case 1:
ret = pair_setup_response1(sctx, in, in_len);
if (ret < 0)
break;
*out = pair_setup_request1(out_len, sctx);
break;
case 2:
ret = pair_setup_response1(sctx, in, in_len);
if (ret < 0)
break;
*out = pair_setup_request2(out_len, sctx);
break;
case 3:
ret = pair_setup_response2(sctx, in, in_len);
if (ret < 0)
break;
*out = pair_setup_request2(out_len, sctx);
break;
case 4:
ret = pair_setup_response2(sctx, in, in_len);
if (ret < 0)
break;
*out = pair_setup_request3(out_len, sctx);
break;
case 5:
ret = pair_setup_response3(sctx, in, in_len);
if (ret < 0)
break;
*out = pair_setup_request3(out_len, sctx);
break;
case 6:
ret = pair_setup_response3(sctx, in, in_len);
if (ret < 0)
break;
break;
default:
sctx->errmsg = "Setup: Unsupported state";
ret = -1;
}
if (ret < 0 || !(*out))
return -1;
return 0;
}
uint8_t * uint8_t *
pair_setup_request1(size_t *len, struct pair_setup_context *sctx) pair_setup_request1(size_t *len, struct pair_setup_context *sctx)
{ {
@ -351,7 +456,7 @@ pair_setup_request3(size_t *len, struct pair_setup_context *sctx)
} }
int int
pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *data, size_t data_len) pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
{ {
if (!sctx->type->pair_setup_response1) if (!sctx->type->pair_setup_response1)
{ {
@ -359,11 +464,11 @@ pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *data, size_
return -1; return -1;
} }
return sctx->type->pair_setup_response1(sctx, data, data_len); return sctx->type->pair_setup_response1(sctx, in, in_len);
} }
int int
pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *data, size_t data_len) pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
{ {
if (!sctx->type->pair_setup_response2) if (!sctx->type->pair_setup_response2)
{ {
@ -371,11 +476,11 @@ pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *data, size_
return -1; return -1;
} }
return sctx->type->pair_setup_response2(sctx, data, data_len); return sctx->type->pair_setup_response2(sctx, in, in_len);
} }
int int
pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *data, size_t data_len) pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
{ {
if (!sctx->type->pair_setup_response3) if (!sctx->type->pair_setup_response3)
{ {
@ -383,61 +488,36 @@ pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *data, size_
return -1; return -1;
} }
if (sctx->type->pair_setup_response3(sctx, data, data_len) != 0) if (sctx->type->pair_setup_response3(sctx, in, in_len) != 0)
return -1; return -1;
return 0; return 0;
} }
int int
pair_setup_result(const char **hexkey, const uint8_t **key, size_t *key_len, struct pair_setup_context *sctx) pair_setup_result(const char **client_setup_keys, struct pair_result **result, struct pair_setup_context *sctx)
{ {
const uint8_t *out_key; if (sctx->status != PAIR_STATUS_COMPLETED)
size_t out_len;
char *ptr;
int i;
if (!sctx->setup_is_completed)
{ {
sctx->errmsg = "Setup result: The pair setup has not been completed"; sctx->errmsg = "Setup result: Pair setup has not been completed";
return -1; return -1;
} }
if (!sctx->type->pair_setup_result) if (sctx->type->pair_setup_result)
{ {
sctx->errmsg = "Setup result: Unsupported"; if (sctx->type->pair_setup_result(sctx) != 0)
return -1; return -1;
} }
if (sctx->type->pair_setup_result(&out_key, &out_len, sctx) != 0) if (client_setup_keys)
{ *client_setup_keys = sctx->result_str;
return -1; if (result)
} *result = &sctx->result;
if (2 * out_len + 1 > sizeof(sctx->auth_key))
{
sctx->errmsg = "Setup result: Invalid key length";
return -1;
}
ptr = sctx->auth_key;
for (i = 0; i < out_len; i++)
ptr += sprintf(ptr, "%02x", out_key[i]);
*ptr = '\0';
if (key)
*key = out_key;
if (key_len)
*key_len = out_len;
if (hexkey)
*hexkey = sctx->auth_key;
return 0; return 0;
} }
struct pair_verify_context * struct pair_verify_context *
pair_verify_new(enum pair_type type, const char *hexkey, const char *device_id) pair_verify_new(enum pair_type type, const char *client_setup_keys, pair_cb get_cb, void *cb_arg, const char *device_id)
{ {
struct pair_verify_context *vctx; struct pair_verify_context *vctx;
@ -450,7 +530,7 @@ pair_verify_new(enum pair_type type, const char *hexkey, const char *device_id)
vctx->type = pair[type]; vctx->type = pair[type];
if (pair[type]->pair_verify_new(vctx, hexkey, device_id) < 0) if (pair[type]->pair_verify_new(vctx, client_setup_keys, get_cb, cb_arg, device_id) < 0)
{ {
free(vctx); free(vctx);
return NULL; return NULL;
@ -477,6 +557,64 @@ pair_verify_errmsg(struct pair_verify_context *vctx)
return vctx->errmsg; return vctx->errmsg;
} }
int
pair_verify(uint8_t **out, size_t *out_len, struct pair_verify_context *vctx, const uint8_t *in, size_t in_len)
{
int state;
int ret = -1;
if (!vctx->type->pair_state_get)
{
vctx->errmsg = "Getting pair state unsupported";
return -1;
}
*out = NULL;
*out_len = 0;
state = vctx->type->pair_state_get(&vctx->errmsg, in, in_len);
if (state < 0)
return -1;
switch (state)
{
case 0:
*out = pair_verify_request1(out_len, vctx);
break;
case 1:
ret = pair_verify_response1(vctx, in, in_len);
if (ret < 0)
break;
*out = pair_verify_request1(out_len, vctx);
break;
case 2:
ret = pair_verify_response1(vctx, in, in_len);
if (ret < 0)
break;
*out = pair_verify_request2(out_len, vctx);
break;
case 3:
ret = pair_verify_response2(vctx, in, in_len);
if (ret < 0)
break;
*out = pair_verify_request2(out_len, vctx);
break;
case 4:
ret = pair_verify_response2(vctx, in, in_len);
if (ret < 0)
break;
break;
default:
vctx->errmsg = "Verify: Unsupported state";
ret = -1;
}
if (ret < 0 || !(*out))
return -1;
return 0;
}
uint8_t * uint8_t *
pair_verify_request1(size_t *len, struct pair_verify_context *vctx) pair_verify_request1(size_t *len, struct pair_verify_context *vctx)
{ {
@ -502,7 +640,7 @@ pair_verify_request2(size_t *len, struct pair_verify_context *vctx)
} }
int int
pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *data, size_t data_len) pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len)
{ {
if (!vctx->type->pair_verify_response1) if (!vctx->type->pair_verify_response1)
{ {
@ -510,11 +648,11 @@ pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *data, siz
return -1; return -1;
} }
return vctx->type->pair_verify_response1(vctx, data, data_len); return vctx->type->pair_verify_response1(vctx, in, in_len);
} }
int int
pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *data, size_t data_len) pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len)
{ {
if (!vctx->type->pair_verify_response2) if (!vctx->type->pair_verify_response2)
{ {
@ -522,28 +660,29 @@ pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *data, siz
return -1; return -1;
} }
if (vctx->type->pair_verify_response2(vctx, data, data_len) != 0) if (vctx->type->pair_verify_response2(vctx, in, in_len) != 0)
return -1; return -1;
vctx->verify_is_completed = 1;
return 0; return 0;
} }
int int
pair_verify_result(const uint8_t **shared_secret, size_t *shared_secret_len, struct pair_verify_context *vctx) pair_verify_result(struct pair_result **result, struct pair_verify_context *vctx)
{ {
if (!vctx->verify_is_completed) if (vctx->status != PAIR_STATUS_COMPLETED)
{ {
vctx->errmsg = "Verify result: The pairing verification did not complete"; vctx->errmsg = "Verify result: The pairing verification did not complete";
return -1; return -1;
} }
if (!vctx->type->pair_verify_result) if (vctx->type->pair_verify_result)
return -1; {
if (vctx->type->pair_verify_result(vctx) != 0)
if (vctx->type->pair_verify_result(shared_secret, shared_secret_len, vctx) != 0)
return -1; return -1;
}
if (result)
*result = &vctx->result;
return 0; return 0;
} }
@ -575,7 +714,7 @@ pair_cipher_errmsg(struct pair_cipher_context *cctx)
} }
ssize_t ssize_t
pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx) pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx)
{ {
if (!cctx->type->pair_encrypt) if (!cctx->type->pair_encrypt)
{ {
@ -587,7 +726,7 @@ pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, uint8_t *plaintext, s
} }
ssize_t ssize_t
pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx) pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx)
{ {
if (!cctx->type->pair_decrypt) if (!cctx->type->pair_decrypt)
{ {
@ -611,7 +750,40 @@ pair_decrypt_rollback(struct pair_cipher_context *cctx)
} }
int int
pair_state_get(enum pair_type type, const char **errmsg, const uint8_t *data, size_t data_len) pair_add(enum pair_type type, uint8_t **out, size_t *out_len, pair_cb add_cb, void *cb_arg, const uint8_t *in, size_t in_len)
{
if (!pair[type]->pair_add)
{
return -1;
}
return pair[type]->pair_add(out, out_len, add_cb, cb_arg, in, in_len);
}
int
pair_remove(enum pair_type type, uint8_t **out, size_t *out_len, pair_cb remove_cb, void *cb_arg, const uint8_t *in, size_t in_len)
{
if (!pair[type]->pair_remove)
{
return -1;
}
return pair[type]->pair_remove(out, out_len, remove_cb, cb_arg, in, in_len);
}
int
pair_list(enum pair_type type, uint8_t **out, size_t *out_len, pair_list_cb list_cb, void *cb_arg, const uint8_t *in, size_t in_len)
{
if (!pair[type]->pair_list)
{
return -1;
}
return pair[type]->pair_list(out, out_len, list_cb, cb_arg, in, in_len);
}
int
pair_state_get(enum pair_type type, const char **errmsg, const uint8_t *in, size_t in_len)
{ {
if (!pair[type]->pair_state_get) if (!pair[type]->pair_state_get)
{ {
@ -619,5 +791,16 @@ pair_state_get(enum pair_type type, const char **errmsg, const uint8_t *data, si
return -1; return -1;
} }
return pair[type]->pair_state_get(errmsg, data, data_len); return pair[type]->pair_state_get(errmsg, in, in_len);
}
void
pair_public_key_get(enum pair_type type, uint8_t server_public_key[32], const char *device_id)
{
if (!pair[type]->pair_public_key_get)
{
return;
}
pair[type]->pair_public_key_get(server_public_key, device_id);
} }

View File

@ -4,7 +4,16 @@
#include <stdint.h> #include <stdint.h>
#define PAIR_AP_VERSION_MAJOR 0 #define PAIR_AP_VERSION_MAJOR 0
#define PAIR_AP_VERSION_MINOR 2 #define PAIR_AP_VERSION_MINOR 11
#define PAIR_AP_DEVICE_ID_LEN_MAX 64
#define PAIR_AP_POST_PIN_START "POST /pair-pin-start"
#define PAIR_AP_POST_SETUP "POST /pair-setup"
#define PAIR_AP_POST_VERIFY "POST /pair-verify"
#define PAIR_AP_POST_ADD "POST /pair-add"
#define PAIR_AP_POST_LIST "POST /pair-list"
#define PAIR_AP_POST_REMOVE "POST /pair-remove"
enum pair_type enum pair_type
{ {
@ -15,28 +24,80 @@ enum pair_type
// verification // verification
PAIR_CLIENT_HOMEKIT_NORMAL, PAIR_CLIENT_HOMEKIT_NORMAL,
// Same as normal except PIN is fixed to 3939 and stops after setup step 2, // Same as normal except PIN is fixed to 3939 and stops after setup step 2,
// when session key is established. This is the only mode where the server // when session key is established
// side is also supported.
PAIR_CLIENT_HOMEKIT_TRANSIENT, PAIR_CLIENT_HOMEKIT_TRANSIENT,
PAIR_SERVER_HOMEKIT_TRANSIENT, // Server side implementation supporting both transient and normal mode,
// letting client choose mode. If a PIN is with pair_setup_new() then only
// normal mode will be possible.
PAIR_SERVER_HOMEKIT,
};
/* This struct stores the various forms of pairing results. The shared secret
* is used to initialise an encrypted session via pair_cipher_new(). For
* non-transient client pair setup, you also get a key string (client_setup_keys) from
* pair_setup_result() that you can store and use to later initialise
* pair_verify_new(). For non-transient server pair setup, you can either:
* - Register an "add pairing" callback (add_cb) with pair_setup_new(), and
* then save the client id and key in the callback (see server-example.c for
* this approach).
* - Check pairing result with pair_setup_result() and if successful read and
* store the client id and key from the result struct.
* - Decide not to authenticate clients during pair-verify (set get_cb to NULL)
* in which case you don't need to save client ids and keys from pair-setup.
*
* Table showing returned data (everything else will be zeroed):
*
* | pair-setup | pair-verify
* --------------------------------|-------------------------------|--------------
* PAIR_CLIENT_FRUIT | client keys | shared secret
* PAIR_CLIENT_HOMEKIT_NORMAL | client keys, server public | shared secret
| key, server id | shared secret
* PAIR_CLIENT_HOMEKIT_TRANSIENT | shared secret | n/a
* PAIR_SERVER_HOMEKIT (normal) | client public key, client id | shared secret
* PAIR_SERVER_HOMEKIT (transient) | shared secret | n/a
*/
struct pair_result
{
char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; // ID of the peer
uint8_t client_private_key[64];
uint8_t client_public_key[32];
uint8_t server_public_key[32];
uint8_t shared_secret[64];
size_t shared_secret_len; // Will be 32 (normal) or 64 (transient)
}; };
struct pair_setup_context; struct pair_setup_context;
struct pair_verify_context; struct pair_verify_context;
struct pair_cipher_context; struct pair_cipher_context;
typedef int (*pair_cb)(uint8_t public_key[32], const char *device_id, void *cb_arg);
typedef void (*pair_list_cb)(pair_cb list_cb, void *list_cb_arg, void *cb_arg);
/* ------------------------------- pair setup ------------------------------- */
/* Client /* Client
* When you have the pin-code (must be 4 bytes), create a new context with this * When you have the pin-code (must be 4 chars), create a new context with this
* function and then call pair_setup_request1(). device_id is only * function and then call pair_setup() or pair_setup_request1(). device_id is
* required for homekit pairing, where it should have length 16. * only required for Homekit pairing. If the client previously paired
* (non-transient) and has saved credentials, it should instead skip setup and
* only do verification. The callback is only for Homekit, and you can leave it
* at NULL if you don't care about saving ID and key of the server for later
* verification (then you also set get_cb to NULL in pair_verify_new), or if you
* will read the id and key via pair_setup_result.
* *
* Server * Server
* Create a new context with the pin-code to verify with, then when the request * The client will make a connection and then at some point make a /pair-setup
* is received use pair_setup_response1() to read it, and then reply with using * or a /pair-verify. The server should:
* pair_setup_request1(). * - new /pair-setup: create a setup context with a pin-code (or NULL to allow
* transient pairing), and then call pair_setup() to process request and
* construct reply (also for subsequent /pair-setup requests)
* - new /pair_verify: create a verify context and then call pair_verify()
* to process request and construct reply (also for subsequent /pair-verify
* requests)
*/ */
struct pair_setup_context * struct pair_setup_context *
pair_setup_new(enum pair_type type, const char *pin, const char *device_id); pair_setup_new(enum pair_type type, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id);
void void
pair_setup_free(struct pair_setup_context *sctx); pair_setup_free(struct pair_setup_context *sctx);
@ -45,7 +106,25 @@ pair_setup_free(struct pair_setup_context *sctx);
const char * const char *
pair_setup_errmsg(struct pair_setup_context *sctx); pair_setup_errmsg(struct pair_setup_context *sctx);
/* Will create a request (if client) or response (if server) based on the setup
* context and last message from the peer. If this is the first client request
* then set *in to NULL. Returns negative on error.
*/
int
pair_setup(uint8_t **out, size_t *out_len, struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
/* Returns the result of a pairing, or negative if pairing is not completed. See
* 'struct pair_result' for info about pairing results. The string is a
* representation of the result that is easy to persist and can be used to feed
* back into pair_verify_new. The result and string becomes invalid when you
* free sctx.
*/
int
pair_setup_result(const char **client_setup_keys, struct pair_result **result, struct pair_setup_context *sctx);
/* These are for constructing specific message types and reading specific
* message types. Not needed for Homekit pairing if you use pair_setup().
*/
uint8_t * uint8_t *
pair_setup_request1(size_t *len, struct pair_setup_context *sctx); pair_setup_request1(size_t *len, struct pair_setup_context *sctx);
uint8_t * uint8_t *
@ -54,28 +133,34 @@ uint8_t *
pair_setup_request3(size_t *len, struct pair_setup_context *sctx); pair_setup_request3(size_t *len, struct pair_setup_context *sctx);
int int
pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *data, size_t data_len); pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
int int
pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *data, size_t data_len); pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
int int
pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *data, size_t data_len); pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len);
/* Returns a 0-terminated string that is the authorisation key, along with a
* pointer to the binary representation. The string can be used to initialize
* pair_verify_new().
* Note that the pointers become invalid when you free sctx.
*/
int
pair_setup_result(const char **hexkey, const uint8_t **key, size_t *key_len, struct pair_setup_context *sctx);
/* When you have completed the setup you can extract a key with /* ------------------------------ pair verify ------------------------------- */
* pair_setup_result(). Give the string as input to this function to
* create a verification context and then call pair_verify_request1() /* Client
* device_id is only required for homekit pairing, where it should have len 16. * When you have completed pair setup you get a string containing some keys
* from pair_setup_result(). Give the string as input to this function to create
* a verification context. Set the callback to NULL. Then call pair_verify().
* The device_id is required for Homekit pairing.
*
* Server
* When you get a pair verify request from a new peer, create a new context with
* client_setup_keys set to NULL, with a callback set and the server's device ID
* (same as for setup). Then call pair_verify(). The callback is used to get
* the persisted client public key (saved after pair setup), so the client can
* be verified. You can set the callback to NULL if you don't care about that.
* If set, the callback is made as part of pair_verify_response2. The job of the
* callback is to fill out the public_key with the public key from the setup
* stage (see 'struct pair_result'). If the client device id is not known (i.e.
* it has not completed pair-setup), return -1.
*/ */
struct pair_verify_context * struct pair_verify_context *
pair_verify_new(enum pair_type type, const char *hexkey, const char *device_id); pair_verify_new(enum pair_type type, const char *client_setup_keys, pair_cb get_cb, void *cb_arg, const char *device_id);
void void
pair_verify_free(struct pair_verify_context *vctx); pair_verify_free(struct pair_verify_context *vctx);
@ -84,25 +169,38 @@ pair_verify_free(struct pair_verify_context *vctx);
const char * const char *
pair_verify_errmsg(struct pair_verify_context *vctx); pair_verify_errmsg(struct pair_verify_context *vctx);
/* Will create a request (if client) or response (if server) based on the verify
* context and last message from the peer. If this is the first client request
* then set *in to NULL. Returns negative on error.
*/
int
pair_verify(uint8_t **out, size_t *out_len, struct pair_verify_context *sctx, const uint8_t *in, size_t in_len);
/* Returns a pointer to the result of the pairing. Only the shared secret will
* be filled out. Note that the result become invalid when you free vctx.
*/
int
pair_verify_result(struct pair_result **result, struct pair_verify_context *vctx);
/* These are for constructing specific message types and reading specific
* message types. Not needed for Homekit pairing where you can use pair_verify().
*/
uint8_t * uint8_t *
pair_verify_request1(size_t *len, struct pair_verify_context *vctx); pair_verify_request1(size_t *len, struct pair_verify_context *vctx);
uint8_t * uint8_t *
pair_verify_request2(size_t *len, struct pair_verify_context *vctx); pair_verify_request2(size_t *len, struct pair_verify_context *vctx);
int int
pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *data, size_t data_len); pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len);
int int
pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *data, size_t data_len); pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len);
/* Returns a pointer to the shared secret that is the result of the pairing.
* Note that the pointers become invalid when you free vctx.
*/
int
pair_verify_result(const uint8_t **shared_secret, size_t *shared_secret_len, struct pair_verify_context *vctx);
/* When you have completed the verification you can extract a key with /* ------------------------------- ciphering -------------------------------- */
* pair_verify_result(). Give the shared secret as input to this function to
/* When you have completed the verification you can extract a shared secret with
* pair_verify_result() - or, in case of transient pairing, from
* pair_setup_result(). Give the shared secret as input to this function to
* create a ciphering context. * create a ciphering context.
*/ */
struct pair_cipher_context * struct pair_cipher_context *
@ -120,14 +218,14 @@ pair_cipher_errmsg(struct pair_cipher_context *cctx);
* returned. * returned.
*/ */
ssize_t ssize_t
pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx); pair_encrypt(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx);
/* The return value equals length of ciphertext that was decrypted, so if the /* The return value equals length of ciphertext that was decrypted, so if the
* return value == ciphertext_len then everything was decrypted. On error -1 is * return value == ciphertext_len then everything was decrypted. On error -1 is
* returned. * returned.
*/ */
ssize_t ssize_t
pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx); pair_decrypt(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx);
/* Rolls back the nonce /* Rolls back the nonce
*/ */
@ -136,10 +234,33 @@ pair_encrypt_rollback(struct pair_cipher_context *cctx);
void void
pair_decrypt_rollback(struct pair_cipher_context *cctx); pair_decrypt_rollback(struct pair_cipher_context *cctx);
/* --------------------------------- other ---------------------------------- */
/* These are for Homekit pairing where they are called by the controller, e.g.
* the Home app
*
* TODO this part is currenly not working
*/
int
pair_add(enum pair_type type, uint8_t **out, size_t *out_len, pair_cb add_cb, void *cb_arg, const uint8_t *in, size_t in_len);
int
pair_remove(enum pair_type type, uint8_t **out, size_t *out_len, pair_cb remove_cb, void *cb_arg, const uint8_t *in, size_t in_len);
int
pair_list(enum pair_type type, uint8_t **out, size_t *out_len, pair_list_cb list_cb, void *cb_arg, const uint8_t *in, size_t in_len);
/* For parsing an incoming message to see what type ("state") it is. Mostly /* For parsing an incoming message to see what type ("state") it is. Mostly
* useful for servers. Returns 1-6 for pair-setup and 1-4 for pair-verify. * useful for servers. Returns 1-6 for pair-setup and 1-4 for pair-verify.
*/ */
int int
pair_state_get(enum pair_type type, const char **errmsg, const uint8_t *data, size_t data_len); pair_state_get(enum pair_type type, const char **errmsg, const uint8_t *in, size_t in_len);
/* For servers, pair_ap calculates the public key using device_id as a seed.
* This function returns that public key.
*/
void
pair_public_key_get(enum pair_type type, uint8_t server_public_key[32], const char *device_id);
#endif /* !__PAIR_AP_H__ */ #endif /* !__PAIR_AP_H__ */

View File

@ -33,6 +33,8 @@
#include <plist/plist.h> #include <plist/plist.h>
#include <sodium.h> #include <sodium.h>
#include <assert.h>
#include "pair-internal.h" #include "pair-internal.h"
/* ----------------------------- DEFINES ETC ------------------------------- */ /* ----------------------------- DEFINES ETC ------------------------------- */
@ -58,6 +60,7 @@ typedef struct
{ {
bnum N; bnum N;
bnum g; bnum g;
int N_len;
} NGConstant; } NGConstant;
struct SRPUser struct SRPUser
@ -120,6 +123,8 @@ new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex)
bnum_hex2bn(ng->N, n_hex); bnum_hex2bn(ng->N, n_hex);
bnum_hex2bn(ng->g, g_hex); bnum_hex2bn(ng->g, g_hex);
ng->N_len = bnum_num_bytes(ng->N);
return ng; return ng;
} }
@ -346,7 +351,7 @@ srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, in
bnum_bin2bn(s, bytes_s, len_s); bnum_bin2bn(s, bytes_s, len_s);
bnum_bin2bn(B, bytes_B, len_B); bnum_bin2bn(B, bytes_B, len_B);
k = H_nn_pad(usr->alg, usr->ng->N, usr->ng->g); k = H_nn_pad(usr->alg, usr->ng->N, usr->ng->g, usr->ng->N_len);
bnum_new(v); bnum_new(v);
bnum_new(tmp1); bnum_new(tmp1);
bnum_new(tmp2); bnum_new(tmp2);
@ -355,7 +360,7 @@ srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, in
if (!s || !B || !k || !v || !tmp1 || !tmp2 || !tmp3) if (!s || !B || !k || !v || !tmp1 || !tmp2 || !tmp3)
goto cleanup1; goto cleanup1;
u = H_nn_pad(usr->alg, usr->A, B); u = H_nn_pad(usr->alg, usr->A, B, usr->ng->N_len);
x = calculate_x(usr->alg, s, usr->username, usr->password, usr->password_len); x = calculate_x(usr->alg, s, usr->username, usr->password, usr->password_len);
if (!u || !x) if (!u || !x)
goto cleanup2; goto cleanup2;
@ -595,11 +600,11 @@ encrypt_ctr(unsigned char *ciphertext, int ciphertext_len,
/* -------------------------- IMPLEMENTATION -------------------------------- */ /* -------------------------- IMPLEMENTATION -------------------------------- */
static int static int
pair_client_setup_new(struct pair_setup_context *handle, const char *pin, const char *device_id) client_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id)
{ {
struct pair_client_setup_context *sctx = &handle->sctx.client; struct pair_client_setup_context *sctx = &handle->sctx.client;
if (sodium_init() == -1) if (!is_initialized())
return -1; return -1;
if (!pin || strlen(pin) < 4) if (!pin || strlen(pin) < 4)
@ -611,7 +616,7 @@ pair_client_setup_new(struct pair_setup_context *handle, const char *pin, const
} }
static void static void
pair_client_setup_free(struct pair_setup_context *handle) client_setup_free(struct pair_setup_context *handle)
{ {
struct pair_client_setup_context *sctx = &handle->sctx.client; struct pair_client_setup_context *sctx = &handle->sctx.client;
@ -625,7 +630,7 @@ pair_client_setup_free(struct pair_setup_context *handle)
} }
static uint8_t * static uint8_t *
pair_client_setup_request1(size_t *len, struct pair_setup_context *handle) client_setup_request1(size_t *len, struct pair_setup_context *handle)
{ {
struct pair_client_setup_context *sctx = &handle->sctx.client; struct pair_client_setup_context *sctx = &handle->sctx.client;
plist_t dict; plist_t dict;
@ -651,7 +656,7 @@ pair_client_setup_request1(size_t *len, struct pair_setup_context *handle)
} }
static uint8_t * static uint8_t *
pair_client_setup_request2(size_t *len, struct pair_setup_context *handle) client_setup_request2(size_t *len, struct pair_setup_context *handle)
{ {
struct pair_client_setup_context *sctx = &handle->sctx.client; struct pair_client_setup_context *sctx = &handle->sctx.client;
plist_t dict; plist_t dict;
@ -681,7 +686,7 @@ pair_client_setup_request2(size_t *len, struct pair_setup_context *handle)
} }
static uint8_t * static uint8_t *
pair_client_setup_request3(size_t *len, struct pair_setup_context *handle) client_setup_request3(size_t *len, struct pair_setup_context *handle)
{ {
struct pair_client_setup_context *sctx = &handle->sctx.client; struct pair_client_setup_context *sctx = &handle->sctx.client;
plist_t dict; plist_t dict;
@ -747,7 +752,7 @@ pair_client_setup_request3(size_t *len, struct pair_setup_context *handle)
} }
static int static int
pair_client_setup_response1(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) client_setup_response1(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
{ {
struct pair_client_setup_context *sctx = &handle->sctx.client; struct pair_client_setup_context *sctx = &handle->sctx.client;
plist_t dict; plist_t dict;
@ -774,7 +779,7 @@ pair_client_setup_response1(struct pair_setup_context *handle, const uint8_t *da
} }
static int static int
pair_client_setup_response2(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) client_setup_response2(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
{ {
struct pair_client_setup_context *sctx = &handle->sctx.client; struct pair_client_setup_context *sctx = &handle->sctx.client;
plist_t dict; plist_t dict;
@ -806,7 +811,7 @@ pair_client_setup_response2(struct pair_setup_context *handle, const uint8_t *da
} }
static int static int
pair_client_setup_response3(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) client_setup_response3(struct pair_setup_context *handle, const uint8_t *data, size_t data_len)
{ {
struct pair_client_setup_context *sctx = &handle->sctx.client; struct pair_client_setup_context *sctx = &handle->sctx.client;
plist_t dict; plist_t dict;
@ -837,30 +842,36 @@ pair_client_setup_response3(struct pair_setup_context *handle, const uint8_t *da
plist_free(dict); plist_free(dict);
handle->setup_is_completed = 1; assert(sizeof(handle->result.client_private_key) == sizeof(sctx->private_key));
assert(sizeof(handle->result.client_public_key) == sizeof(sctx->public_key));
memcpy(handle->result.client_private_key, sctx->private_key, sizeof(sctx->private_key));
memcpy(handle->result.client_public_key, sctx->public_key, sizeof(sctx->public_key));
handle->status = PAIR_STATUS_COMPLETED;
return 0; return 0;
} }
static int static int
pair_client_setup_result(const uint8_t **key, size_t *key_len, struct pair_setup_context *handle) client_setup_result(struct pair_setup_context *handle)
{ {
struct pair_client_setup_context *sctx = &handle->sctx.client; struct pair_client_setup_context *sctx = &handle->sctx.client;
char *ptr;
int i;
// Last 32 bytes of private key should match public key, but check assumption // Last 32 bytes of the private key is the public key, so we don't need to
if (memcmp(sctx->private_key + sizeof(sctx->private_key) - sizeof(sctx->public_key), sctx->public_key, sizeof(sctx->public_key)) != 0) // explicitly export that
{ ptr = handle->result_str;
handle->errmsg = "Pair setup result: Unexpected keys, private key does not match public key"; for (i = 0; i < sizeof(sctx->private_key); i++)
return -1; ptr += sprintf(ptr, "%02x", sctx->private_key[i]); // 2 x 64 bytes
} *ptr = '\0';
*key = sctx->private_key;
*key_len = sizeof(sctx->private_key);
return 0; return 0;
} }
static int static int
pair_client_verify_new(struct pair_verify_context *handle, const char *hexkey, const char *device_id) client_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id)
{ {
struct pair_client_verify_context *vctx = &handle->vctx.client; struct pair_client_verify_context *vctx = &handle->vctx.client;
char hex[] = { 0, 0, 0 }; char hex[] = { 0, 0, 0 };
@ -868,13 +879,13 @@ pair_client_verify_new(struct pair_verify_context *handle, const char *hexkey, c
const char *ptr; const char *ptr;
int i; int i;
if (sodium_init() == -1) if (!is_initialized())
return -1; return -1;
if (!hexkey) if (!client_setup_keys)
return -1; return -1;
hexkey_len = strlen(hexkey); hexkey_len = strlen(client_setup_keys);
if (hexkey_len != 2 * sizeof(vctx->client_private_key)) if (hexkey_len != 2 * sizeof(vctx->client_private_key))
return -1; return -1;
@ -885,7 +896,7 @@ pair_client_verify_new(struct pair_verify_context *handle, const char *hexkey, c
if (device_id) if (device_id)
memcpy(vctx->device_id, device_id, strlen(device_id)); memcpy(vctx->device_id, device_id, strlen(device_id));
ptr = hexkey; ptr = client_setup_keys;
for (i = 0; i < sizeof(vctx->client_private_key); i++, ptr+=2) for (i = 0; i < sizeof(vctx->client_private_key); i++, ptr+=2)
{ {
hex[0] = ptr[0]; hex[0] = ptr[0];
@ -893,19 +904,13 @@ pair_client_verify_new(struct pair_verify_context *handle, const char *hexkey, c
vctx->client_private_key[i] = strtol(hex, NULL, 16); vctx->client_private_key[i] = strtol(hex, NULL, 16);
} }
ptr = hexkey + hexkey_len - 2 * sizeof(vctx->client_public_key); crypto_sign_ed25519_sk_to_pk(vctx->client_public_key, vctx->client_private_key);
for (i = 0; i < sizeof(vctx->client_public_key); i++, ptr+=2)
{
hex[0] = ptr[0];
hex[1] = ptr[1];
vctx->client_public_key[i] = strtol(hex, NULL, 16);
}
return 0; return 0;
} }
static uint8_t * static uint8_t *
pair_client_verify_request1(size_t *len, struct pair_verify_context *handle) client_verify_request1(size_t *len, struct pair_verify_context *handle)
{ {
struct pair_client_verify_context *vctx = &handle->vctx.client; struct pair_client_verify_context *vctx = &handle->vctx.client;
const uint8_t basepoint[32] = {9}; const uint8_t basepoint[32] = {9};
@ -935,7 +940,7 @@ pair_client_verify_request1(size_t *len, struct pair_verify_context *handle)
} }
static uint8_t * static uint8_t *
pair_client_verify_request2(size_t *len, struct pair_verify_context *handle) client_verify_request2(size_t *len, struct pair_verify_context *handle)
{ {
struct pair_client_verify_context *vctx = &handle->vctx.client; struct pair_client_verify_context *vctx = &handle->vctx.client;
uint8_t shared_secret[crypto_scalarmult_BYTES]; uint8_t shared_secret[crypto_scalarmult_BYTES];
@ -983,14 +988,14 @@ pair_client_verify_request2(size_t *len, struct pair_verify_context *handle)
return NULL; return NULL;
} }
ret = encrypt_ctr(encrypted, sizeof(encrypted), vctx->server_public_key, sizeof(vctx->server_public_key), signature, sizeof(signature), key, iv, &errmsg); ret = encrypt_ctr(encrypted, sizeof(encrypted), vctx->server_fruit_public_key, sizeof(vctx->server_fruit_public_key), signature, sizeof(signature), key, iv, &errmsg);
if (ret < 0) if (ret < 0)
{ {
handle->errmsg = errmsg; handle->errmsg = errmsg;
return NULL; return NULL;
} }
*len = 4 + sizeof(vctx->server_public_key); *len = 4 + sizeof(vctx->server_fruit_public_key);
data = calloc(1, *len); data = calloc(1, *len);
if (!data) if (!data)
{ {
@ -998,18 +1003,18 @@ pair_client_verify_request2(size_t *len, struct pair_verify_context *handle)
return NULL; return NULL;
} }
memcpy(data + 4, encrypted, sizeof(vctx->server_public_key)); memcpy(data + 4, encrypted, sizeof(vctx->server_fruit_public_key));
return data; return data;
} }
static int static int
pair_client_verify_response1(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) client_verify_response1(struct pair_verify_context *handle, const uint8_t *data, size_t data_len)
{ {
struct pair_client_verify_context *vctx = &handle->vctx.client; struct pair_client_verify_context *vctx = &handle->vctx.client;
size_t wanted; size_t wanted;
wanted = sizeof(vctx->server_eph_public_key) + sizeof(vctx->server_public_key); wanted = sizeof(vctx->server_eph_public_key) + sizeof(vctx->server_fruit_public_key);
if (data_len < wanted) if (data_len < wanted)
{ {
handle->errmsg = "Verify response 2: Unexpected response (too short)"; handle->errmsg = "Verify response 2: Unexpected response (too short)";
@ -1017,25 +1022,21 @@ pair_client_verify_response1(struct pair_verify_context *handle, const uint8_t *
} }
memcpy(vctx->server_eph_public_key, data, sizeof(vctx->server_eph_public_key)); memcpy(vctx->server_eph_public_key, data, sizeof(vctx->server_eph_public_key));
memcpy(vctx->server_public_key, data + sizeof(vctx->server_eph_public_key), sizeof(vctx->server_public_key)); memcpy(vctx->server_fruit_public_key, data + sizeof(vctx->server_eph_public_key), sizeof(vctx->server_fruit_public_key));
return 0; return 0;
} }
static int static int
pair_client_verify_response2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) client_verify_response2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len)
{
// TODO actually check response
return 0;
}
static int
pair_client_verify_result(const uint8_t **key, size_t *key_len, struct pair_verify_context *handle)
{ {
struct pair_client_verify_context *vctx = &handle->vctx.client; struct pair_client_verify_context *vctx = &handle->vctx.client;
// TODO actually check response
*key = vctx->shared_secret; memcpy(handle->result.shared_secret, vctx->shared_secret, sizeof(vctx->shared_secret));
*key_len = sizeof(vctx->shared_secret); handle->result.shared_secret_len = sizeof(vctx->shared_secret);
handle->status = PAIR_STATUS_COMPLETED;
return 0; return 0;
} }
@ -1043,24 +1044,23 @@ pair_client_verify_result(const uint8_t **key, size_t *key_len, struct pair_veri
struct pair_definition pair_client_fruit = struct pair_definition pair_client_fruit =
{ {
.pair_setup_new = pair_client_setup_new, .pair_setup_new = client_setup_new,
.pair_setup_free = pair_client_setup_free, .pair_setup_free = client_setup_free,
.pair_setup_result = pair_client_setup_result, .pair_setup_result = client_setup_result,
.pair_setup_request1 = pair_client_setup_request1, .pair_setup_request1 = client_setup_request1,
.pair_setup_request2 = pair_client_setup_request2, .pair_setup_request2 = client_setup_request2,
.pair_setup_request3 = pair_client_setup_request3, .pair_setup_request3 = client_setup_request3,
.pair_setup_response1 = pair_client_setup_response1, .pair_setup_response1 = client_setup_response1,
.pair_setup_response2 = pair_client_setup_response2, .pair_setup_response2 = client_setup_response2,
.pair_setup_response3 = pair_client_setup_response3, .pair_setup_response3 = client_setup_response3,
.pair_verify_new = pair_client_verify_new, .pair_verify_new = client_verify_new,
.pair_verify_result = pair_client_verify_result,
.pair_verify_request1 = pair_client_verify_request1, .pair_verify_request1 = client_verify_request1,
.pair_verify_request2 = pair_client_verify_request2, .pair_verify_request2 = client_verify_request2,
.pair_verify_response1 = pair_client_verify_response1, .pair_verify_response1 = client_verify_response1,
.pair_verify_response2 = pair_client_verify_response2, .pair_verify_response2 = client_verify_response2,
}; };

File diff suppressed because it is too large Load Diff