/* * Adaption of ap2-sender by ViktoriiaKh: * * * To test, it is useful to try with this receiver: * * * The Secure Remote Password 6a implementation is adapted from: * - Tom Cocagne * * * TLV helpers are adapted from ESP homekit: * * * 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 #include #include #include #include #include #include "pair-internal.h" #include "pair-tlv.h" /* ----------------------------- DEFINES ETC ------------------------------- */ #define USERNAME "Pair-Setup" #define AUTHTAG_LENGTH 16 #define NONCE_LENGTH 12 // 96 bits according to chacha poly1305 #define REQUEST_BUFSIZE 4096 #define ENCRYPTED_LEN_MAX 0x400 enum pair_keys { PAIR_SETUP_MSG01 = 0, PAIR_SETUP_MSG02, PAIR_SETUP_MSG03, PAIR_SETUP_MSG04, PAIR_SETUP_MSG05, PAIR_SETUP_MSG06, PAIR_SETUP_CONTROLLER_SIGN, PAIR_SETUP_ACCESSORY_SIGN, PAIR_VERIFY_MSG01, PAIR_VERIFY_MSG02, PAIR_VERIFY_MSG03, PAIR_VERIFY_MSG04, PAIR_CONTROL_WRITE, PAIR_CONTROL_READ, PAIR_EVENTS_WRITE, PAIR_EVENTS_READ, }; struct pair_keys_map { uint8_t state; const char *salt; const char *info; const char nonce[8]; }; static struct pair_keys_map pair_keys_map[] = { // Used for /pair-setup { 0x01, NULL, NULL, "" }, { 0x02, NULL, NULL, "" }, { 0x03, NULL, NULL, "" }, { 0x04, NULL, NULL, "" }, { 0x05, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", "PS-Msg05" }, { 0x06, "Pair-Setup-Encrypt-Salt", "Pair-Setup-Encrypt-Info", "PS-Msg06" }, { 0, "Pair-Setup-Controller-Sign-Salt", "Pair-Setup-Controller-Sign-Info", "" }, { 0, "Pair-Setup-Accessory-Sign-Salt", "Pair-Setup-Accessory-Sign-Info", "" }, // Used for /pair-verify { 0x01, NULL, NULL, "" }, { 0x02, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", "PV-Msg02" }, { 0x03, "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", "PV-Msg03" }, { 0x04, NULL, NULL, "" }, // Encryption/decryption of control channel { 0, "Control-Salt", "Control-Write-Encryption-Key", "" }, { 0, "Control-Salt", "Control-Read-Encryption-Key", "" }, // Encryption/decryption of event channel { 0, "Events-Salt", "Events-Write-Encryption-Key", "" }, { 0, "Events-Salt", "Events-Read-Encryption-Key", "" }, }; enum pair_method { PairingMethodPairSetup = 0x00, PairingMethodPairSetupWithAuth = 0x01, PairingMethodPairVerify = 0x02, PairingMethodAddPairing = 0x03, PairingMethodRemovePairing = 0x04, PairingMethodListPairings = 0x05 }; enum pair_flags { PairingFlagsTransient = 0x10, }; // Forwards const struct pair_definition pair_client_homekit_normal; const struct pair_definition pair_client_homekit_transient; const struct pair_definition pair_server_homekit; /* ---------------------------------- SRP ----------------------------------- */ typedef enum { SRP_NG_2048, SRP_NG_3072, SRP_NG_CUSTOM } SRP_NGType; typedef struct { bnum N; bnum g; int N_len; } NGConstant; struct SRPUser { enum hash_alg alg; NGConstant *ng; bnum a; bnum A; bnum S; const unsigned char *bytes_A; int authenticated; char *username; unsigned char *password; int password_len; unsigned char M [SHA512_DIGEST_LENGTH]; unsigned char H_AMK [SHA512_DIGEST_LENGTH]; unsigned char session_key [SHA512_DIGEST_LENGTH]; int session_key_len; }; struct SRPVerifier { enum hash_alg alg; NGConstant *ng; const unsigned char *bytes_B; int authenticated; char *username; unsigned char M [SHA512_DIGEST_LENGTH]; unsigned char H_AMK [SHA512_DIGEST_LENGTH]; unsigned char session_key [SHA512_DIGEST_LENGTH]; int session_key_len; }; struct NGHex { const char *n_hex; const char *g_hex; }; // These constants here were pulled from Appendix A of RFC 5054 static struct NGHex global_Ng_constants[] = { { /* 2048 */ "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4" "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60" "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF" "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907" "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861" "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB" "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73", "2" }, { /* 3072 */ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B" "139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485" "B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1F" "E649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23" "DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32" "905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF69558" "17183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521" "ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D7" "1E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B1817" "7B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82" "D120A93AD2CAFFFFFFFFFFFFFFFF", "5" }, {0,0} /* null sentinel */ }; static NGConstant * new_ng(SRP_NGType ng_type, const char *n_hex, const char *g_hex) { NGConstant *ng = calloc(1, sizeof(NGConstant)); if ( ng_type != SRP_NG_CUSTOM ) { n_hex = global_Ng_constants[ ng_type ].n_hex; g_hex = global_Ng_constants[ ng_type ].g_hex; } bnum_hex2bn(ng->N, n_hex); bnum_hex2bn(ng->g, g_hex); ng->N_len = bnum_num_bytes(ng->N); return ng; } static void free_ng(NGConstant * ng) { if (!ng) return; bnum_free(ng->N); bnum_free(ng->g); free(ng); } static bnum calculate_x(enum hash_alg alg, const bnum salt, const char *username, const unsigned char *password, int password_len) { unsigned char ucp_hash[SHA512_DIGEST_LENGTH]; HashCTX ctx; hash_init( alg, &ctx ); hash_update( alg, &ctx, username, strlen(username) ); hash_update( alg, &ctx, ":", 1 ); hash_update( alg, &ctx, password, password_len ); hash_final( alg, &ctx, ucp_hash ); return H_ns( alg, salt, ucp_hash, hash_length(alg) ); } static void calculate_M(enum hash_alg alg, NGConstant *ng, unsigned char *dest, const char *I, const bnum s, const bnum A, const bnum B, const unsigned char *K, int K_len) { unsigned char H_N[ SHA512_DIGEST_LENGTH ]; unsigned char H_g[ SHA512_DIGEST_LENGTH ]; unsigned char H_I[ SHA512_DIGEST_LENGTH ]; unsigned char H_xor[ SHA512_DIGEST_LENGTH ]; HashCTX ctx; int i = 0; int hash_len = hash_length(alg); hash_num( alg, ng->N, H_N ); hash_num( alg, ng->g, H_g ); hash(alg, (const unsigned char *)I, strlen(I), H_I); for (i=0; i < hash_len; i++ ) H_xor[i] = H_N[i] ^ H_g[i]; hash_init( alg, &ctx ); hash_update( alg, &ctx, H_xor, hash_len ); hash_update( alg, &ctx, H_I, hash_len ); update_hash_n( alg, &ctx, s ); update_hash_n( alg, &ctx, A ); update_hash_n( alg, &ctx, B ); hash_update( alg, &ctx, K, K_len ); hash_final( alg, &ctx, dest ); } static void calculate_H_AMK(enum hash_alg alg, unsigned char *dest, const bnum A, const unsigned char * M, const unsigned char * K, int K_len) { HashCTX ctx; hash_init( alg, &ctx ); update_hash_n( alg, &ctx, A ); hash_update( alg, &ctx, M, hash_length(alg) ); hash_update( alg, &ctx, K, K_len ); hash_final( alg, &ctx, dest ); } /* ----------------------- SRP for the client side -------------------------- */ static struct SRPUser * srp_user_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, const unsigned char *bytes_password, int len_password, const char *n_hex, const char *g_hex) { struct SRPUser *usr = calloc(1, sizeof(struct SRPUser)); int ulen = strlen(username) + 1; if (!usr) goto err_exit; usr->alg = alg; usr->ng = new_ng( ng_type, n_hex, g_hex ); bnum_new(usr->a); bnum_new(usr->A); bnum_new(usr->S); if (!usr->ng || !usr->a || !usr->A || !usr->S) goto err_exit; usr->username = malloc(ulen); usr->password = malloc(len_password); usr->password_len = len_password; if (!usr->username || !usr->password) goto err_exit; memcpy(usr->username, username, ulen); memcpy(usr->password, bytes_password, len_password); usr->authenticated = 0; usr->bytes_A = 0; return usr; err_exit: if (!usr) return NULL; bnum_free(usr->a); bnum_free(usr->A); bnum_free(usr->S); free(usr->username); if (usr->password) { memset(usr->password, 0, usr->password_len); free(usr->password); } free(usr); return NULL; } static void srp_user_free(struct SRPUser *usr) { if(!usr) return; bnum_free(usr->a); bnum_free(usr->A); bnum_free(usr->S); free_ng(usr->ng); memset(usr->password, 0, usr->password_len); free(usr->username); free(usr->password); free((char *)usr->bytes_A); memset(usr, 0, sizeof(*usr)); free(usr); } static int srp_user_is_authenticated(struct SRPUser *usr) { return usr->authenticated; } static const unsigned char * srp_user_get_session_key(struct SRPUser *usr, int *key_length) { if (key_length) *key_length = usr->session_key_len; return usr->session_key; } /* Output: username, bytes_A, len_A */ static void srp_user_start_authentication(struct SRPUser *usr, const char **username, const unsigned char **bytes_A, int *len_A) { // BN_hex2bn(&(usr->a), "D929DFB605687233C9E9030C2280156D03BDB9FDCF3CCE3BC27D9CCFCB5FF6A1"); bnum_random(usr->a, 256); #ifdef DEBUG_PAIR bnum_dump("Random value of usr->a:\n", usr->a); #endif bnum_modexp(usr->A, usr->ng->g, usr->a, usr->ng->N); *len_A = bnum_num_bytes(usr->A); *bytes_A = malloc(*len_A); if (!*bytes_A) { *len_A = 0; *bytes_A = 0; *username = 0; return; } bnum_bn2bin(usr->A, (unsigned char *) *bytes_A, *len_A); usr->bytes_A = *bytes_A; *username = usr->username; } /* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */ static void srp_user_process_challenge(struct SRPUser *usr, const unsigned char *bytes_s, int len_s, const unsigned char *bytes_B, int len_B, const unsigned char **bytes_M, int *len_M ) { bnum s, B, k, v; bnum tmp1, tmp2, tmp3; bnum u, x; *len_M = 0; *bytes_M = 0; bnum_bin2bn(s, bytes_s, len_s); bnum_bin2bn(B, bytes_B, len_B); k = H_nn_pad(usr->alg, usr->ng->N, usr->ng->g, usr->ng->N_len); bnum_new(v); bnum_new(tmp1); bnum_new(tmp2); bnum_new(tmp3); if (!s || !B || !k || !v || !tmp1 || !tmp2 || !tmp3) goto cleanup1; 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); if (!u || !x) goto cleanup2; // SRP-6a safety check if (!bnum_is_zero(B) && !bnum_is_zero(u)) { bnum_modexp(v, usr->ng->g, x, usr->ng->N); // S = (B - k*(g^x)) ^ (a + ux) bnum_mul(tmp1, u, x); bnum_add(tmp2, usr->a, tmp1); // tmp2 = (a + ux) bnum_modexp(tmp1, usr->ng->g, x, usr->ng->N); bnum_mul(tmp3, k, tmp1); // tmp3 = k*(g^x) bnum_sub(tmp1, B, tmp3); // tmp1 = (B - K*(g^x)) bnum_modexp(usr->S, tmp1, tmp2, usr->ng->N); hash_num(usr->alg, usr->S, usr->session_key); usr->session_key_len = hash_length(usr->alg); calculate_M(usr->alg, usr->ng, usr->M, usr->username, s, usr->A, B, usr->session_key, usr->session_key_len); calculate_H_AMK(usr->alg, usr->H_AMK, usr->A, usr->M, usr->session_key, usr->session_key_len); *bytes_M = usr->M; if (len_M) *len_M = hash_length(usr->alg); } else { *bytes_M = NULL; if (len_M) *len_M = 0; } cleanup2: bnum_free(x); bnum_free(u); cleanup1: bnum_free(tmp3); bnum_free(tmp2); bnum_free(tmp1); bnum_free(v); bnum_free(k); bnum_free(B); bnum_free(s); } static void srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK) { if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->alg)) == 0) usr->authenticated = 1; } /* ----------------------- SRP for the server side -------------------------- */ static int srp_create_salted_verification_key(enum hash_alg alg, SRP_NGType ng_type, const char *username, const unsigned char *password, int len_password, unsigned char **bytes_s, int *len_s, unsigned char **bytes_v, int *len_v, const char *n_hex, const char *g_hex ) { bnum s, v, x; NGConstant *ng; bnum_new(s); bnum_new(v); x = NULL; ng = new_ng(ng_type, n_hex, g_hex); *bytes_s = NULL; *bytes_v = NULL; if (!s || !v || !ng) goto error; bnum_random(s, 128); // MODIFIED from csrp's BN_rand(s, 32, -1, 0) #ifdef DEBUG_PAIR bnum_dump("Random value of s:\n", s); #endif x = calculate_x(alg, s, username, password, len_password); if (!x) goto error; bnum_modexp(v, ng->g, x, ng->N); *len_s = bnum_num_bytes(s); *len_v = bnum_num_bytes(v); *bytes_s = malloc(*len_s); *bytes_v = malloc(*len_v); if (!(*bytes_s) || !(*bytes_v)) goto error; bnum_bn2bin(s, (unsigned char *) *bytes_s, *len_s); bnum_bn2bin(v, (unsigned char *) *bytes_v, *len_v); free_ng(ng); bnum_free(s); bnum_free(v); bnum_free(x); return 0; error: free(*bytes_s); free(*bytes_v); free_ng(ng); bnum_free(s); bnum_free(v); bnum_free(x); return -1; } static int srp_verifier_start_authentication(enum hash_alg alg, SRP_NGType ng_type, const unsigned char *bytes_v, int len_v, unsigned char **bytes_b, int *len_b, unsigned char **bytes_B, int *len_B, const char *n_hex, const char *g_hex) { bnum v, k, b, B, tmp1, tmp2; NGConstant *ng; v = NULL; k = NULL; bnum_new(b); bnum_new(B); bnum_new(tmp1); bnum_new(tmp2); *len_b = 0; *bytes_b = NULL; *len_B = 0; *bytes_B = NULL; ng = new_ng(ng_type, n_hex, g_hex); if (!b || !B || !tmp1 || !tmp2 || !ng) goto error; bnum_bin2bn(v, bytes_v, len_v); bnum_random(b, 256); // MODIFIED from BN_rand(b, 256, -1, 0) #ifdef DEBUG_PAIR bnum_dump("Random value of b:\n", b); #endif k = H_nn_pad(alg, ng->N, ng->g, ng->N_len); // MODIFIED from H_nn(alg, ng->N, ng->g) if (!k) goto error; // B = kv + g^b bnum_mul(tmp1, k, v); bnum_modexp(tmp2, ng->g, b, ng->N); bnum_modadd(B, tmp1, tmp2, ng->N); *len_B = bnum_num_bytes(B); *len_b = bnum_num_bytes(b); *bytes_B = malloc(*len_B); *bytes_b = malloc(*len_b); if (!(*bytes_B) || !(*bytes_b)) goto error; bnum_bn2bin(B, (unsigned char *) *bytes_B, *len_B); bnum_bn2bin(b, (unsigned char *) *bytes_b, *len_b); bnum_free(b); bnum_free(B); bnum_free(v); bnum_free(k); bnum_free(tmp1); bnum_free(tmp2); free_ng(ng); return 0; error: free(*bytes_B); free(*bytes_b); bnum_free(b); bnum_free(B); bnum_free(v); bnum_free(k); bnum_free(tmp1); bnum_free(tmp2); free_ng(ng); return -1; } static void srp_verifier_free(struct SRPVerifier *ver) { if (!ver) return; free_ng(ver->ng); free(ver->username); memset(ver, 0, sizeof(struct SRPVerifier)); free(ver); } static struct SRPVerifier * srp_verifier_new(enum hash_alg alg, SRP_NGType ng_type, const char *username, const unsigned char *bytes_s, int len_s, const unsigned char *bytes_v, int len_v, const unsigned char *bytes_A, int len_A, const unsigned char *bytes_b, int len_b, const unsigned char *bytes_B, int len_B, const char *n_hex, const char *g_hex ) { struct SRPVerifier *ver = NULL; bnum s, v, A, b, B, S, tmp1, tmp2, u, k; NGConstant *ng; size_t ulen; bnum_bin2bn(s, bytes_s, len_s); bnum_bin2bn(v, bytes_v, len_v); bnum_bin2bn(A, bytes_A, len_A); bnum_bin2bn(b, bytes_b, len_b); bnum_bin2bn(B, bytes_B, len_B); bnum_new(S); bnum_new(tmp1); bnum_new(tmp2); u = NULL; k = NULL; ng = new_ng(ng_type, n_hex, g_hex); if (!s || !v || !A || !B || !S || !b || !tmp1 || !tmp2 || !ng) goto error; ver = calloc(1, sizeof(struct SRPVerifier)); if (!ver) goto error; ulen = strlen(username) + 1; ver->alg = alg; ver->ng = ng; ver->username = malloc(ulen); if (!ver->username) goto error; memcpy(ver->username, username, ulen); ver->authenticated = 0; // SRP-6a safety check bnum_mod(tmp1, A, ng->N); if (bnum_is_zero(tmp1)) goto error; k = H_nn_pad(alg, ng->N, ng->g, ng->N_len); // MODIFIED from H_nn(alg, ng->N, ng->g) u = H_nn_pad(alg, A, B, ng->N_len); // MODIFIED from H_nn(alg, A, B) // S = (A *(v^u)) ^ b bnum_modexp(tmp1, v, u, ng->N); bnum_mul(tmp2, A, tmp1); bnum_modexp(S, tmp2, b, ng->N); hash_num(alg, S, ver->session_key); ver->session_key_len = hash_length(ver->alg); calculate_M(alg, ng, ver->M, username, s, A, B, ver->session_key, ver->session_key_len); calculate_H_AMK(alg, ver->H_AMK, A, ver->M, ver->session_key, ver->session_key_len); ver->bytes_B = bytes_B; bnum_free(s); bnum_free(v); bnum_free(A); bnum_free(u); bnum_free(k); bnum_free(B); bnum_free(S); bnum_free(b); bnum_free(tmp1); bnum_free(tmp2); return ver; error: srp_verifier_free(ver); bnum_free(s); bnum_free(v); bnum_free(A); bnum_free(u); bnum_free(k); bnum_free(B); bnum_free(S); bnum_free(b); bnum_free(tmp1); bnum_free(tmp2); return NULL; } // user_M must be exactly SHA512_DIGEST_LENGTH bytes in size static void srp_verifier_verify_session(struct SRPVerifier *ver, const unsigned char *user_M, const unsigned char **bytes_HAMK) { if (memcmp(ver->M, user_M, hash_length(ver->alg)) == 0) { ver->authenticated = 1; *bytes_HAMK = ver->H_AMK; } else *bytes_HAMK = NULL; } static const unsigned char * srp_verifier_get_session_key(struct SRPVerifier *ver, int *key_length) { if (key_length) *key_length = hash_length(ver->alg); return ver->session_key; } /* -------------------------------- HELPERS --------------------------------- */ static void hexread(uint8_t *out, size_t out_len, const char *in) { char hex[] = { 0, 0, 0 }; int i; for (i = 0; i < out_len; i++, in+=2) { hex[0] = in[0]; hex[1] = in[1]; out[i] = strtol(hex, NULL, 16); } } static pair_tlv_values_t * message_process(const uint8_t *data, size_t data_len, const char **errmsg) { pair_tlv_values_t *response; pair_tlv_t *error; int ret; response = pair_tlv_new(); if (!response) { *errmsg = "Out of memory\n"; return NULL; } ret = pair_tlv_parse(data, data_len, response); if (ret < 0) { *errmsg = "Could not parse TLV\n"; goto error; } #ifdef DEBUG_PAIR pair_tlv_debug(response); #endif error = pair_tlv_get_value(response, TLVType_Error); if (error) { if (error->value[0] == TLVError_Authentication) *errmsg = "Device returned an authentication failure"; else if (error->value[0] == TLVError_Backoff) *errmsg = "Device told us to back off pairing attempts\n"; else if (error->value[0] == TLVError_MaxPeers) *errmsg = "Max peers trying to connect to device\n"; else if (error->value[0] == TLVError_MaxTries) *errmsg = "Max pairing attemps reached\n"; else if (error->value[0] == TLVError_Unavailable) *errmsg = "Device is unuavailble at this time\n"; else *errmsg = "Device is busy/returned unknown error\n"; goto error; } return response; error: pair_tlv_free(response); return NULL; } /* Executes SHA512 RFC 5869 extract + expand, writing a derived key to okm hkdfExtract(SHA512, salt, salt_len, ikm, ikm_len, prk); hkdfExpand(SHA512, prk, SHA512_LEN, info, info_len, okm, okm_len); */ static int hkdf_extract_expand(uint8_t *okm, size_t okm_len, const uint8_t *ikm, size_t ikm_len, enum pair_keys pair_key) { #ifdef CONFIG_OPENSSL #include EVP_PKEY_CTX *pctx; if (okm_len > SHA512_DIGEST_LENGTH) return -1; if (! (pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL))) return -1; if (EVP_PKEY_derive_init(pctx) <= 0) goto error; if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha512()) <= 0) goto error; if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (const unsigned char *)pair_keys_map[pair_key].salt, strlen(pair_keys_map[pair_key].salt)) <= 0) goto error; if (EVP_PKEY_CTX_set1_hkdf_key(pctx, ikm, ikm_len) <= 0) goto error; if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (const unsigned char *)pair_keys_map[pair_key].info, strlen(pair_keys_map[pair_key].info)) <= 0) goto error; if (EVP_PKEY_derive(pctx, okm, &okm_len) <= 0) goto error; EVP_PKEY_CTX_free(pctx); return 0; error: EVP_PKEY_CTX_free(pctx); return -1; #elif CONFIG_GCRYPT uint8_t prk[SHA512_DIGEST_LENGTH]; gcry_md_hd_t hmac_handle; if (okm_len > SHA512_DIGEST_LENGTH) return -1; // Below calculation not valid if output is larger than hash size if (gcry_md_open(&hmac_handle, GCRY_MD_SHA512, GCRY_MD_FLAG_HMAC) != GPG_ERR_NO_ERROR) return -1; if (gcry_md_setkey(hmac_handle, (const unsigned char *)pair_keys_map[pair_key].salt, strlen(pair_keys_map[pair_key].salt)) != GPG_ERR_NO_ERROR) goto error; gcry_md_write(hmac_handle, ikm, ikm_len); memcpy(prk, gcry_md_read(hmac_handle, 0), sizeof(prk)); gcry_md_reset(hmac_handle); if (gcry_md_setkey(hmac_handle, prk, sizeof(prk)) != GPG_ERR_NO_ERROR) goto error; gcry_md_write(hmac_handle, (const unsigned char *)pair_keys_map[pair_key].info, strlen(pair_keys_map[pair_key].info)); gcry_md_putc(hmac_handle, 1); memcpy(okm, gcry_md_read(hmac_handle, 0), okm_len); gcry_md_close(hmac_handle); return 0; error: gcry_md_close(hmac_handle); return -1; #else return -1; #endif } static int encrypt_chacha(uint8_t *cipher, const uint8_t *plain, size_t plain_len, const uint8_t *key, size_t key_len, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len, const uint8_t nonce[NONCE_LENGTH]) { #ifdef CONFIG_OPENSSL EVP_CIPHER_CTX *ctx; int len; if (! (ctx = EVP_CIPHER_CTX_new())) return -1; if (EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce) != 1) goto error; if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) // Maybe not necessary goto error; if (ad_len > 0 && EVP_EncryptUpdate(ctx, NULL, &len, ad, ad_len) != 1) goto error; if (EVP_EncryptUpdate(ctx, cipher, &len, plain, plain_len) != 1) goto error; assert(len == plain_len); if (EVP_EncryptFinal_ex(ctx, NULL, &len) != 1) goto error; if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, tag) != 1) goto error; EVP_CIPHER_CTX_free(ctx); return 0; error: EVP_CIPHER_CTX_free(ctx); return -1; #elif CONFIG_GCRYPT gcry_cipher_hd_t hd; if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0) != GPG_ERR_NO_ERROR) return -1; if (gcry_cipher_setkey(hd, key, key_len) != GPG_ERR_NO_ERROR) goto error; if (gcry_cipher_setiv(hd, nonce, NONCE_LENGTH) != GPG_ERR_NO_ERROR) goto error; if (ad_len > 0 && gcry_cipher_authenticate(hd, ad, ad_len) != GPG_ERR_NO_ERROR) goto error; if (gcry_cipher_encrypt(hd, cipher, plain_len, plain, plain_len) != GPG_ERR_NO_ERROR) goto error; if (gcry_cipher_gettag(hd, tag, tag_len) != GPG_ERR_NO_ERROR) goto error; gcry_cipher_close(hd); return 0; error: gcry_cipher_close(hd); return -1; #else return -1; #endif } static int decrypt_chacha(uint8_t *plain, const uint8_t *cipher, size_t cipher_len, const uint8_t *key, size_t key_len, const void *ad, size_t ad_len, uint8_t *tag, size_t tag_len, const uint8_t nonce[NONCE_LENGTH]) { #ifdef CONFIG_OPENSSL EVP_CIPHER_CTX *ctx; int len; if (! (ctx = EVP_CIPHER_CTX_new())) return -1; if (EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce) != 1) goto error; if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) // Maybe not necessary goto error; if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag) != 1) goto error; if (ad_len > 0 && EVP_DecryptUpdate(ctx, NULL, &len, ad, ad_len) != 1) goto error; if (EVP_DecryptUpdate(ctx, plain, &len, cipher, cipher_len) != 1) goto error; if (EVP_DecryptFinal_ex(ctx, NULL, &len) != 1) goto error; EVP_CIPHER_CTX_free(ctx); return 0; error: EVP_CIPHER_CTX_free(ctx); return -1; #elif CONFIG_GCRYPT gcry_cipher_hd_t hd; if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0) != GPG_ERR_NO_ERROR) return -1; if (gcry_cipher_setkey(hd, key, key_len) != GPG_ERR_NO_ERROR) goto error; if (gcry_cipher_setiv(hd, nonce, NONCE_LENGTH) != GPG_ERR_NO_ERROR) goto error; if (ad_len > 0 && gcry_cipher_authenticate(hd, ad, ad_len) != GPG_ERR_NO_ERROR) goto error; if (gcry_cipher_decrypt(hd, plain, cipher_len, cipher, cipher_len) != GPG_ERR_NO_ERROR) goto error; if (gcry_cipher_checktag(hd, tag, tag_len) != GPG_ERR_NO_ERROR) goto error; gcry_cipher_close(hd); return 0; error: gcry_cipher_close(hd); return -1; #else return -1; #endif } static int create_info(uint8_t *info, size_t *info_len, uint8_t *a, size_t a_len, uint8_t *b, size_t b_len, uint8_t *c, size_t c_len) { if (a_len + b_len + c_len > *info_len) return -1; *info_len = a_len + b_len + c_len; memcpy(info, a, a_len); memcpy(info + a_len, b, b_len); memcpy(info + a_len + b_len, c, c_len); return 0; } static int create_and_sign_device_info(uint8_t *msg, size_t *msg_len, const char *device_id, uint8_t *device_x, size_t device_x_len, uint8_t *pk, size_t pk_len, uint8_t *sk) { pair_tlv_values_t *tlv; uint8_t device_info[256]; size_t device_info_len; size_t device_id_len; uint8_t signature[crypto_sign_BYTES]; int ret; device_id_len = strlen(device_id); device_info_len = sizeof(device_info); ret = create_info(device_info, &device_info_len, device_x, device_x_len, (uint8_t *)device_id, device_id_len, pk, pk_len); if (ret < 0) return -1; crypto_sign_detached(signature, NULL, device_info, device_info_len, sk); tlv = pair_tlv_new(); pair_tlv_add_value(tlv, TLVType_Identifier, (unsigned char *)device_id, device_id_len); pair_tlv_add_value(tlv, TLVType_Signature, signature, sizeof(signature)); ret = pair_tlv_format(tlv, msg, msg_len); pair_tlv_free(tlv); return ret; } static int create_and_sign_accessory_info(uint8_t *msg, size_t *msg_len, uint8_t *server_pk, size_t server_pk_len, const char *accessory_id, uint8_t *client_pk, size_t client_pk_len, uint8_t *sk) { pair_tlv_values_t *tlv; uint8_t accessory_info[256]; size_t accessory_info_len; size_t accessory_id_len; uint8_t signature[crypto_sign_BYTES]; int ret; accessory_id_len = strlen(accessory_id); accessory_info_len = sizeof(accessory_info); ret = create_info(accessory_info, &accessory_info_len, server_pk, server_pk_len, (uint8_t *)accessory_id, accessory_id_len, client_pk, client_pk_len); if (ret < 0) return -1; crypto_sign_detached(signature, NULL, accessory_info, accessory_info_len, sk); tlv = pair_tlv_new(); pair_tlv_add_value(tlv, TLVType_Identifier, (unsigned char *)accessory_id, accessory_id_len); pair_tlv_add_value(tlv, TLVType_Signature, signature, sizeof(signature)); ret = pair_tlv_format(tlv, msg, msg_len); pair_tlv_free(tlv); return ret; } static int verify_info(uint8_t *signature, uint8_t *pk, uint8_t *a, size_t a_len, uint8_t *b, size_t b_len, uint8_t *c, size_t c_len) { uint8_t info[256]; size_t info_len; int ret; info_len = sizeof(info); ret = create_info(info, &info_len, a, a_len, b, b_len, c, c_len); if (ret < 0) return -1; return crypto_sign_verify_detached(signature, info, info_len, pk); } /* ------------------------- CLIENT IMPLEMENTATION -------------------------- */ static int 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; if (!is_initialized()) return -1; if (handle->type == &pair_client_homekit_normal) { if (!pin || strlen(pin) < 4) return -1; } else if (handle->type == &pair_client_homekit_transient && !pin) { pin = "3939"; } if (device_id && strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX) return -1; memcpy(sctx->pin, pin, sizeof(sctx->pin)); sctx->add_cb = add_cb; sctx->add_cb_arg = cb_arg; if (device_id) memcpy(sctx->device_id, device_id, strlen(device_id)); crypto_sign_keypair(sctx->public_key, sctx->private_key); #ifdef DEBUG_PAIR hexdump("Client public key:\n", sctx->public_key, sizeof(sctx->public_key)); hexdump("Client private key:\n", sctx->private_key, sizeof(sctx->private_key)); #endif return 0; } static void client_setup_free(struct pair_setup_context *handle) { struct pair_client_setup_context *sctx = &handle->sctx.client; srp_user_free(sctx->user); free(sctx->pkB); free(sctx->M2); free(sctx->salt); free(sctx->epk); free(sctx->authtag); } static uint8_t * client_setup_request1(size_t *len, struct pair_setup_context *handle) { struct pair_client_setup_context *sctx = &handle->sctx.client; pair_tlv_values_t *request; uint8_t *data; size_t data_len; uint8_t method; uint8_t flags; int endian_test = 1; int ret; data_len = REQUEST_BUFSIZE; data = malloc(data_len); request = pair_tlv_new(); // Test here instead of setup_new() so we can give an error message if(*(char *)&endian_test != 1) { handle->errmsg = "Setup request 1: No support for big endian architechture"; goto error; } sctx->user = srp_user_new(HASH_SHA512, SRP_NG_3072, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), 0, 0); if (!sctx->user) { handle->errmsg = "Setup request 1: Create SRP user failed"; goto error; } method = PairingMethodPairSetup; pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG01].state, sizeof(pair_keys_map[PAIR_SETUP_MSG01].state)); pair_tlv_add_value(request, TLVType_Method, &method, sizeof(method)); if (handle->type == &pair_client_homekit_transient) { flags = PairingFlagsTransient; pair_tlv_add_value(request, TLVType_Flags, &flags, sizeof(flags)); } ret = pair_tlv_format(request, data, &data_len); if (ret < 0) { handle->errmsg = "Setup request 1: pair_tlv_format returned an error"; goto error; } *len = data_len; pair_tlv_free(request); return data; error: pair_tlv_free(request); free(data); return NULL; } static uint8_t * client_setup_request2(size_t *len, struct pair_setup_context *handle) { struct pair_client_setup_context *sctx = &handle->sctx.client; pair_tlv_values_t *request; uint8_t *data; size_t data_len; const char *auth_username = NULL; int ret; data_len = REQUEST_BUFSIZE; data = malloc(data_len); request = pair_tlv_new(); // Calculate A srp_user_start_authentication(sctx->user, &auth_username, &sctx->pkA, &sctx->pkA_len); // Calculate M1 (client proof) srp_user_process_challenge(sctx->user, (const unsigned char *)sctx->salt, sctx->salt_len, (const unsigned char *)sctx->pkB, sctx->pkB_len, &sctx->M1, &sctx->M1_len); pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG03].state, sizeof(pair_keys_map[PAIR_SETUP_MSG03].state)); pair_tlv_add_value(request, TLVType_PublicKey, sctx->pkA, sctx->pkA_len); pair_tlv_add_value(request, TLVType_Proof, sctx->M1, sctx->M1_len); ret = pair_tlv_format(request, data, &data_len); if (ret < 0) { handle->errmsg = "Setup request 2: pair_tlv_format returned an error"; goto error; } *len = data_len; pair_tlv_free(request); return data; error: pair_tlv_free(request); free(data); return NULL; } static uint8_t * client_setup_request3(size_t *len, struct pair_setup_context *handle) { struct pair_client_setup_context *sctx = &handle->sctx.client; pair_tlv_values_t *request; uint8_t *data; size_t data_len; const unsigned char *session_key; int session_key_len; uint8_t device_x[32]; uint8_t nonce[NONCE_LENGTH] = { 0 }; uint8_t tag[AUTHTAG_LENGTH]; uint8_t derived_key[32]; pair_tlv_values_t *append; size_t append_len; uint8_t *encrypted_data = NULL; size_t encrypted_data_len; int ret; data_len = REQUEST_BUFSIZE; data = malloc(data_len); request = pair_tlv_new(); session_key = srp_user_get_session_key(sctx->user, &session_key_len); if (!session_key) { handle->errmsg = "Setup request 3: No valid session key"; goto error; } ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_CONTROLLER_SIGN); if (ret < 0) { handle->errmsg = "Setup request 3: hkdf error getting device_x"; goto error; } ret = create_and_sign_device_info(data, &data_len, sctx->device_id, device_x, sizeof(device_x), sctx->public_key, sizeof(sctx->public_key), sctx->private_key); if (ret < 0) { handle->errmsg = "Setup request 3: error creating signed device info"; goto error; } ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, PAIR_SETUP_MSG05); if (ret < 0) { handle->errmsg = "Setup request 3: hkdf error getting derived_key"; goto error; } // Append TLV-encoded public key to *data, which already has identifier and signature append = pair_tlv_new(); append_len = REQUEST_BUFSIZE - data_len; pair_tlv_add_value(append, TLVType_PublicKey, sctx->public_key, sizeof(sctx->public_key)); ret = pair_tlv_format(append, data + data_len, &append_len); pair_tlv_free(append); if (ret < 0) { handle->errmsg = "Setup request 3: error appending public key to TLV"; goto error; } data_len += append_len; memcpy(nonce + 4, pair_keys_map[PAIR_SETUP_MSG05].nonce, NONCE_LENGTH - 4); encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag encrypted_data = malloc(encrypted_data_len); ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); if (ret < 0) { handle->errmsg = "Setup request 3: Could not encrypt"; goto error; } memcpy(encrypted_data + data_len, tag, sizeof(tag)); pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_SETUP_MSG05].state, sizeof(pair_keys_map[PAIR_SETUP_MSG05].state)); pair_tlv_add_value(request, TLVType_EncryptedData, encrypted_data, encrypted_data_len); data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format ret = pair_tlv_format(request, data, &data_len); if (ret < 0) { handle->errmsg = "Setup request 3: error appending public key to TLV"; goto error; } *len = data_len; free(encrypted_data); pair_tlv_free(request); return data; error: free(encrypted_data); pair_tlv_free(request); free(data); return NULL; } static int 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; pair_tlv_values_t *response; pair_tlv_t *pk; pair_tlv_t *salt; response = message_process(data, data_len, &handle->errmsg); if (!response) { return -1; } pk = pair_tlv_get_value(response, TLVType_PublicKey); salt = pair_tlv_get_value(response, TLVType_Salt); if (!pk || !salt) { handle->errmsg = "Setup response 1: Missing or invalid pk/salt"; goto error; } sctx->pkB_len = pk->size; // 384 sctx->pkB = malloc(sctx->pkB_len); memcpy(sctx->pkB, pk->value, sctx->pkB_len); sctx->salt_len = salt->size; // 16 sctx->salt = malloc(sctx->salt_len); memcpy(sctx->salt, salt->value, sctx->salt_len); pair_tlv_free(response); return 0; error: pair_tlv_free(response); return -1; } static int 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; pair_tlv_values_t *response; pair_tlv_t *proof; const uint8_t *session_key; int session_key_len; response = message_process(data, data_len, &handle->errmsg); if (!response) { return -1; } proof = pair_tlv_get_value(response, TLVType_Proof); if (!proof) { handle->errmsg = "Setup response 2: Missing proof"; goto error; } sctx->M2_len = proof->size; // 64 sctx->M2 = malloc(sctx->M2_len); memcpy(sctx->M2, proof->value, sctx->M2_len); // Check M2 srp_user_verify_session(sctx->user, (const unsigned char *)sctx->M2); if (!srp_user_is_authenticated(sctx->user)) { handle->errmsg = "Setup response 2: Server authentication failed"; goto error; } if (handle->type == &pair_client_homekit_transient) { session_key = srp_user_get_session_key(sctx->user, &session_key_len); if (!session_key) { handle->errmsg = "Setup response 2: Could not compute session key"; goto error; } assert(sizeof(handle->result.shared_secret) >= session_key_len); memcpy(handle->result.shared_secret, session_key, session_key_len); handle->result.shared_secret_len = session_key_len; handle->status = PAIR_STATUS_COMPLETED; } pair_tlv_free(response); return 0; error: pair_tlv_free(response); return -1; } static int 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; pair_tlv_values_t *response; pair_tlv_t *encrypted_data; pair_tlv_t *device_id; pair_tlv_t *pk; pair_tlv_t *signature; uint8_t nonce[NONCE_LENGTH] = { 0 }; uint8_t tag[AUTHTAG_LENGTH]; uint8_t derived_key[32]; size_t encrypted_len; uint8_t *decrypted_data = NULL; const uint8_t *session_key; int session_key_len; uint8_t device_x[32]; int ret; response = message_process(data, data_len, &handle->errmsg); if (!response) { return -1; } encrypted_data = pair_tlv_get_value(response, TLVType_EncryptedData); if (!encrypted_data) { handle->errmsg = "Setup response 3: Missing encrypted_data"; goto error; } session_key = srp_user_get_session_key(sctx->user, &session_key_len); if (!session_key) { handle->errmsg = "Setup response 3: No valid session key"; goto error; } ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, PAIR_SETUP_MSG06); if (ret < 0) { handle->errmsg = "Setup response 3: hkdf error getting derived_key"; goto error; } // encrypted_data->value consists of the encrypted payload + the auth tag if (encrypted_data->size < AUTHTAG_LENGTH) { handle->errmsg = "Setup response 3: Invalid encrypted data"; goto error; } encrypted_len = encrypted_data->size - AUTHTAG_LENGTH; memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH); memcpy(nonce + 4, pair_keys_map[PAIR_SETUP_MSG06].nonce, NONCE_LENGTH - 4); decrypted_data = malloc(encrypted_len); ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); if (ret < 0) { handle->errmsg = "Setup response 3: Decryption error"; goto error; } pair_tlv_free(response); response = message_process(decrypted_data, encrypted_len, &handle->errmsg); if (!response) { goto error; } ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_ACCESSORY_SIGN); if (ret < 0) { handle->errmsg = "Setup response 3: hkdf error getting device_x"; goto error; } device_id = pair_tlv_get_value(response, TLVType_Identifier); pk = pair_tlv_get_value(response, TLVType_PublicKey); signature = pair_tlv_get_value(response, TLVType_Signature); if (!device_id || device_id->size >= sizeof(handle->result.device_id) || !pk || pk->size != crypto_sign_PUBLICKEYBYTES || !signature || signature->size != crypto_sign_BYTES) { handle->errmsg = "Setup response 3: Missing/invalid device ID, public key or signature"; goto error; } ret = verify_info(signature->value, pk->value, device_x, sizeof(device_x), device_id->value, device_id->size, pk->value, pk->size); if (ret < 0) { handle->errmsg = "Setup request 3: Invalid signature"; goto error; } assert(sizeof(handle->result.client_private_key) == sizeof(sctx->private_key)); assert(sizeof(handle->result.client_public_key) == sizeof(sctx->public_key)); assert(sizeof(handle->result.server_public_key) == pk->size); 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)); memcpy(handle->result.server_public_key, pk->value, pk->size); memcpy(handle->result.device_id, device_id->value, device_id->size); if (sctx->add_cb) sctx->add_cb(handle->result.server_public_key, handle->result.device_id, sctx->add_cb_arg); handle->status = PAIR_STATUS_COMPLETED; free(decrypted_data); pair_tlv_free(response); return 0; error: free(decrypted_data); pair_tlv_free(response); return -1; } static int client_setup_result(struct pair_setup_context *handle) { char *ptr; int i; assert(sizeof(handle->result_str) >= 2 * sizeof(handle->result.client_private_key) + 2 * sizeof(handle->result.server_public_key) + 1); // It is enough to export the private key, since the public key can be // extracted from that with crypto_sign_ed25519_sk_to_pk (it is the last 32 // bytes) ptr = handle->result_str; for (i = 0; i < sizeof(handle->result.client_private_key); i++) ptr += sprintf(ptr, "%02x", handle->result.client_private_key[i]); // 2 x 64 bytes for (i = 0; i < sizeof(handle->result.server_public_key); i++) ptr += sprintf(ptr, "%02x", handle->result.server_public_key[i]); // 2 x 32 bytes *ptr = '\0'; return 0; } static int 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; size_t hexkey_len; if (!is_initialized()) return -1; if (!device_id || strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX) return -1; if (!client_setup_keys) return -1; hexkey_len = strlen(client_setup_keys); if (hexkey_len == 2 * sizeof(vctx->client_private_key) + 2 * sizeof(vctx->server_public_key)) { hexread(vctx->client_private_key, sizeof(vctx->client_private_key), client_setup_keys); hexread(vctx->server_public_key, sizeof(vctx->server_public_key), client_setup_keys + 2 * sizeof(vctx->client_private_key)); vctx->verify_server_signature = true; } else if (hexkey_len == 2 * sizeof(vctx->client_private_key)) // No server public key known, so signature validation will be skipped { hexread(vctx->client_private_key, sizeof(vctx->client_private_key), client_setup_keys); } else return -1; crypto_sign_ed25519_sk_to_pk(vctx->client_public_key, vctx->client_private_key); snprintf(vctx->device_id, sizeof(vctx->device_id), "%s", device_id); return 0; } static uint8_t * client_verify_request1(size_t *len, struct pair_verify_context *handle) { struct pair_client_verify_context *vctx = &handle->vctx.client; // const uint8_t basepoint[crypto_scalarmult_BYTES] = {9}; // 32 bytes pair_tlv_values_t *request; uint8_t *data; size_t data_len; int ret; data_len = REQUEST_BUFSIZE; data = malloc(data_len); request = pair_tlv_new(); crypto_box_keypair(vctx->client_eph_public_key, vctx->client_eph_private_key); /* // TODO keep around in case box_keypair doesn't work ret = crypto_scalarmult(vctx->client_eph_public_key, vctx->client_eph_private_key, basepoint); if (ret < 0) { handle->errmsg = "Verify request 1: Curve 25519 returned an error"; goto error; } */ pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_VERIFY_MSG01].state, sizeof(pair_keys_map[PAIR_VERIFY_MSG01].state)); pair_tlv_add_value(request, TLVType_PublicKey, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key)); ret = pair_tlv_format(request, data, &data_len); if (ret < 0) { handle->errmsg = "Verify request 1: pair_tlv_format returned an error"; goto error; } *len = data_len; pair_tlv_free(request); return data; error: pair_tlv_free(request); free(data); return NULL; } static uint8_t * client_verify_request2(size_t *len, struct pair_verify_context *handle) { struct pair_client_verify_context *vctx = &handle->vctx.client; pair_tlv_values_t *request; uint8_t *data; size_t data_len; uint8_t nonce[NONCE_LENGTH] = { 0 }; uint8_t tag[AUTHTAG_LENGTH]; uint8_t derived_key[32]; uint8_t *encrypted_data = NULL; size_t encrypted_data_len; int ret; data_len = REQUEST_BUFSIZE; data = malloc(data_len); request = pair_tlv_new(); ret = create_and_sign_device_info(data, &data_len, vctx->device_id, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key), vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key), vctx->client_private_key); if (ret < 0) { handle->errmsg = "Verify request 2: error creating signed device info"; goto error; } ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), PAIR_VERIFY_MSG03); if (ret < 0) { handle->errmsg = "Verify request 2: hkdf error getting derived_key"; goto error; } memcpy(nonce + 4, pair_keys_map[PAIR_VERIFY_MSG03].nonce, NONCE_LENGTH - 4); encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag encrypted_data = malloc(encrypted_data_len); ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); if (ret < 0) { handle->errmsg = "Verify request 2: Could not encrypt"; goto error; } memcpy(encrypted_data + data_len, tag, sizeof(tag)); pair_tlv_add_value(request, TLVType_State, &pair_keys_map[PAIR_VERIFY_MSG03].state, sizeof(pair_keys_map[PAIR_VERIFY_MSG03].state)); pair_tlv_add_value(request, TLVType_EncryptedData, encrypted_data, encrypted_data_len); data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format ret = pair_tlv_format(request, data, &data_len); if (ret < 0) { handle->errmsg = "Verify request 2: pair_tlv_format returned an error"; goto error; } *len = data_len; free(encrypted_data); pair_tlv_free(request); return data; error: free(encrypted_data); pair_tlv_free(request); free(data); return NULL; } static int 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; pair_tlv_values_t *response; pair_tlv_t *encrypted_data; pair_tlv_t *public_key; pair_tlv_t *device_id; pair_tlv_t *signature; uint8_t nonce[NONCE_LENGTH] = { 0 }; uint8_t tag[AUTHTAG_LENGTH]; uint8_t derived_key[32]; size_t encrypted_len; uint8_t *decrypted_data = NULL; int ret; response = message_process(data, data_len, &handle->errmsg); if (!response) { return -1; } encrypted_data = pair_tlv_get_value(response, TLVType_EncryptedData); if (!encrypted_data) { handle->errmsg = "Verify response 1: Missing encrypted_data"; goto error; } public_key = pair_tlv_get_value(response, TLVType_PublicKey); if (!public_key || public_key->size != sizeof(vctx->server_eph_public_key)) { handle->errmsg = "Verify response 1: Missing or invalid public_key"; goto error; } memcpy(vctx->server_eph_public_key, public_key->value, sizeof(vctx->server_eph_public_key)); ret = crypto_scalarmult(vctx->shared_secret, vctx->client_eph_private_key, vctx->server_eph_public_key); if (ret < 0) { handle->errmsg = "Verify response 1: Curve 25519 returned an error"; goto error; } ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), PAIR_VERIFY_MSG02); if (ret < 0) { handle->errmsg = "Verify response 1: hkdf error getting derived_key"; goto error; } // encrypted_data->value consists of the encrypted payload + the auth tag if (encrypted_data->size < AUTHTAG_LENGTH) { handle->errmsg = "Verify response 1: Invalid encrypted data"; goto error; } encrypted_len = encrypted_data->size - AUTHTAG_LENGTH; memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH); memcpy(nonce + 4, pair_keys_map[PAIR_VERIFY_MSG02].nonce, NONCE_LENGTH - 4); decrypted_data = malloc(encrypted_len); ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); if (ret < 0) { handle->errmsg = "Verify response 1: Decryption error"; goto error; } pair_tlv_free(response); response = message_process(decrypted_data, encrypted_len, &handle->errmsg); if (!response) { goto error; } device_id = pair_tlv_get_value(response, TLVType_Identifier); signature = pair_tlv_get_value(response, TLVType_Signature); if (!device_id || !signature || signature->size != crypto_sign_BYTES) { handle->errmsg = "Verify response 1: Missing device ID or signature"; goto error; } if (vctx->verify_server_signature) { ret = verify_info(signature->value, vctx->server_public_key, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key), device_id->value, device_id->size, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key)); if (ret < 0) { handle->errmsg = "Verify response 1: Invalid signature"; goto error; } } free(decrypted_data); pair_tlv_free(response); return 0; error: free(decrypted_data); pair_tlv_free(response); return -1; } static int client_verify_response2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) { struct pair_client_verify_context *vctx = &handle->vctx.client; pair_tlv_values_t *response; response = message_process(data, data_len, &handle->errmsg); if (!response) { return -1; } memcpy(handle->result.shared_secret, vctx->shared_secret, sizeof(vctx->shared_secret)); handle->result.shared_secret_len = sizeof(vctx->shared_secret); handle->status = PAIR_STATUS_COMPLETED; return 0; } /* ------------------------- SERVER IMPLEMENTATION -------------------------- */ // Use (unsecure) keys seeded from device_id. We need the keys to always be the // same during pair setup and pair verify, since the client saves them after // pair-setup 3, so that the signature in pair-verify 1 can be checked. static void server_keypair(uint8_t *public_key, uint8_t *private_key, const char *device_id) { uint8_t seed[crypto_sign_SEEDBYTES] = { 0 }; size_t len; len = strlen(device_id); if (len > sizeof(seed)) len = sizeof(seed); memcpy(seed, device_id, len); crypto_sign_seed_keypair(public_key, private_key, seed); } static uint8_t * server_auth_failed_response(size_t *len, enum pair_keys msg_state) { pair_tlv_values_t *response; uint8_t *data; size_t data_len; uint8_t error = TLVError_Authentication; data_len = REQUEST_BUFSIZE; data = malloc(data_len); response = pair_tlv_new(); pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); pair_tlv_add_value(response, TLVType_Error, &error, sizeof(error)); pair_tlv_format(response, data, &data_len); pair_tlv_free(response); *len = data_len; return data; } static int server_setup_new(struct pair_setup_context *handle, const char *pin, pair_cb add_cb, void *cb_arg, const char *device_id) { struct pair_server_setup_context *sctx = &handle->sctx.server; if (!is_initialized()) return -1; if (!pin) pin = "3939"; if (!device_id || strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX) return -1; memcpy(sctx->pin, pin, sizeof(sctx->pin)); sctx->add_cb = add_cb; sctx->add_cb_arg = cb_arg; snprintf(sctx->device_id, sizeof(sctx->device_id), "%s", device_id); server_keypair(sctx->public_key, sctx->private_key, sctx->device_id); return 0; } static void server_setup_free(struct pair_setup_context *handle) { struct pair_server_setup_context *sctx = &handle->sctx.server; srp_verifier_free(sctx->verifier); free(sctx->pkA); free(sctx->pkB); free(sctx->b); free(sctx->M1); free(sctx->v); free(sctx->salt); } static int server_setup_request1(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) { struct pair_server_setup_context *sctx = &handle->sctx.server; // enum pair_keys msg_state = PAIR_SETUP_MSG01; pair_tlv_values_t *request; pair_tlv_t *method; pair_tlv_t *type; int ret; request = message_process(data, data_len, &handle->errmsg); if (!request) { RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); } method = pair_tlv_get_value(request, TLVType_Method); if (!method || method->size != 1 || method->value[0] != 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 1: Missing or unexpected pairing method in TLV"); } type = pair_tlv_get_value(request, TLVType_Flags); sctx->is_transient = (type && type->size == 1 && type->value[0] == PairingFlagsTransient); // Note this is modified to return a 16 byte salt ret = srp_create_salted_verification_key(HASH_SHA512, SRP_NG_3072, USERNAME, (unsigned char *)sctx->pin, sizeof(sctx->pin), &sctx->salt, &sctx->salt_len, &sctx->v, &sctx->v_len, NULL, NULL); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 1: Could not create verification key"); } ret = srp_verifier_start_authentication(HASH_SHA512, SRP_NG_3072, sctx->v, sctx->v_len, &sctx->b, &sctx->b_len, &sctx->pkB, &sctx->pkB_len, NULL, NULL); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 1: Could not compute B"); } pair_tlv_free(request); return 0; error: pair_tlv_free(request); return -1; } static int server_setup_request2(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) { struct pair_server_setup_context *sctx = &handle->sctx.server; // enum pair_keys msg_state = PAIR_SETUP_MSG03; pair_tlv_values_t *request; pair_tlv_t *pk; pair_tlv_t *proof; request = message_process(data, data_len, &handle->errmsg); if (!request) { RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); } pk = pair_tlv_get_value(request, TLVType_PublicKey); proof = pair_tlv_get_value(request, TLVType_Proof); if (!pk || !proof) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 2: Missing pkA or proof"); } sctx->pkA_len = pk->size; // 384 sctx->pkA = malloc(sctx->pkA_len); memcpy(sctx->pkA, pk->value, sctx->pkA_len); sctx->M1_len = proof->size; // 64 sctx->M1 = malloc(sctx->M1_len); memcpy(sctx->M1, proof->value, sctx->M1_len); sctx->verifier = srp_verifier_new(HASH_SHA512, SRP_NG_3072, USERNAME, sctx->salt, sctx->salt_len, sctx->v, sctx->v_len, sctx->pkA, sctx->pkA_len, sctx->b, sctx->b_len, sctx->pkB, sctx->pkB_len, NULL, NULL); if (!sctx->verifier) { handle->status = PAIR_STATUS_AUTH_FAILED; goto out; } sctx->M2_len = 64; // 512 bit hash srp_verifier_verify_session(sctx->verifier, sctx->M1, &sctx->M2); if (!sctx->M2) { handle->status = PAIR_STATUS_AUTH_FAILED; goto out; // Not an error, server should give proper TLV-formatet reply } out: pair_tlv_free(request); return 0; error: pair_tlv_free(request); return -1; } static int server_setup_request3(struct pair_setup_context *handle, const uint8_t *data, size_t data_len) { struct pair_server_setup_context *sctx = &handle->sctx.server; enum pair_keys msg_state = PAIR_SETUP_MSG05; pair_tlv_values_t *request; pair_tlv_t *encrypted_data; pair_tlv_t *device_id; pair_tlv_t *pk; pair_tlv_t *signature; const uint8_t *session_key; int session_key_len; uint8_t nonce[NONCE_LENGTH] = { 0 }; uint8_t tag[AUTHTAG_LENGTH]; uint8_t derived_key[32]; size_t encrypted_len; uint8_t *decrypted_data = NULL; uint8_t device_x[32]; int ret; request = message_process(data, data_len, &handle->errmsg); if (!request) { RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); } session_key = srp_verifier_get_session_key(sctx->verifier, &session_key_len); if (!session_key) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: No valid session key"); } ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, msg_state); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: hkdf error getting derived_key"); } encrypted_data = pair_tlv_get_value(request, TLVType_EncryptedData); if (!encrypted_data) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Missing encrypted_data"); } // encrypted_data->value consists of the encrypted payload + the auth tag if (encrypted_data->size < AUTHTAG_LENGTH) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Invalid encrypted data"); } encrypted_len = encrypted_data->size - AUTHTAG_LENGTH; memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH); memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4); decrypted_data = malloc(encrypted_len); ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Decryption error"); } pair_tlv_free(request); request = message_process(decrypted_data, encrypted_len, &handle->errmsg); if (!request) { RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); } ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_CONTROLLER_SIGN); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: hkdf error getting device_x"); } device_id = pair_tlv_get_value(request, TLVType_Identifier); pk = pair_tlv_get_value(request, TLVType_PublicKey); signature = pair_tlv_get_value(request, TLVType_Signature); if (!device_id || device_id->size >= sizeof(handle->result.device_id) || !pk || pk->size != crypto_sign_PUBLICKEYBYTES || !signature || signature->size != crypto_sign_BYTES) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 3: Missing/invalid device ID, public key or signature"); } ret = verify_info(signature->value, pk->value, device_x, sizeof(device_x), device_id->value, device_id->size, pk->value, pk->size); if (ret < 0) { handle->status = PAIR_STATUS_AUTH_FAILED; goto out; } memcpy(handle->result.device_id, device_id->value, device_id->size); memcpy(handle->result.client_public_key, pk->value, pk->size); out: free(decrypted_data); pair_tlv_free(request); return 0; error: free(decrypted_data); pair_tlv_free(request); return -1; } static uint8_t * server_setup_response1(size_t *len, struct pair_setup_context *handle) { struct pair_server_setup_context *sctx = &handle->sctx.server; enum pair_keys msg_state = PAIR_SETUP_MSG02; pair_tlv_values_t *response; uint8_t *data; size_t data_len; int ret; if (handle->status == PAIR_STATUS_AUTH_FAILED) return server_auth_failed_response(len, msg_state); data_len = REQUEST_BUFSIZE; data = malloc(data_len); response = pair_tlv_new(); pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); pair_tlv_add_value(response, TLVType_Salt, sctx->salt, sctx->salt_len); // 16 pair_tlv_add_value(response, TLVType_PublicKey, sctx->pkB, sctx->pkB_len); // 384 ret = pair_tlv_format(response, data, &data_len); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 1: pair_tlv_format returned an error"); } *len = data_len; pair_tlv_free(response); return data; error: free(data); pair_tlv_free(response); return NULL; } static uint8_t * server_setup_response2(size_t *len, struct pair_setup_context *handle) { struct pair_server_setup_context *sctx = &handle->sctx.server; enum pair_keys msg_state = PAIR_SETUP_MSG04; pair_tlv_values_t *response; uint8_t *data; size_t data_len; const uint8_t *session_key; int session_key_len; int ret; if (handle->status == PAIR_STATUS_AUTH_FAILED) return server_auth_failed_response(len, msg_state); data_len = REQUEST_BUFSIZE; data = malloc(data_len); response = pair_tlv_new(); pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); pair_tlv_add_value(response, TLVType_Proof, sctx->M2, sctx->M2_len); // 384 ret = pair_tlv_format(response, data, &data_len); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 2: pair_tlv_format returned an error"); } if (sctx->is_transient) { session_key = srp_verifier_get_session_key(sctx->verifier, &session_key_len); if (!session_key) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup request 2: Could not compute session key"); } assert(sizeof(handle->result.shared_secret) >= session_key_len); memcpy(handle->result.shared_secret, session_key, session_key_len); handle->result.shared_secret_len = session_key_len; handle->status = PAIR_STATUS_COMPLETED; } *len = data_len; pair_tlv_free(response); return data; error: free(data); pair_tlv_free(response); return NULL; } static uint8_t * server_setup_response3(size_t *len, struct pair_setup_context *handle) { struct pair_server_setup_context *sctx = &handle->sctx.server; enum pair_keys msg_state = PAIR_SETUP_MSG06; const uint8_t *session_key; int session_key_len; pair_tlv_values_t *response; uint8_t nonce[NONCE_LENGTH] = { 0 }; uint8_t tag[AUTHTAG_LENGTH]; uint8_t derived_key[32]; pair_tlv_values_t *append; size_t append_len; uint8_t *encrypted_data = NULL; size_t encrypted_data_len; uint8_t *data; size_t data_len; uint8_t device_x[32]; int ret; if (handle->status == PAIR_STATUS_AUTH_FAILED) return server_auth_failed_response(len, msg_state); data_len = REQUEST_BUFSIZE; data = malloc(data_len); response = pair_tlv_new(); session_key = srp_verifier_get_session_key(sctx->verifier, &session_key_len); if (!session_key) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: No valid session key"); } ret = hkdf_extract_expand(device_x, sizeof(device_x), session_key, session_key_len, PAIR_SETUP_ACCESSORY_SIGN); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: hkdf error getting device_x"); } ret = create_and_sign_device_info(data, &data_len, sctx->device_id, device_x, sizeof(device_x), sctx->public_key, sizeof(sctx->public_key), sctx->private_key); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: create device info returned an error"); } // Append TLV-encoded public key to *data, which already has identifier and signature append = pair_tlv_new(); append_len = REQUEST_BUFSIZE - data_len; pair_tlv_add_value(append, TLVType_PublicKey, sctx->public_key, sizeof(sctx->public_key)); ret = pair_tlv_format(append, data + data_len, &append_len); pair_tlv_free(append); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: error appending public key to TLV"); } data_len += append_len; ret = hkdf_extract_expand(derived_key, sizeof(derived_key), session_key, session_key_len, msg_state); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: hkdf error getting derived_key"); } memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4); encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag encrypted_data = malloc(encrypted_data_len); ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: Could not encrypt"); } memcpy(encrypted_data + data_len, tag, sizeof(tag)); pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); pair_tlv_add_value(response, TLVType_EncryptedData, encrypted_data, encrypted_data_len); data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format ret = pair_tlv_format(response, data, &data_len); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Setup response 3: error appending public key to TLV"); } if (sctx->add_cb) sctx->add_cb(handle->result.client_public_key, handle->result.device_id, sctx->add_cb_arg); handle->status = PAIR_STATUS_COMPLETED; *len = data_len; free(encrypted_data); pair_tlv_free(response); return data; error: free(encrypted_data); free(data); pair_tlv_free(response); return NULL; } static int server_verify_new(struct pair_verify_context *handle, const char *client_setup_keys, pair_cb cb, void *cb_arg, const char *device_id) { struct pair_server_verify_context *vctx = &handle->vctx.server; if (!is_initialized()) return -1; if (client_setup_keys) return -1; if (!device_id || strlen(device_id) >= PAIR_AP_DEVICE_ID_LEN_MAX) return -1; snprintf(vctx->device_id, sizeof(vctx->device_id), "%s", device_id); vctx->get_cb = cb; vctx->get_cb_arg = cb_arg; vctx->verify_client_signature = cb; server_keypair(vctx->server_public_key, vctx->server_private_key, vctx->device_id); return 0; } static int server_verify_request1(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) { struct pair_server_verify_context *vctx = &handle->vctx.server; // enum pair_keys msg_state = PAIR_VERIFY_MSG01; pair_tlv_values_t *request; pair_tlv_t *pk; request = message_process(data, data_len, &handle->errmsg); if (!request) { RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); } pk = pair_tlv_get_value(request, TLVType_PublicKey); if (!pk || pk->size != sizeof(vctx->client_eph_public_key)) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 1: Missing or invalid public_key"); } memcpy(vctx->client_eph_public_key, pk->value, sizeof(vctx->client_eph_public_key)); pair_tlv_free(request); return 0; error: pair_tlv_free(request); return -1; } static int server_verify_request2(struct pair_verify_context *handle, const uint8_t *data, size_t data_len) { struct pair_server_verify_context *vctx = &handle->vctx.server; enum pair_keys msg_state = PAIR_VERIFY_MSG03; pair_tlv_values_t *request; pair_tlv_t *encrypted_data; pair_tlv_t *device_id; pair_tlv_t *signature; uint8_t nonce[NONCE_LENGTH] = { 0 }; uint8_t tag[AUTHTAG_LENGTH]; uint8_t derived_key[32]; size_t encrypted_len; uint8_t *decrypted_data = NULL; char id_str[PAIR_AP_DEVICE_ID_LEN_MAX] = { 0 }; uint8_t client_public_key[crypto_sign_PUBLICKEYBYTES]; int ret; request = message_process(data, data_len, &handle->errmsg); if (!request) { RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); } ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), msg_state); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: hkdf error getting derived_key"); } encrypted_data = pair_tlv_get_value(request, TLVType_EncryptedData); if (!encrypted_data) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Missing encrypted_data"); } // encrypted_data->value consists of the encrypted payload + the auth tag if (encrypted_data->size < AUTHTAG_LENGTH) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Invalid encrypted data"); } encrypted_len = encrypted_data->size - AUTHTAG_LENGTH; memcpy(tag, encrypted_data->value + encrypted_len, AUTHTAG_LENGTH); memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4); decrypted_data = malloc(encrypted_len); ret = decrypt_chacha(decrypted_data, encrypted_data->value, encrypted_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Decryption error"); } pair_tlv_free(request); request = message_process(decrypted_data, encrypted_len, &handle->errmsg); if (!request) { RETURN_ERROR(PAIR_STATUS_INVALID, handle->errmsg); } device_id = pair_tlv_get_value(request, TLVType_Identifier); signature = pair_tlv_get_value(request, TLVType_Signature); if (!device_id || !signature || signature->size != crypto_sign_BYTES) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Missing identifier or signature"); } if (vctx->verify_client_signature) { if (device_id->size >= sizeof(id_str)) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify request 2: Device ID from peer is too long"); } memcpy(id_str, device_id->value, device_id->size); ret = vctx->get_cb(client_public_key, id_str, vctx->get_cb_arg); if (ret < 0) { handle->status = PAIR_STATUS_AUTH_FAILED; goto out; } ret = verify_info(signature->value, client_public_key, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key), device_id->value, device_id->size, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key)); if (ret < 0) { handle->status = PAIR_STATUS_AUTH_FAILED; goto out; } } out: free(decrypted_data); pair_tlv_free(request); return 0; error: free(decrypted_data); pair_tlv_free(request); return -1; } static uint8_t * server_verify_response1(size_t *len, struct pair_verify_context *handle) { struct pair_server_verify_context *vctx = &handle->vctx.server; enum pair_keys msg_state = PAIR_VERIFY_MSG02; pair_tlv_values_t *response; uint8_t nonce[NONCE_LENGTH] = { 0 }; uint8_t tag[AUTHTAG_LENGTH]; uint8_t derived_key[32]; uint8_t *encrypted_data = NULL; size_t encrypted_data_len; uint8_t *data; size_t data_len; int ret; if (handle->status == PAIR_STATUS_AUTH_FAILED) return server_auth_failed_response(len, msg_state); data_len = REQUEST_BUFSIZE; data = malloc(data_len); response = pair_tlv_new(); crypto_box_keypair(vctx->server_eph_public_key, vctx->server_eph_private_key); ret = crypto_scalarmult(vctx->shared_secret, vctx->server_eph_private_key, vctx->client_eph_public_key); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: Error generating shared secret"); } ret = create_and_sign_accessory_info(data, &data_len, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key), vctx->device_id, vctx->client_eph_public_key, sizeof(vctx->client_eph_public_key), vctx->server_private_key); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: Error creating device info"); } ret = hkdf_extract_expand(derived_key, sizeof(derived_key), vctx->shared_secret, sizeof(vctx->shared_secret), msg_state); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: hkdf error getting derived_key"); } memcpy(nonce + 4, pair_keys_map[msg_state].nonce, NONCE_LENGTH - 4); encrypted_data_len = data_len + sizeof(tag); // Space for ciphered payload and authtag encrypted_data = malloc(encrypted_data_len); ret = encrypt_chacha(encrypted_data, data, data_len, derived_key, sizeof(derived_key), NULL, 0, tag, sizeof(tag), nonce); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: Could not encrypt"); } memcpy(encrypted_data + data_len, tag, sizeof(tag)); pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); pair_tlv_add_value(response, TLVType_PublicKey, vctx->server_eph_public_key, sizeof(vctx->server_eph_public_key)); pair_tlv_add_value(response, TLVType_EncryptedData, encrypted_data, encrypted_data_len); data_len = REQUEST_BUFSIZE; // Re-using *data, so pass original length to pair_tlv_format ret = pair_tlv_format(response, data, &data_len); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 1: pair_tlv_format returned an error"); } *len = data_len; free(encrypted_data); pair_tlv_free(response); return data; error: free(encrypted_data); free(data); pair_tlv_free(response); return NULL; } static uint8_t * server_verify_response2(size_t *len, struct pair_verify_context *handle) { struct pair_server_verify_context *vctx = &handle->vctx.server; enum pair_keys msg_state = PAIR_VERIFY_MSG04; pair_tlv_values_t *response; uint8_t *data; size_t data_len; int ret; if (handle->status == PAIR_STATUS_AUTH_FAILED) return server_auth_failed_response(len, msg_state); data_len = REQUEST_BUFSIZE; data = malloc(data_len); response = pair_tlv_new(); pair_tlv_add_value(response, TLVType_State, &pair_keys_map[msg_state].state, sizeof(pair_keys_map[msg_state].state)); ret = pair_tlv_format(response, data, &data_len); if (ret < 0) { RETURN_ERROR(PAIR_STATUS_INVALID, "Verify response 2: pair_tlv_format returned an error"); } *len = data_len; memcpy(handle->result.shared_secret, vctx->shared_secret, sizeof(vctx->shared_secret)); handle->result.shared_secret_len = sizeof(vctx->shared_secret); handle->status = PAIR_STATUS_COMPLETED; pair_tlv_free(response); return data; error: free(data); pair_tlv_free(response); return NULL; } static int server_add_remove_request(pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len) { const char *errmsg; pair_tlv_values_t *request; pair_tlv_t *device_id; pair_tlv_t *pk; char id_str[PAIR_AP_DEVICE_ID_LEN_MAX] = { 0 }; uint8_t *public_key = NULL; request = message_process(in, in_len, &errmsg); if (!request) { goto error; } device_id = pair_tlv_get_value(request, TLVType_Identifier); if (!device_id || device_id->size >= sizeof(id_str)) { goto error; } // Only present when adding pk = pair_tlv_get_value(request, TLVType_PublicKey); if (pk && pk->size == crypto_sign_PUBLICKEYBYTES) { public_key = pk->value; } memcpy(id_str, device_id->value, device_id->size); cb(public_key, id_str, cb_arg); pair_tlv_free(request); return 0; error: pair_tlv_free(request); return -1; } static uint8_t * server_add_remove_response(size_t *len) { pair_tlv_values_t *response; uint8_t *data; uint8_t state = 2; size_t data_len; int ret; data_len = REQUEST_BUFSIZE; data = malloc(data_len); response = pair_tlv_new(); pair_tlv_add_value(response, TLVType_State, &state, sizeof(state)); ret = pair_tlv_format(response, data, &data_len); if (ret < 0) { goto error; } *len = data_len; pair_tlv_free(response); return data; error: free(data); pair_tlv_free(response); return NULL; } static int server_add_remove(uint8_t **out, size_t *out_len, pair_cb cb, void *cb_arg, const uint8_t *in, size_t in_len) { int ret; ret = server_add_remove_request(cb, cb_arg, in, in_len); if (ret < 0) return -1; *out = server_add_remove_response(out_len); if (!*out) return -1; return 0; } static int server_list_cb(uint8_t public_key[crypto_sign_PUBLICKEYBYTES], const char *device_id, void *cb_arg) { pair_tlv_values_t *response = cb_arg; pair_tlv_t *previous_id; uint8_t permissions = 1; // Means admin (TODO don't hardcode - let caller set) // If this isn't the first iteration (item) then we must add a separator previous_id = pair_tlv_get_value(response, TLVType_Identifier); if (previous_id) pair_tlv_add_value(response, TLVType_Separator, NULL, 0); pair_tlv_add_value(response, TLVType_Identifier, (unsigned char *)device_id, strlen(device_id)); pair_tlv_add_value(response, TLVType_PublicKey, public_key, crypto_sign_PUBLICKEYBYTES); pair_tlv_add_value(response, TLVType_Permissions, &permissions, sizeof(permissions)); return 0; } static uint8_t * server_list_response(size_t *len, pair_list_cb cb, void *cb_arg) { pair_tlv_values_t *response; uint8_t *data; uint8_t state = 2; size_t data_len; int ret; data_len = REQUEST_BUFSIZE; data = malloc(data_len); response = pair_tlv_new(); pair_tlv_add_value(response, TLVType_State, &state, sizeof(state)); cb(server_list_cb, response, cb_arg); ret = pair_tlv_format(response, data, &data_len); if (ret < 0) { goto error; } *len = data_len; pair_tlv_free(response); return data; error: free(data); pair_tlv_free(response); return NULL; } static int server_list(uint8_t **out, size_t *out_len, pair_list_cb cb, void *cb_arg, const uint8_t *in, size_t in_len) { // Skip reading the request, it just has state = 1 and pair method = // PairingMethodListPairings *out = server_list_response(out_len, cb, cb_arg); if (!*out) return -1; return 0; } /* ----------------------- CIPHERING IMPLEMENTATION ------------------------- */ static void cipher_free(struct pair_cipher_context *cctx) { if (!cctx) return; free(cctx); } static struct pair_cipher_context * cipher_new(struct pair_definition *type, int channel, const uint8_t *shared_secret, size_t shared_secret_len) { struct pair_cipher_context *cctx; enum pair_keys write_key; enum pair_keys read_key; int ret; // Note that events is opposite, probably because it is a reverse connection switch (channel) { case 0: write_key = PAIR_CONTROL_WRITE; read_key = PAIR_CONTROL_READ; break; case 1: write_key = PAIR_EVENTS_READ; read_key = PAIR_EVENTS_WRITE; break; case 2: write_key = PAIR_CONTROL_READ; read_key = PAIR_CONTROL_WRITE; break; case 3: write_key = PAIR_EVENTS_WRITE; read_key = PAIR_EVENTS_READ; break; default: return NULL; } cctx = calloc(1, sizeof(struct pair_cipher_context)); if (!cctx) goto error; cctx->type = type; ret = hkdf_extract_expand(cctx->encryption_key, sizeof(cctx->encryption_key), shared_secret, shared_secret_len, write_key); if (ret < 0) goto error; ret = hkdf_extract_expand(cctx->decryption_key, sizeof(cctx->decryption_key), shared_secret, shared_secret_len, read_key); if (ret < 0) goto error; return cctx; error: pair_cipher_free(cctx); return NULL; } static ssize_t encrypt(uint8_t **ciphertext, size_t *ciphertext_len, const uint8_t *plaintext, size_t plaintext_len, struct pair_cipher_context *cctx) { uint8_t nonce[NONCE_LENGTH] = { 0 }; uint8_t tag[AUTHTAG_LENGTH]; const uint8_t *plain_block; uint8_t *cipher_block; uint16_t block_len; int nblocks; int ret; int i; if (plaintext_len == 0 || !plaintext) return -1; // Encryption is done in blocks, where each block consists of a short, the // encrypted data and an auth tag. The short is the size of the encrypted // data. The encrypted data in the block cannot exceed ENCRYPTED_LEN_MAX. nblocks = 1 + ((plaintext_len - 1) / ENCRYPTED_LEN_MAX); // Ceiling of division *ciphertext_len = nblocks * (sizeof(block_len) + AUTHTAG_LENGTH) + plaintext_len; *ciphertext = malloc(*ciphertext_len); cctx->encryption_counter_prev = cctx->encryption_counter; for (i = 0, plain_block = plaintext, cipher_block = *ciphertext; i < nblocks; i++) { // If it is the last block we will encrypt only the remaining data block_len = (i + 1 == nblocks) ? (plaintext + plaintext_len - plain_block) : ENCRYPTED_LEN_MAX; memcpy(nonce + 4, &(cctx->encryption_counter), sizeof(cctx->encryption_counter));// TODO BE or LE? // Write the ciphered block memcpy(cipher_block, &block_len, sizeof(block_len)); // TODO BE or LE? ret = encrypt_chacha(cipher_block + sizeof(block_len), plain_block, block_len, cctx->encryption_key, sizeof(cctx->encryption_key), &block_len, sizeof(block_len), tag, sizeof(tag), nonce); if (ret < 0) { cctx->errmsg = "Encryption with chacha poly1305 failed"; cctx->encryption_counter = cctx->encryption_counter_prev; free(*ciphertext); return -1; } memcpy(cipher_block + sizeof(block_len) + block_len, tag, AUTHTAG_LENGTH); plain_block += block_len; cipher_block += block_len + sizeof(block_len) + AUTHTAG_LENGTH; cctx->encryption_counter++; } #ifdef DEBUG_PAIR hexdump("Encrypted:\n", *ciphertext, *ciphertext_len); #endif return plain_block - plaintext; } static ssize_t decrypt(uint8_t **plaintext, size_t *plaintext_len, const uint8_t *ciphertext, size_t ciphertext_len, struct pair_cipher_context *cctx) { uint8_t nonce[NONCE_LENGTH] = { 0 }; uint8_t tag[AUTHTAG_LENGTH]; uint8_t *plain_block; const uint8_t *cipher_block; uint16_t block_len; int ret; if (ciphertext_len < sizeof(block_len) || !ciphertext) return -1; // This will allocate more than we need. Since we don't know the number of // blocks in the ciphertext yet we can't calculate the exact required length. *plaintext = malloc(ciphertext_len); cctx->decryption_counter_prev = cctx->decryption_counter; for (plain_block = *plaintext, cipher_block = ciphertext; cipher_block < ciphertext + ciphertext_len; ) { memcpy(&block_len, cipher_block, sizeof(block_len)); // TODO BE or LE? if (cipher_block + block_len + sizeof(block_len) + AUTHTAG_LENGTH > ciphertext + ciphertext_len) { // The remaining ciphertext doesn't contain an entire block, so stop break; } memcpy(tag, cipher_block + sizeof(block_len) + block_len, sizeof(tag)); memcpy(nonce + 4, &(cctx->decryption_counter), sizeof(cctx->decryption_counter));// TODO BE or LE? ret = decrypt_chacha(plain_block, cipher_block + sizeof(block_len), block_len, cctx->decryption_key, sizeof(cctx->decryption_key), &block_len, sizeof(block_len), tag, sizeof(tag), nonce); if (ret < 0) { cctx->errmsg = "Decryption with chacha poly1305 failed"; cctx->decryption_counter = cctx->decryption_counter_prev; free(*plaintext); return -1; } plain_block += block_len; cipher_block += block_len + sizeof(block_len) + AUTHTAG_LENGTH; cctx->decryption_counter++; } *plaintext_len = plain_block - *plaintext; #ifdef DEBUG_PAIR hexdump("Decrypted:\n", *plaintext, *plaintext_len); #endif return cipher_block - ciphertext; } static int state_get(const char **errmsg, const uint8_t *data, size_t data_len) { pair_tlv_values_t *message; pair_tlv_t *state; int ret; if (!data || data_len == 0) { return 0; // state 0 = no incoming data yet -> first request } message = message_process(data, data_len, errmsg); if (!message) { goto error; } state = pair_tlv_get_value(message, TLVType_State); if (!state || state->size != 1) { *errmsg = "Could not get message state"; goto error; } ret = state->value[0]; pair_tlv_free(message); return ret; error: pair_tlv_free(message); return -1; } static void public_key_get(uint8_t server_public_key[crypto_sign_PUBLICKEYBYTES], const char *device_id) { uint8_t private_key[crypto_sign_SECRETKEYBYTES]; server_keypair(server_public_key, private_key, device_id); } const struct pair_definition pair_client_homekit_normal = { .pair_setup_new = client_setup_new, .pair_setup_free = client_setup_free, .pair_setup_result = client_setup_result, .pair_setup_request1 = client_setup_request1, .pair_setup_request2 = client_setup_request2, .pair_setup_request3 = client_setup_request3, .pair_setup_response1 = client_setup_response1, .pair_setup_response2 = client_setup_response2, .pair_setup_response3 = client_setup_response3, .pair_verify_new = client_verify_new, .pair_verify_request1 = client_verify_request1, .pair_verify_request2 = client_verify_request2, .pair_verify_response1 = client_verify_response1, .pair_verify_response2 = client_verify_response2, .pair_cipher_new = cipher_new, .pair_cipher_free = cipher_free, .pair_encrypt = encrypt, .pair_decrypt = decrypt, .pair_state_get = state_get, }; const struct pair_definition pair_client_homekit_transient = { .pair_setup_new = client_setup_new, .pair_setup_free = client_setup_free, .pair_setup_request1 = client_setup_request1, .pair_setup_request2 = client_setup_request2, .pair_setup_request3 = client_setup_request3, .pair_setup_response1 = client_setup_response1, .pair_setup_response2 = client_setup_response2, .pair_setup_response3 = client_setup_response3, .pair_verify_new = client_verify_new, .pair_verify_request1 = client_verify_request1, .pair_verify_request2 = client_verify_request2, .pair_verify_response1 = client_verify_response1, .pair_verify_response2 = client_verify_response2, .pair_cipher_new = cipher_new, .pair_cipher_free = cipher_free, .pair_encrypt = encrypt, .pair_decrypt = decrypt, .pair_state_get = state_get, }; const struct pair_definition pair_server_homekit = { .pair_setup_new = server_setup_new, .pair_setup_free = server_setup_free, .pair_setup_request1 = server_setup_response1, .pair_setup_request2 = server_setup_response2, .pair_setup_request3 = server_setup_response3, .pair_setup_response1 = server_setup_request1, .pair_setup_response2 = server_setup_request2, .pair_setup_response3 = server_setup_request3, .pair_verify_new = server_verify_new, .pair_verify_request1 = server_verify_response1, .pair_verify_request2 = server_verify_response2, .pair_verify_response1 = server_verify_request1, .pair_verify_response2 = server_verify_request2, .pair_add = server_add_remove, .pair_remove = server_add_remove, .pair_list = server_list, .pair_cipher_new = cipher_new, .pair_cipher_free = cipher_free, .pair_encrypt = encrypt, .pair_decrypt = decrypt, .pair_state_get = state_get, .pair_public_key_get = public_key_get, };