Skip to content

Commit

Permalink
Add support for QRCode pairing
Browse files Browse the repository at this point in the history
  • Loading branch information
maximkulkin committed Feb 13, 2019
1 parent 22a8465 commit 19bde63
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile.projbuild
@@ -1,6 +1,7 @@
EXTRA_WOLFSSL_CFLAGS = \
-DWOLFCRYPT_HAVE_SRP \
-DWOLFSSL_SHA512 \
-DWOLFSSL_BASE64_ENCODE \
-DNO_MD5 \
-DNO_SHA \
-DHAVE_HKDF \
Expand Down
1 change: 1 addition & 0 deletions component.mk
Expand Up @@ -39,6 +39,7 @@ ifdef component_compile_rules
EXTRA_WOLFSSL_CFLAGS = \
-DWOLFCRYPT_HAVE_SRP \
-DWOLFSSL_SHA512 \
-DWOLFSSL_BASE64_ENCODE \
-DNO_MD5 \
-DNO_SHA \
-DHAVE_HKDF \
Expand Down
1 change: 1 addition & 0 deletions include/homekit/homekit.h
Expand Up @@ -32,6 +32,7 @@ typedef struct {
char *password;
void (*password_callback)(const char *password);

char *setupId;
// Callback for "POST /resource" to get snapshot image from camera
void (*on_resource)(const char *body, size_t body_size);

Expand Down
35 changes: 35 additions & 0 deletions src/server.c
Expand Up @@ -23,6 +23,8 @@

#include <http-parser/http_parser.h>
#include <cJSON.h>
#include <wolfssl/wolfcrypt/hash.h>
#include <wolfssl/wolfcrypt/coding.h>

#include "base64.h"
#include "crypto.h"
Expand Down Expand Up @@ -3292,6 +3294,28 @@ void homekit_setup_mdns(homekit_server_t *server) {
// accessory category identifier
homekit_mdns_add_txt("ci", "%d", accessory->category);

if (server->config->setupId) {
DEBUG("Accessory Setup ID = %s", server->config->setupId);

size_t data_size = strlen(server->config->setupId) + strlen(server->accessory_id) + 1;
char *data = malloc(data_size);
snprintf(data, data_size, "%s%s", server->config->setupId, server->accessory_id);
data[data_size-1] = 0;

unsigned char shaHash[SHA512_DIGEST_SIZE];
wc_Sha512Hash((const unsigned char *)data, data_size-1, shaHash);

free(data);

unsigned char encodedHash[9];
memset(encodedHash, 0, sizeof(encodedHash));

word32 len = sizeof(encodedHash);
Base64_Encode_NoNl((const unsigned char *)shaHash, 4, encodedHash, &len);

homekit_mdns_add_txt("sh", "%s", encodedHash);
}

homekit_mdns_configure_finalize();
}

Expand Down Expand Up @@ -3367,6 +3391,7 @@ void homekit_server_task(void *args) {
}

#define ISDIGIT(x) isdigit((unsigned char)(x))
#define ISBASE36(x) (isdigit((unsigned char)(x)) || (x >= 'A' && x <= 'Z'))

void homekit_server_init(homekit_server_config_t *config) {
if (!config->accessories) {
Expand All @@ -3393,6 +3418,16 @@ void homekit_server_init(homekit_server_config_t *config) {
}
}

if (config->setupId) {
const char *p = config->setupId;
if (strlen(p) != 4 ||
!(ISBASE36(p[0]) && ISBASE36(p[1]) && ISBASE36(p[2]) && ISBASE36(p[3]))) {
ERROR("Error initializing HomeKit accessory server: "
"invalid setup ID format");
return;
}
}

homekit_accessories_init(config->accessories);

homekit_server_t *server = server_new();
Expand Down
Binary file added tools/Arial Bold.ttf
Binary file not shown.
88 changes: 88 additions & 0 deletions tools/gen_qrcode
@@ -0,0 +1,88 @@
#!/usr/bin/env python
import argparse
import os.path
import PIL.Image
import PIL.ImageDraw
import PIL.ImageFont
import qrcode
import re
import sys

script_dir = os.path.dirname(os.path.realpath(__file__))

BASE36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'

PAYLOAD_VERSION = 0
PAYLOAD_FLAGS = 2 # 2=IP, 4=BLE, 8=IP_WAC

def gen_homekit_setup_uri(category, password, setup_id, version=0, reserved=0, flags=2):
payload = 0
payload |= (version & 0x7)

payload <<= 4
payload |=(reserved & 0xf) # reserved bits

payload <<= 8
payload |= (category & 0xff)

payload <<= 4
payload |= (flags & 0xf)

payload <<= 27
payload |= (int(password.replace('-', '')) & 0x7fffffff)

encodedPayload = ''
for _ in range(9):
encodedPayload += BASE36[payload % 36]
payload //= 36

return 'X-HM://%s%s' % (''.join(reversed(encodedPayload)), setup_id)


def gen_homekit_qrcode(setup_uri, password):
code = qrcode.QRCode(version=2, border=0, box_size=12,
error_correction=qrcode.constants.ERROR_CORRECT_Q)
code.add_data(setup_uri)

# open template
img = PIL.Image.open(os.path.join(script_dir, 'qrcode.png'))
# add QR code to it
img.paste(code.make_image().get_image(), (50, 180))

# add password digits
setup_code = password.replace('-', '')

font = PIL.ImageFont.truetype(os.path.join(script_dir, 'Arial Bold.ttf'), 56)
draw = PIL.ImageDraw.Draw(img)

for i in range(4):
draw.text((170 + i*50, 35), setup_code[i], font=font, fill=(0, 0, 0))
draw.text((170 + i*50, 95), setup_code[i+4], font=font, fill=(0, 0, 0))

return img


def main():
parser = argparse.ArgumentParser()
parser.add_argument('category', type=int)
parser.add_argument('password')
parser.add_argument('setup_id')
parser.add_argument('output_image')

args = parser.parse_args()

if not re.match('\d{3}-\d{2}-\d{3}', args.password):
raise ValueError('Invalid password')

if not re.match('[0-9A-Z]{4}', args.setup_id):
raise ValueError('Invalid setup ID')

setupURI = gen_homekit_setup_uri(args.category, args.password, args.setup_id)

qrcodeImage = gen_homekit_qrcode(setupURI, args.password)

qrcodeImage.save(args.output_image)


if __name__ == '__main__':
main()
Binary file added tools/qrcode.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

2 comments on commit 19bde63

@cduvenhorst
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think with QR-Code-Pairing the protocol version was bumped from 1.0 to 1.1 and advertised in over mdns. (line 3275 of server.c)

@maximkulkin
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still works that way

Please sign in to comment.