shairport-sync/pair_ap/pair.c

807 lines
19 KiB
C

/*
* The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> // for isprint()
#include <assert.h>
#include <sodium.h>
#include "pair.h"
#include "pair-internal.h"
extern struct pair_definition pair_client_fruit;
extern struct pair_definition pair_client_homekit_normal;
extern struct pair_definition pair_client_homekit_transient;
extern struct pair_definition pair_server_homekit;
// Must be in sync with enum pair_type
static struct pair_definition *pair[] = {
&pair_client_fruit,
&pair_client_homekit_normal,
&pair_client_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 ------------------------ */
int
hash_init(enum hash_alg alg, HashCTX *c)
{
#if CONFIG_OPENSSL
switch (alg)
{
case HASH_SHA1 : return SHA1_Init(&c->sha);
case HASH_SHA224: return SHA224_Init(&c->sha256);
case HASH_SHA256: return SHA256_Init(&c->sha256);
case HASH_SHA384: return SHA384_Init(&c->sha512);
case HASH_SHA512: return SHA512_Init(&c->sha512);
default:
return -1;
};
#elif CONFIG_GCRYPT
gcry_error_t err;
err = gcry_md_open(c, alg, 0);
if (err)
return -1;
return 0;
#endif
}
int
hash_update(enum hash_alg alg, HashCTX *c, const void *data, size_t len)
{
#if CONFIG_OPENSSL
switch (alg)
{
case HASH_SHA1 : return SHA1_Update(&c->sha, data, len);
case HASH_SHA224: return SHA224_Update(&c->sha256, data, len);
case HASH_SHA256: return SHA256_Update(&c->sha256, data, len);
case HASH_SHA384: return SHA384_Update(&c->sha512, data, len);
case HASH_SHA512: return SHA512_Update(&c->sha512, data, len);
default:
return -1;
};
#elif CONFIG_GCRYPT
gcry_md_write(*c, data, len);
return 0;
#endif
}
int
hash_final(enum hash_alg alg, HashCTX *c, unsigned char *md)
{
#if CONFIG_OPENSSL
switch (alg)
{
case HASH_SHA1 : return SHA1_Final(md, &c->sha);
case HASH_SHA224: return SHA224_Final(md, &c->sha256);
case HASH_SHA256: return SHA256_Final(md, &c->sha256);
case HASH_SHA384: return SHA384_Final(md, &c->sha512);
case HASH_SHA512: return SHA512_Final(md, &c->sha512);
default:
return -1;
};
#elif CONFIG_GCRYPT
unsigned char *buf = gcry_md_read(*c, alg);
if (!buf)
return -1;
memcpy(md, buf, gcry_md_get_algo_dlen(alg));
gcry_md_close(*c);
return 0;
#endif
}
unsigned char *
hash(enum hash_alg alg, const unsigned char *d, size_t n, unsigned char *md)
{
#if CONFIG_OPENSSL
switch (alg)
{
case HASH_SHA1 : return SHA1(d, n, md);
case HASH_SHA224: return SHA224(d, n, md);
case HASH_SHA256: return SHA256(d, n, md);
case HASH_SHA384: return SHA384(d, n, md);
case HASH_SHA512: return SHA512(d, n, md);
default:
return NULL;
};
#elif CONFIG_GCRYPT
gcry_md_hash_buffer(alg, md, d, n);
return md;
#endif
}
int
hash_length(enum hash_alg alg)
{
#if CONFIG_OPENSSL
switch (alg)
{
case HASH_SHA1 : return SHA_DIGEST_LENGTH;
case HASH_SHA224: return SHA224_DIGEST_LENGTH;
case HASH_SHA256: return SHA256_DIGEST_LENGTH;
case HASH_SHA384: return SHA384_DIGEST_LENGTH;
case HASH_SHA512: return SHA512_DIGEST_LENGTH;
default:
return -1;
};
#elif CONFIG_GCRYPT
return gcry_md_get_algo_dlen(alg);
#endif
}
int
hash_ab(enum hash_alg alg, unsigned char *md, const unsigned char *m1, int m1_len, const unsigned char *m2, int m2_len)
{
HashCTX ctx;
hash_init(alg, &ctx);
hash_update(alg, &ctx, m1, m1_len);
hash_update(alg, &ctx, m2, m2_len);
return hash_final(alg, &ctx, md);
}
// See rfc5054 PAD()
bnum
H_nn_pad(enum hash_alg alg, const bnum n1, const bnum n2, int padded_len)
{
bnum bn;
unsigned char *bin;
unsigned char buff[SHA512_DIGEST_LENGTH];
int len_n1 = bnum_num_bytes(n1);
int len_n2 = bnum_num_bytes(n2);
int nbytes = 2 * padded_len;
int offset_n1 = padded_len - len_n1;
int offset_n2 = nbytes - len_n2;
assert(len_n1 <= padded_len);
assert(len_n2 <= padded_len);
bin = calloc(1, nbytes);
bnum_bn2bin(n1, bin + offset_n1, len_n1);
bnum_bn2bin(n2, bin + offset_n2, len_n2);
hash(alg, bin, nbytes, buff);
free(bin);
bnum_bin2bn(bn, buff, hash_length(alg));
return bn;
}
bnum
H_ns(enum hash_alg alg, const bnum n, const unsigned char *bytes, int len_bytes)
{
bnum bn;
unsigned char buff[SHA512_DIGEST_LENGTH];
int len_n = bnum_num_bytes(n);
int nbytes = len_n + len_bytes;
unsigned char *bin = malloc(nbytes);
bnum_bn2bin(n, bin, len_n);
memcpy(bin + len_n, bytes, len_bytes);
hash(alg, bin, nbytes, buff);
free(bin);
bnum_bin2bn(bn, buff, hash_length(alg));
return bn;
}
void
update_hash_n(enum hash_alg alg, HashCTX *ctx, const bnum n)
{
unsigned long len = bnum_num_bytes(n);
unsigned char *n_bytes = malloc(len);
bnum_bn2bin(n, n_bytes, len);
hash_update(alg, ctx, n_bytes, len);
free(n_bytes);
}
void
hash_num(enum hash_alg alg, const bnum n, unsigned char *dest)
{
int nbytes = bnum_num_bytes(n);
unsigned char *bin = malloc(nbytes);
bnum_bn2bin(n, bin, nbytes);
hash( alg, bin, nbytes, dest );
free(bin);
}
/* ----------------------------- OTHER HELPERS -------------------------------*/
#ifdef DEBUG_PAIR
void
hexdump(const char *msg, uint8_t *mem, size_t len)
{
int i, j;
int hexdump_cols = 16;
if (msg)
printf("%s", msg);
for (i = 0; i < len + ((len % hexdump_cols) ? (hexdump_cols - len % hexdump_cols) : 0); i++)
{
if(i % hexdump_cols == 0)
printf("0x%06x: ", i);
if (i < len)
printf("%02x ", 0xFF & ((char*)mem)[i]);
else
printf(" ");
if (i % hexdump_cols == (hexdump_cols - 1))
{
for (j = i - (hexdump_cols - 1); j <= i; j++)
{
if (j >= len)
putchar(' ');
else if (isprint(((char*)mem)[j]))
putchar(0xFF & ((char*)mem)[j]);
else
putchar('.');
}
putchar('\n');
}
}
}
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
/* ----------------------------------- API -----------------------------------*/
struct pair_setup_context *
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;
if (!pair[type]->pair_setup_new)
return NULL;
sctx = calloc(1, sizeof(struct pair_setup_context));
if (!sctx)
return NULL;
sctx->type = pair[type];
if (pair[type]->pair_setup_new(sctx, pin, add_cb, cb_arg, device_id) < 0)
{
free(sctx);
return NULL;
}
return sctx;
}
void
pair_setup_free(struct pair_setup_context *sctx)
{
if (!sctx)
return;
if (sctx->type->pair_setup_free)
sctx->type->pair_setup_free(sctx);
free(sctx);
}
const char *
pair_setup_errmsg(struct pair_setup_context *sctx)
{
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 *
pair_setup_request1(size_t *len, struct pair_setup_context *sctx)
{
if (!sctx->type->pair_setup_request1)
{
sctx->errmsg = "Setup request 1: Unsupported";
return NULL;
}
return sctx->type->pair_setup_request1(len, sctx);
}
uint8_t *
pair_setup_request2(size_t *len, struct pair_setup_context *sctx)
{
if (!sctx->type->pair_setup_request2)
{
sctx->errmsg = "Setup request 2: Unsupported";
return NULL;
}
return sctx->type->pair_setup_request2(len, sctx);
}
uint8_t *
pair_setup_request3(size_t *len, struct pair_setup_context *sctx)
{
if (!sctx->type->pair_setup_request3)
{
sctx->errmsg = "Setup request 3: Unsupported";
return NULL;
}
return sctx->type->pair_setup_request3(len, sctx);
}
int
pair_setup_response1(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
{
if (!sctx->type->pair_setup_response1)
{
sctx->errmsg = "Setup response 1: Unsupported";
return -1;
}
return sctx->type->pair_setup_response1(sctx, in, in_len);
}
int
pair_setup_response2(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
{
if (!sctx->type->pair_setup_response2)
{
sctx->errmsg = "Setup response 2: Unsupported";
return -1;
}
return sctx->type->pair_setup_response2(sctx, in, in_len);
}
int
pair_setup_response3(struct pair_setup_context *sctx, const uint8_t *in, size_t in_len)
{
if (!sctx->type->pair_setup_response3)
{
sctx->errmsg = "Setup response 3: Unsupported";
return -1;
}
if (sctx->type->pair_setup_response3(sctx, in, in_len) != 0)
return -1;
return 0;
}
int
pair_setup_result(const char **client_setup_keys, struct pair_result **result, struct pair_setup_context *sctx)
{
if (sctx->status != PAIR_STATUS_COMPLETED)
{
sctx->errmsg = "Setup result: Pair setup has not been completed";
return -1;
}
if (sctx->type->pair_setup_result)
{
if (sctx->type->pair_setup_result(sctx) != 0)
return -1;
}
if (client_setup_keys)
*client_setup_keys = sctx->result_str;
if (result)
*result = &sctx->result;
return 0;
}
struct pair_verify_context *
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;
if (!pair[type]->pair_verify_new)
return NULL;
vctx = calloc(1, sizeof(struct pair_verify_context));
if (!vctx)
return NULL;
vctx->type = pair[type];
if (pair[type]->pair_verify_new(vctx, client_setup_keys, get_cb, cb_arg, device_id) < 0)
{
free(vctx);
return NULL;
}
return vctx;
}
void
pair_verify_free(struct pair_verify_context *vctx)
{
if (!vctx)
return;
if (vctx->type->pair_verify_free)
vctx->type->pair_verify_free(vctx);
free(vctx);
}
const char *
pair_verify_errmsg(struct pair_verify_context *vctx)
{
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 *
pair_verify_request1(size_t *len, struct pair_verify_context *vctx)
{
if (!vctx->type->pair_verify_request1)
{
vctx->errmsg = "Verify request 1: Unsupported";
return NULL;
}
return vctx->type->pair_verify_request1(len, vctx);
}
uint8_t *
pair_verify_request2(size_t *len, struct pair_verify_context *vctx)
{
if (!vctx->type->pair_verify_request2)
{
vctx->errmsg = "Verify request 2: Unsupported";
return NULL;
}
return vctx->type->pair_verify_request2(len, vctx);
}
int
pair_verify_response1(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len)
{
if (!vctx->type->pair_verify_response1)
{
vctx->errmsg = "Verify response 1: Unsupported";
return -1;
}
return vctx->type->pair_verify_response1(vctx, in, in_len);
}
int
pair_verify_response2(struct pair_verify_context *vctx, const uint8_t *in, size_t in_len)
{
if (!vctx->type->pair_verify_response2)
{
vctx->errmsg = "Verify response 2: Unsupported";
return -1;
}
if (vctx->type->pair_verify_response2(vctx, in, in_len) != 0)
return -1;
return 0;
}
int
pair_verify_result(struct pair_result **result, struct pair_verify_context *vctx)
{
if (vctx->status != PAIR_STATUS_COMPLETED)
{
vctx->errmsg = "Verify result: The pairing verification did not complete";
return -1;
}
if (vctx->type->pair_verify_result)
{
if (vctx->type->pair_verify_result(vctx) != 0)
return -1;
}
if (result)
*result = &vctx->result;
return 0;
}
struct pair_cipher_context *
pair_cipher_new(enum pair_type type, int channel, const uint8_t *shared_secret, size_t shared_secret_len)
{
if (!pair[type]->pair_cipher_new)
return NULL;
return pair[type]->pair_cipher_new(pair[type], channel, shared_secret, shared_secret_len);
}
void
pair_cipher_free(struct pair_cipher_context *cctx)
{
if (!cctx)
return;
if (!cctx->type->pair_cipher_free)
return;
return cctx->type->pair_cipher_free(cctx);
}
const char *
pair_cipher_errmsg(struct pair_cipher_context *cctx)
{
return cctx->errmsg;
}
ssize_t
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)
{
cctx->errmsg = "Encryption unsupported";
return -1;
}
return cctx->type->pair_encrypt(ciphertext, ciphertext_len, plaintext, plaintext_len, 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)
{
if (!cctx->type->pair_decrypt)
{
cctx->errmsg = "Decryption unsupported";
return -1;
}
return cctx->type->pair_decrypt(plaintext, plaintext_len, ciphertext, ciphertext_len, cctx);
}
void
pair_encrypt_rollback(struct pair_cipher_context *cctx)
{
cctx->encryption_counter = cctx->encryption_counter_prev;
}
void
pair_decrypt_rollback(struct pair_cipher_context *cctx)
{
cctx->decryption_counter = cctx->decryption_counter_prev;
}
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)
{
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)
{
*errmsg = "Getting pair state unsupported";
return -1;
}
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);
}