From da9f1dacd8bfe0b99af61bddca646071dd38cb23 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 19 Nov 2020 14:39:02 -0600 Subject: [PATCH 01/56] Initial commit, Zephyr Dockerfile + hello world --- .dockerignore | 1 + .gitignore | 1 + CMakeLists.txt | 7 ++++++ Dockerfile | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ main.c | 17 ++++++++++++++ 6 files changed, 151 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 main.c diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..72e8ffc0d --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +* diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..567609b12 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..3941b18ca --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,7 @@ + +# Zephyr project +cmake_minimum_required(VERSION 3.13.1) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(mini_durango) + +target_sources(app PRIVATE main.c) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..87e90b633 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,63 @@ +FROM ubuntu:18.04 + +ARG TARGET +ARG USER +ARG UID + +ENV DEBIAN_FRONTEND noninteractive + +# Need different version of cmake from kitware +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + gnupg \ + software-properties-common \ + wget && \ + wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | apt-key add - && \ + apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + cmake + +# Zephyr dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ccache \ + device-tree-compiler \ + dfu-util \ + file \ + g++-multilib \ + gcc \ + gcc-multilib \ + git \ + gperf \ + libsdl2-dev \ + make \ + ninja-build \ + python3-dev \ + python3-pip \ + python3-setuptools \ + python3-tk \ + python3-wheel \ + wget \ + xz-utils + +# Install Zephyr +WORKDIR /zephyr-workspace +RUN pip3 install west && \ + west init --mr v2.4.0 && \ + west update && \ + pip3 install -r zephyr/scripts/requirements.txt + +# Get the Zephyr SDK +# +# TODO we don't need all the compilers in this package... can we reduce +# this to just arm-none-eabi-gcc? +RUN wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.11.4/zephyr-sdk-0.11.4-setup.run -O /tmp/zephyr-sdk-0.11.4-setup.run 2>/dev/null && \ + chmod +x /tmp/zephyr-sdk-0.11.4-setup.run && \ + /tmp/zephyr-sdk-0.11.4-setup.run -- -d /opt/zephyr-sdk-0.11.4 + +# Extra config needed for Zephyr's tools +ENV PYTHONIOENCODING "UTF-8" + +WORKDIR /zephyr-workspace/$TARGET diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..91cde458e --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +# Project name/paths, used for docker image +TARGET ?= mini-durango +DOCKER_IMAGE ?= $(TARGET) +DOCKER_CONTAINER ?= $(TARGET) +DOCKER_ROOT ?= $(abspath $(firstword $(MAKEFILE_LIST))/..) + +# QEMU configuration +QEMU ?= /opt/zephyr-sdk-0.11.4/sysroots/x86_64-pokysdk-linux/usr/bin/qemu-system-arm +QEMU_FLAGS += -cpu cortex-m3 +QEMU_FLAGS += -machine lm3s6965evb +QEMU_FLAGS += -nographic +QEMU_FLAGS += -vga none +QEMU_FLAGS += -net none +QEMU_FLAGS += -pidfile qemu.pid +QEMU_FLAGS += -chardev stdio,id=con,mux=on +QEMU_FLAGS += -serial chardev:con +QEMU_FLAGS += -mon chardev=con,mode=readline +QEMU_FLAGS += -icount shift=6,align=off,sleep=off +QEMU_FLAGS += -rtc clock=vm +QEMU_FLAGS += -kernel /zephyr-workspace/$(TARGET)/build/zephyr/zephyr.elf +QEMU_FLAGS += -semihosting + + +# Run in docker +.PHONY: docker +docker: Dockerfile + $(strip docker build \ + --build-arg TARGET=$(TARGET) \ + --build-arg USER=$(USER) \ + --build-arg UID=$(UID) \ + -t $(DOCKER_IMAGE) -f $< .) + $(strip docker run -it --rm \ + -v $(DOCKER_ROOT):/zephyr-workspace/$(TARGET) \ + --name $(DOCKER_CONTAINER) \ + $(DOCKER_IMAGE) bash) + +# Zephyr west commands +.PHONY: update +update: + west update + +.DEFAULT_GOAL := +.PHONY: build +build: + west build -p auto -b qemu_cortex_m3 + +.PHONY: clean +clean: + rm -rf build + +.PHONY: rom_report +rom_report: build + west build -t rom_report + +.PHONY: ram +ram_report: build + west build -t ram_report + +# QEMU +.PHONY: run +run: build + $(QEMU) $(QEMU_FLAGS) diff --git a/main.c b/main.c new file mode 100644 index 000000000..70d985c8b --- /dev/null +++ b/main.c @@ -0,0 +1,17 @@ +/* + * mini-durango - A Veracruz client targetting microcontroller devices + * + */ +#include +#include + +void main(void) { + printk("Hello World! %s\n", CONFIG_BOARD); + + // exit QEMU + __asm__ volatile ( + "mov r0, #0x18 \n\t" + "ldr r1, =#0x20026 \n\t" + "bkpt #0xab \n\t" + ); +} From 4d7c41cf5cd9a94d260469b31d953c7ff20ceff7 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 19 Nov 2020 20:19:46 -0600 Subject: [PATCH 02/56] Added a quick README --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..b73572ff9 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +## Mini-Durango + +Need to add more here... + +## How to build + +At the moment, Mini-Durango runs inside QEMU inside a Docker instance. + +First build/run Docker: + +``` bash +make docker +``` + +Then, build/run the Mini-Durango client: + +``` bash +make build run +``` + +If everything goes well, you should see QEMU run and print hello: + +``` bash +*** Booting Zephyr OS build zephyr-v2.4.0 *** +Hello World! qemu_cortex_m3 +``` From 615ed4f3934d8e281a7627ddb595ba30de3ebe24 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 3 Dec 2020 20:59:24 -0600 Subject: [PATCH 03/56] Pinned cmake to version 3.18.4 --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 87e90b633..0b5dace23 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,8 @@ RUN apt-get update && \ apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main' && \ apt-get update && \ apt-get install -y --no-install-recommends \ - cmake + cmake=3.18.4-0kitware1 \ + cmake-data=3.18.4-0kitware1 # Zephyr dependencies RUN apt-get update && \ From 3ea4f03fadf31bcdfacf17e67234522d2899df0a Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 3 Dec 2020 20:59:41 -0600 Subject: [PATCH 04/56] Managed to get Zephyr's SLIP-based network working in Docker --- CMakeLists.txt | 10 ++++ Dockerfile | 16 +++++- Makefile | 13 ++++- README.md | 4 +- ca_certificate.h | 25 +++++++++ globalsign_r2.der | Bin 0 -> 958 bytes main.c | 131 ++++++++++++++++++++++++++++++++++++++++++++-- prj.conf | 41 +++++++++++++++ slip-setup.sh | 13 +++++ 9 files changed, 243 insertions(+), 10 deletions(-) create mode 100644 ca_certificate.h create mode 100644 globalsign_r2.der create mode 100644 prj.conf create mode 100755 slip-setup.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 3941b18ca..2018e967d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,3 +5,13 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(mini_durango) target_sources(app PRIVATE main.c) + +include(${ZEPHYR_BASE}/samples/net/common/common.cmake) + +set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/) +generate_inc_file_for_target( + app + globalsign_r2.der + ${gen_dir}/globalsign_r2.der.inc + ) + diff --git a/Dockerfile b/Dockerfile index 0b5dace23..8466dc81d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,8 @@ ARG USER ARG UID ENV DEBIAN_FRONTEND noninteractive +# Needed for Zephyr's tools +ENV PYTHONIOENCODING "UTF-8" # Need different version of cmake from kitware RUN apt-get update && \ @@ -58,7 +60,17 @@ RUN wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.11.4/ chmod +x /tmp/zephyr-sdk-0.11.4-setup.run && \ /tmp/zephyr-sdk-0.11.4-setup.run -- -d /opt/zephyr-sdk-0.11.4 -# Extra config needed for Zephyr's tools -ENV PYTHONIOENCODING "UTF-8" +# Setup emulated network connection +# These aren't all needed, but are useful for debugging +WORKDIR /zephyr-workspace/tools/net-tools +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + iproute2 \ + iptables \ + iputils-ping \ + net-tools \ + socat \ + tcpdump +RUN make tunslip6 WORKDIR /zephyr-workspace/$TARGET diff --git a/Makefile b/Makefile index 91cde458e..69799114c 100644 --- a/Makefile +++ b/Makefile @@ -15,8 +15,10 @@ QEMU_FLAGS += -pidfile qemu.pid QEMU_FLAGS += -chardev stdio,id=con,mux=on QEMU_FLAGS += -serial chardev:con QEMU_FLAGS += -mon chardev=con,mode=readline -QEMU_FLAGS += -icount shift=6,align=off,sleep=off +#QEMU_FLAGS += -icount shift=6,align=off,sleep=off QEMU_FLAGS += -rtc clock=vm +#QEMU_FLAGS += -nic tap,model=stellaris,script=no,downscript=no,ifname=zeth +QEMU_FLAGS += -serial unix:/tmp/slip.sock QEMU_FLAGS += -kernel /zephyr-workspace/$(TARGET)/build/zephyr/zephyr.elf QEMU_FLAGS += -semihosting @@ -31,8 +33,9 @@ docker: Dockerfile -t $(DOCKER_IMAGE) -f $< .) $(strip docker run -it --rm \ -v $(DOCKER_ROOT):/zephyr-workspace/$(TARGET) \ + --cap-add=NET_ADMIN --device /dev/net/tun:/dev/net/tun \ --name $(DOCKER_CONTAINER) \ - $(DOCKER_IMAGE) bash) + $(DOCKER_IMAGE) bash -c "./slip-setup.sh ; bash") # Zephyr west commands .PHONY: update @@ -60,3 +63,9 @@ ram_report: build .PHONY: run run: build $(QEMU) $(QEMU_FLAGS) + +# Network tracing +.PHONY: tcpdump +tcpdump: + tcpdump -i tap0 & + sleep 0.1 diff --git a/README.md b/README.md index b73572ff9..290711beb 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ Then, build/run the Mini-Durango client: make build run ``` -If everything goes well, you should see QEMU run and print hello: +If everything goes well, you should see QEMU run and print something: ``` bash *** Booting Zephyr OS build zephyr-v2.4.0 *** -Hello World! qemu_cortex_m3 +blah blah blah ``` diff --git a/ca_certificate.h b/ca_certificate.h new file mode 100644 index 000000000..8ccfe5c05 --- /dev/null +++ b/ca_certificate.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __CA_CERTIFICATE_H__ +#define __CA_CERTIFICATE_H__ + +#define CA_CERTIFICATE_TAG 1 + +/* By default only certificates in DER format are supported. If you want to use + * certificate in PEM format, you can enable support for it in Kconfig. + */ + +/* GlobalSign Root CA - R2 for https://google.com */ +#if defined(CONFIG_TLS_CREDENTIAL_FILENAMES) +static const unsigned char ca_certificate[] = "globalsign_r2.der"; +#else +static const unsigned char ca_certificate[] = { +#include "globalsign_r2.der.inc" +}; +#endif + +#endif /* __CA_CERTIFICATE_H__ */ diff --git a/globalsign_r2.der b/globalsign_r2.der new file mode 100644 index 0000000000000000000000000000000000000000..4d937187ede7ff4298754f14e5399502f844f389 GIT binary patch literal 958 zcmXqLV%}xY#I$GuGZP~d6E_P35HRw$sXgO0;AP{~YV&CO&dbQi%F1BiW2j&t$HpAW z!YwTBo|B)Hm=m0to~IC$pI@Tj?5Lot5M*R1Y#<0y!6nRvPyrKUMiw-X6X!KBGc+zmHFpYDPz}E}G_kLH`6?d!&>ZtZw zcFJ>E+=}HrQG$D_nqKegdAWJbG*$NLUNg1W^|#2C@9*N@%2XpgZO74_RyupG3GI9x zS^MY$TU&gbXVzBBxDJyydn$N1X0+t2IP1M-K`l?E?}r__rxj0K&55pkxInXI;m^xc zJWs16O;p%(m;36?Ge2Lcb7>b(JT_N)&56@59xV8xKcUx3U|cJ ze=wOVaC%qNx%I2BeqY78Dq-)PoALLHRwf9?F)*I=`enc56+5#cQ(x05=0mglnV1xMeab8n24W4^4Mt6mzzAeynAsTIE|W36ZQ`2;UOQa4S2wOv_rG>< zhm3Q@W}aDpUg_B6B})5xYkr;2Dyw#I$IE7hB-d>#IVSI1Y3waA>(Gz(4!_%X3r(aB zhTL+qe{flAb#92#SF5D63i-Px$JdDU|DrA7C*cV9Tr zASvT|K)2_`58WjtVV*}>xB83T%X6u)Y+q5g`*Wql#NA)==N|mfW7-qT`1^3~&i5|4 zm2QT0T>n-!9sK`(b)LeJfRpiaeP@gJ@H}Naxxg{2IQZAnfGv5d{J(u9zWfck*_XNT rg&_aEjvK#aJ-@HIH}$+i#i0bD+o}#dS%2!BEF4mVzRzoXI9U$>x7>1) literal 0 HcmV?d00001 diff --git a/main.c b/main.c index 70d985c8b..a6ec285df 100644 --- a/main.c +++ b/main.c @@ -2,11 +2,134 @@ * mini-durango - A Veracruz client targetting microcontroller devices * */ -#include -#include -void main(void) { - printk("Hello World! %s\n", CONFIG_BOARD); +#include +#include + +#if !defined(__ZEPHYR__) || defined(CONFIG_POSIX_API) + +#include +#include +#include +#include +#include + +#else + +#include +#include + +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) +#include +#include "ca_certificate.h" +#endif + +#endif + +/* HTTP server to connect to */ +#define HTTP_HOST "google.com" +//#define HTTP_HOST "172.217.1.228" +/* Port to connect to, as string */ +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) +#define HTTP_PORT "443" +#else +#define HTTP_PORT "80" +#endif +/* HTTP path to request */ +#define HTTP_PATH "/" + + +#define SSTRLEN(s) (sizeof(s) - 1) +#define CHECK(r) { if (r == -1) { printf("Error: " #r "\n"); exit(1); } } + +#define REQUEST "GET " HTTP_PATH " HTTP/1.0\r\nHost: " HTTP_HOST "\r\n\r\n" + +static char response[1024]; + +void dump_addrinfo(const struct addrinfo *ai) +{ + printf("addrinfo @%p: ai_family=%d, ai_socktype=%d, ai_protocol=%d, " + "sa_family=%d, sin_port=%x\n", + ai, ai->ai_family, ai->ai_socktype, ai->ai_protocol, + ai->ai_addr->sa_family, + ((struct sockaddr_in *)ai->ai_addr)->sin_port); +} + +void main(void) +{ + static struct addrinfo hints; + struct addrinfo *res; + int st, sock; + +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) + tls_credential_add(CA_CERTIFICATE_TAG, TLS_CREDENTIAL_CA_CERTIFICATE, + ca_certificate, sizeof(ca_certificate)); +#endif + + printf("Preparing HTTP GET request for http://" HTTP_HOST + ":" HTTP_PORT HTTP_PATH "\n"); + + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + st = getaddrinfo(HTTP_HOST, HTTP_PORT, &hints, &res); + printf("getaddrinfo status: %d\n", st); + + if (st != 0) { + printf("Unable to resolve address, quitting\n"); + return; + } + +#if 0 + for (; res; res = res->ai_next) { + dump_addrinfo(res); + } +#endif + + dump_addrinfo(res); + +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) + sock = socket(res->ai_family, res->ai_socktype, IPPROTO_TLS_1_2); +#else + sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); +#endif + CHECK(sock); + printf("sock = %d\n", sock); + +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) + sec_tag_t sec_tag_opt[] = { + CA_CERTIFICATE_TAG, + }; + CHECK(setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST, + sec_tag_opt, sizeof(sec_tag_opt))); + + CHECK(setsockopt(sock, SOL_TLS, TLS_HOSTNAME, + HTTP_HOST, sizeof(HTTP_HOST))) +#endif + + CHECK(connect(sock, res->ai_addr, res->ai_addrlen)); + CHECK(send(sock, REQUEST, SSTRLEN(REQUEST), 0)); + + printf("Response:\n\n"); + + while (1) { + int len = recv(sock, response, sizeof(response) - 1, 0); + + if (len < 0) { + printf("Error reading response\n"); + return; + } + + if (len == 0) { + break; + } + + response[len] = 0; + printf("%s", response); + } + + printf("\n"); + + (void)close(sock); // exit QEMU __asm__ volatile ( diff --git a/prj.conf b/prj.conf new file mode 100644 index 000000000..4a75f720f --- /dev/null +++ b/prj.conf @@ -0,0 +1,41 @@ +# General config +CONFIG_NEWLIB_LIBC=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETS_POSIX_NAMES=y + +##CONFIG_NET_L2_ETHERNET=y +##CONFIG_NET_QEMU_ETHERNET=y +##CONFIG_ETH_STELLARIS=y +##CONFIG_NET_SLIP_TAP=n +##CONFIG_SLIP=n + +CONFIG_NET_SLIP_TAP=y +CONFIG_SLIP=y + +CONFIG_DNS_RESOLVER=y +CONFIG_DNS_SERVER_IP_ADDRESSES=y +#CONFIG_DNS_SERVER1="192.0.2.2" +CONFIG_DNS_SERVER1="8.8.8.8" + +# Network driver config +CONFIG_TEST_RANDOM_GENERATOR=y + +# Network address config +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2" +#CONFIG_NET_CONFIG_PEER_IPV4_ADDR="172.217.1.228" # google +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" +#CONFIG_NET_CONFIG_MY_IPV4_ADDR="172.17.2.1" +#CONFIG_NET_CONFIG_PEER_IPV4_ADDR="172.17.2.2" +#CONFIG_NET_CONFIG_MY_IPV4_GW="172.17.2.2" + +# Network debug config +CONFIG_NET_LOG=y diff --git a/slip-setup.sh b/slip-setup.sh new file mode 100755 index 000000000..221e42174 --- /dev/null +++ b/slip-setup.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# setup slip/tap network +nohup /zephyr-workspace/tools/net-tools/loop-socat.sh >/dev/null 2>&1 & +nohup /zephyr-workspace/tools/net-tools/loop-slip-tap.sh >/dev/null 2>&1 & + +# redirect tap traffic +iptables -t nat -A POSTROUTING -j MASQUERADE -s 192.0.2.1 + +# wait for dhcp +while ! ip addr show tap0 2>/dev/null | grep -q inet ; do sleep 0.1 ; done +echo "slip ready @ $(ip addr show tap0 | grep --color=never -oP '(?<=inet\s)[^ ]*')" + From ac6398c436caf2f1c5d715917e3fb7e22b91dfe4 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 18 Feb 2021 10:36:28 -0600 Subject: [PATCH 05/56] Simple (ok broken) HTTP request using Zephyr's HTTP APIs This will allow for other HTTP operations (POST, PUT, etc) --- Makefile | 6 +- http_test.py | 7 + main.c | 355 ++++++++++++++++++++++++++++-------------- policy.c | 367 ++++++++++++++++++++++++++++++++++++++++++++ policy.h | 12 ++ policy_to_header.py | 59 +++++++ prj.conf | 9 +- 7 files changed, 699 insertions(+), 116 deletions(-) create mode 100755 http_test.py create mode 100644 policy.c create mode 100644 policy.h create mode 100755 policy_to_header.py diff --git a/Makefile b/Makefile index 69799114c..8ff7a13cc 100644 --- a/Makefile +++ b/Makefile @@ -42,9 +42,13 @@ docker: Dockerfile update: west update +# TODO move these into west? +policy.h policy.c: policy.json + ./policy_to_header.py $< policy.h policy.c + .DEFAULT_GOAL := .PHONY: build -build: +build: policy.h policy.c west build -p auto -b qemu_cortex_m3 .PHONY: clean diff --git a/http_test.py b/http_test.py new file mode 100755 index 000000000..0a22e160e --- /dev/null +++ b/http_test.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +import http.client + +c = http.client.HTTPConnection('172.17.0.2', 3017, timeout=3) +c.request("GET", "/") +print(c.getresponse()) diff --git a/main.c b/main.c index a6ec285df..919e20516 100644 --- a/main.c +++ b/main.c @@ -6,135 +6,262 @@ #include #include -#if !defined(__ZEPHYR__) || defined(CONFIG_POSIX_API) - -#include -#include -#include -#include -#include - -#else +//#if !defined(__ZEPHYR__) || defined(CONFIG_POSIX_API) +// +//#include +//#include +//#include +//#include +//#include +// +//#else #include +#include #include -#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) -#include -#include "ca_certificate.h" +// TODO log? +//#include +//LOG_MODULE_REGISTER(http, CONFIG_FOO_LOG_LEVEL); + +//#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) +//#include +//#include "ca_certificate.h" +//#endif +// +//#endif + +///* HTTP server to connect to */ +////#define HTTP_HOST "google.com" +////#define HTTP_HOST "172.217.1.228" +//#define HTTP_HOST "172.17.0.2" +///* Port to connect to, as string */ +//#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) +//#define HTTP_PORT "443" +//#else +////#define HTTP_PORT "80" +//#define HTTP_PORT "3017" +//#endif +///* HTTP path to request */ +//#define HTTP_PATH "/" +// +// +//#define SSTRLEN(s) (sizeof(s) - 1) +//#define CHECK(r) { if (r == -1) { printf("Error: " #r "\n"); exit(1); } } +// +//#define REQUEST "GET " HTTP_PATH " HTTP/1.0\r\nHost: " HTTP_HOST "\r\n\r\n" +// +//static char response[1024]; +// +//void dump_addrinfo(const struct addrinfo *ai) +//{ +// printf("addrinfo @%p: ai_family=%d, ai_socktype=%d, ai_protocol=%d, " +// "sa_family=%d, sin_port=%x\n", +// ai, ai->ai_family, ai->ai_socktype, ai->ai_protocol, +// ai->ai_addr->sa_family, +// ((struct sockaddr_in *)ai->ai_addr)->sin_port); +//} + +#ifndef HTTP_SERVER +#define HTTP_SERVER "172.17.0.2" #endif +#ifndef HTTP_PORT +#define HTTP_PORT 3017 #endif -/* HTTP server to connect to */ -#define HTTP_HOST "google.com" -//#define HTTP_HOST "172.217.1.228" -/* Port to connect to, as string */ -#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) -#define HTTP_PORT "443" -#else -#define HTTP_PORT "80" -#endif -/* HTTP path to request */ -#define HTTP_PATH "/" - - -#define SSTRLEN(s) (sizeof(s) - 1) -#define CHECK(r) { if (r == -1) { printf("Error: " #r "\n"); exit(1); } } - -#define REQUEST "GET " HTTP_PATH " HTTP/1.0\r\nHost: " HTTP_HOST "\r\n\r\n" - -static char response[1024]; - -void dump_addrinfo(const struct addrinfo *ai) -{ - printf("addrinfo @%p: ai_family=%d, ai_socktype=%d, ai_protocol=%d, " - "sa_family=%d, sin_port=%x\n", - ai, ai->ai_family, ai->ai_socktype, ai->ai_protocol, - ai->ai_addr->sa_family, - ((struct sockaddr_in *)ai->ai_addr)->sin_port); +static void http_get_cb( + struct http_response *rsp, + enum http_final_call state, + void *udata) { + // TODO probably handle this? + if (state != HTTP_DATA_MORE) { + printf("http rsp state != HTTP_DATA_FINAL (%d), " + "should handle this\n", state); + } + + printf("rsp = %s (%d bytes)\n", rsp->http_status, rsp->data_len); } -void main(void) -{ - static struct addrinfo hints; - struct addrinfo *res; - int st, sock; - -#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) - tls_credential_add(CA_CERTIFICATE_TAG, TLS_CREDENTIAL_CA_CERTIFICATE, - ca_certificate, sizeof(ca_certificate)); -#endif - - printf("Preparing HTTP GET request for http://" HTTP_HOST - ":" HTTP_PORT HTTP_PATH "\n"); - - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - st = getaddrinfo(HTTP_HOST, HTTP_PORT, &hints, &res); - printf("getaddrinfo status: %d\n", st); - - if (st != 0) { - printf("Unable to resolve address, quitting\n"); - return; - } - -#if 0 - for (; res; res = res->ai_next) { - dump_addrinfo(res); - } -#endif - - dump_addrinfo(res); - -#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) - sock = socket(res->ai_family, res->ai_socktype, IPPROTO_TLS_1_2); -#else - sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); -#endif - CHECK(sock); - printf("sock = %d\n", sock); - -#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) - sec_tag_t sec_tag_opt[] = { - CA_CERTIFICATE_TAG, - }; - CHECK(setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST, - sec_tag_opt, sizeof(sec_tag_opt))); - - CHECK(setsockopt(sock, SOL_TLS, TLS_HOSTNAME, - HTTP_HOST, sizeof(HTTP_HOST))) -#endif - - CHECK(connect(sock, res->ai_addr, res->ai_addrlen)); - CHECK(send(sock, REQUEST, SSTRLEN(REQUEST), 0)); - - printf("Response:\n\n"); - - while (1) { - int len = recv(sock, response, sizeof(response) - 1, 0); - - if (len < 0) { - printf("Error reading response\n"); - return; - } - - if (len == 0) { - break; - } - - response[len] = 0; - printf("%s", response); - } - - printf("\n"); +// TODO TLS? https_get? +int http_get( + struct sockaddr *server_addr, + socklen_t server_addr_len, + // TODO dedup this? + const char *host, + const char *path, + // TODO headers? + uint8_t *buf, + size_t buf_size, + int32_t timeout) { + // create socket and connect to server + // TODO IPv6? can use net_sin(addr)->sin_family here + // DNS? Take in a string more useful API? + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + printf("http socket open failed (%d)\n", -errno); + return -errno; + } + + int res = connect(sock, server_addr, server_addr_len); + if (res < 0) { + printf("http connect failed (%d)\n", -errno); + return -errno; + } + + // perform client request, Zephyr handles most of this for us + struct http_request req; + memset(&req, 0, sizeof(req)); + req.method = HTTP_GET; + req.url = path; + req.host = host; + req.protocol = "HTTP/1.1"; + req.response = http_get_cb; + req.recv_buf = buf; + req.recv_buf_len = buf_size; + + int err = http_client_req(sock, &req, timeout, NULL); + if (err < 0) { + printf("http req failed (%d)\n", err); + return err; + } + + // done, close + close(sock); + return 0; +} - (void)close(sock); +uint8_t buffer[1024]; - // exit QEMU +// hack to exit QEMU +//__attribute__((noreturn)) +void qemu_exit(void) { __asm__ volatile ( "mov r0, #0x18 \n\t" "ldr r1, =#0x20026 \n\t" "bkpt #0xab \n\t" ); } + +void main(void) +{ + // setup address, TODO use DNS? + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(HTTP_PORT); + inet_pton(AF_INET, HTTP_SERVER, &addr.sin_addr); + + // perform GET + printf("connecting to %s:%d...\n", HTTP_SERVER, HTTP_PORT); + int err = http_get( + (struct sockaddr*)&addr, + sizeof(addr), + HTTP_SERVER, + "/", + buffer, + sizeof(buffer), + 3000); + if (err) { + printf("http_get failed (%d)\n", err); + qemu_exit(); + } + + printf("get worked?"); + printf("%s", buffer); + printf("\n"); +// +// while (1) { +// int len = recv(sock, response, sizeof(response) - 1, 0); +// +// if (len < 0) { +// printf("Error reading response\n"); +// return; +// } +// +// if (len == 0) { +// break; +// } +// +// response[len] = 0; +// printf("%s", response); +// } +// +// printf("\n"); +// +// +// static struct addrinfo hints; +// struct addrinfo *res; +// int st, sock; +// +//#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) +// tls_credential_add(CA_CERTIFICATE_TAG, TLS_CREDENTIAL_CA_CERTIFICATE, +// ca_certificate, sizeof(ca_certificate)); +//#endif +// +// printf("Preparing HTTP GET request for http://" HTTP_HOST +// ":" HTTP_PORT HTTP_PATH "\n"); +// +// hints.ai_family = AF_INET; +// hints.ai_socktype = SOCK_STREAM; +// st = getaddrinfo(HTTP_HOST, HTTP_PORT, &hints, &res); +// printf("getaddrinfo status: %d\n", st); +// +// if (st != 0) { +// printf("Unable to resolve address, quitting\n"); +// return; +// } +// +//#if 0 +// for (; res; res = res->ai_next) { +// dump_addrinfo(res); +// } +//#endif +// +// dump_addrinfo(res); +// +//#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) +// sock = socket(res->ai_family, res->ai_socktype, IPPROTO_TLS_1_2); +//#else +// sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); +//#endif +// CHECK(sock); +// printf("sock = %d\n", sock); +// +//#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) +// sec_tag_t sec_tag_opt[] = { +// CA_CERTIFICATE_TAG, +// }; +// CHECK(setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST, +// sec_tag_opt, sizeof(sec_tag_opt))); +// +// CHECK(setsockopt(sock, SOL_TLS, TLS_HOSTNAME, +// HTTP_HOST, sizeof(HTTP_HOST))) +//#endif +// +// CHECK(connect(sock, res->ai_addr, res->ai_addrlen)); +// CHECK(send(sock, REQUEST, SSTRLEN(REQUEST), 0)); +// +// printf("Response:\n\n"); +// +// while (1) { +// int len = recv(sock, response, sizeof(response) - 1, 0); +// +// if (len < 0) { +// printf("Error reading response\n"); +// return; +// } +// +// if (len == 0) { +// break; +// } +// +// response[len] = 0; +// printf("%s", response); +// } +// +// printf("\n"); +// +// (void)close(sock); + qemu_exit(); +} diff --git a/policy.c b/policy.c new file mode 100644 index 000000000..5d36d8efc --- /dev/null +++ b/policy.c @@ -0,0 +1,367 @@ +//// AUTOGENERATED //// + +uint8_t _veracruz_policy_raw[] = { + 0x7b, 0x0a, 0x20, 0x20, 0x22, 0x63, 0x69, 0x70, + 0x68, 0x65, 0x72, 0x73, 0x75, 0x69, 0x74, 0x65, + 0x22, 0x3a, 0x20, 0x22, 0x54, 0x4c, 0x53, 0x5f, + 0x45, 0x43, 0x44, 0x48, 0x45, 0x5f, 0x45, 0x43, + 0x44, 0x53, 0x41, 0x5f, 0x57, 0x49, 0x54, 0x48, + 0x5f, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41, 0x32, + 0x30, 0x5f, 0x50, 0x4f, 0x4c, 0x59, 0x31, 0x33, + 0x30, 0x35, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x35, + 0x36, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x64, + 0x65, 0x62, 0x75, 0x67, 0x22, 0x3a, 0x20, 0x66, + 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x20, + 0x22, 0x65, 0x6e, 0x63, 0x6c, 0x61, 0x76, 0x65, + 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x22, 0x3a, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, + 0x79, 0x22, 0x3a, 0x20, 0x39, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x68, 0x6f, 0x75, 0x72, + 0x22, 0x3a, 0x20, 0x31, 0x36, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x75, + 0x74, 0x65, 0x22, 0x3a, 0x20, 0x31, 0x37, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x6f, + 0x6e, 0x74, 0x68, 0x22, 0x3a, 0x20, 0x37, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x79, 0x65, + 0x61, 0x72, 0x22, 0x3a, 0x20, 0x32, 0x30, 0x32, + 0x31, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, + 0x20, 0x22, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x65, 0x67, 0x79, 0x22, 0x3a, 0x20, 0x22, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x22, 0x69, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3a, + 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, + 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, + 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x5c, 0x6e, 0x4d, 0x49, 0x49, 0x44, + 0x77, 0x7a, 0x43, 0x43, 0x41, 0x71, 0x75, 0x67, + 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x55, + 0x4f, 0x6e, 0x6e, 0x41, 0x4b, 0x42, 0x4f, 0x2b, + 0x4f, 0x52, 0x72, 0x68, 0x76, 0x5a, 0x64, 0x54, + 0x65, 0x56, 0x47, 0x71, 0x77, 0x54, 0x55, 0x58, + 0x6f, 0x4e, 0x30, 0x77, 0x44, 0x51, 0x59, 0x4a, + 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, + 0x41, 0x51, 0x45, 0x4c, 0x42, 0x51, 0x41, 0x77, + 0x67, 0x59, 0x49, 0x78, 0x43, 0x7a, 0x41, 0x4a, + 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, + 0x41, 0x6c, 0x56, 0x54, 0x4d, 0x51, 0x34, 0x77, + 0x44, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x49, + 0x44, 0x41, 0x56, 0x55, 0x5a, 0x58, 0x68, 0x68, + 0x63, 0x7a, 0x45, 0x50, 0x4d, 0x41, 0x30, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x42, 0x77, 0x77, 0x47, + 0x51, 0x58, 0x56, 0x7a, 0x64, 0x47, 0x6c, 0x75, + 0x4d, 0x52, 0x59, 0x77, 0x46, 0x41, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x4b, 0x44, 0x41, 0x31, 0x61, + 0x61, 0x57, 0x4a, 0x69, 0x62, 0x47, 0x55, 0x67, + 0x57, 0x6d, 0x46, 0x69, 0x59, 0x6d, 0x78, 0x6c, + 0x4d, 0x53, 0x4d, 0x77, 0x49, 0x51, 0x59, 0x4a, + 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, + 0x41, 0x51, 0x6b, 0x42, 0x46, 0x68, 0x52, 0x36, + 0x61, 0x57, 0x4a, 0x69, 0x62, 0x47, 0x56, 0x41, + 0x65, 0x6d, 0x46, 0x69, 0x59, 0x6d, 0x78, 0x6c, + 0x4c, 0x6e, 0x70, 0x70, 0x59, 0x6d, 0x4a, 0x73, + 0x5a, 0x54, 0x45, 0x56, 0x4d, 0x42, 0x4d, 0x47, + 0x41, 0x31, 0x55, 0x45, 0x41, 0x77, 0x77, 0x4d, + 0x65, 0x6d, 0x6c, 0x69, 0x59, 0x6d, 0x78, 0x6c, + 0x65, 0x6d, 0x46, 0x69, 0x59, 0x6d, 0x78, 0x6c, + 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x49, 0x77, + 0x4d, 0x44, 0x6b, 0x78, 0x4e, 0x6a, 0x45, 0x35, + 0x4e, 0x44, 0x6b, 0x7a, 0x4d, 0x56, 0x6f, 0x58, + 0x44, 0x54, 0x4d, 0x77, 0x4d, 0x44, 0x6b, 0x78, + 0x4e, 0x44, 0x45, 0x35, 0x4e, 0x44, 0x6b, 0x7a, + 0x4d, 0x56, 0x6f, 0x77, 0x67, 0x59, 0x49, 0x78, + 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, + 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, 0x56, 0x54, + 0x4d, 0x51, 0x34, 0x77, 0x44, 0x41, 0x59, 0x44, + 0x56, 0x51, 0x51, 0x49, 0x44, 0x41, 0x56, 0x55, + 0x5a, 0x58, 0x68, 0x68, 0x63, 0x7a, 0x45, 0x50, + 0x4d, 0x41, 0x30, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x42, 0x77, 0x77, 0x47, 0x51, 0x58, 0x56, 0x7a, + 0x64, 0x47, 0x6c, 0x75, 0x4d, 0x52, 0x59, 0x77, + 0x46, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, + 0x44, 0x41, 0x31, 0x61, 0x61, 0x57, 0x4a, 0x69, + 0x62, 0x47, 0x55, 0x67, 0x57, 0x6d, 0x46, 0x69, + 0x59, 0x6d, 0x78, 0x6c, 0x4d, 0x53, 0x4d, 0x77, + 0x49, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, + 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x6b, 0x42, + 0x46, 0x68, 0x52, 0x36, 0x61, 0x57, 0x4a, 0x69, + 0x62, 0x47, 0x56, 0x41, 0x65, 0x6d, 0x46, 0x69, + 0x59, 0x6d, 0x78, 0x6c, 0x4c, 0x6e, 0x70, 0x70, + 0x59, 0x6d, 0x4a, 0x73, 0x5a, 0x54, 0x45, 0x56, + 0x4d, 0x42, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x41, 0x77, 0x77, 0x4d, 0x65, 0x6d, 0x6c, 0x69, + 0x59, 0x6d, 0x78, 0x6c, 0x65, 0x6d, 0x46, 0x69, + 0x59, 0x6d, 0x78, 0x6c, 0x4d, 0x49, 0x49, 0x42, + 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, + 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, + 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, + 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, + 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, + 0x73, 0x72, 0x4c, 0x52, 0x59, 0x4f, 0x6c, 0x59, + 0x4d, 0x52, 0x41, 0x44, 0x6d, 0x4d, 0x2f, 0x73, + 0x6c, 0x33, 0x72, 0x59, 0x62, 0x4b, 0x71, 0x6d, + 0x2f, 0x68, 0x6c, 0x2f, 0x4b, 0x31, 0x78, 0x47, + 0x52, 0x51, 0x2f, 0x73, 0x42, 0x4d, 0x44, 0x66, + 0x61, 0x33, 0x74, 0x6c, 0x2b, 0x6a, 0x53, 0x4b, + 0x36, 0x38, 0x4b, 0x78, 0x64, 0x4f, 0x71, 0x58, + 0x71, 0x46, 0x56, 0x58, 0x77, 0x59, 0x46, 0x4e, + 0x68, 0x38, 0x6b, 0x2b, 0x37, 0x57, 0x77, 0x43, + 0x43, 0x73, 0x69, 0x63, 0x2f, 0x33, 0x67, 0x64, + 0x54, 0x7a, 0x76, 0x74, 0x43, 0x4e, 0x44, 0x6a, + 0x6f, 0x42, 0x35, 0x70, 0x38, 0x67, 0x61, 0x58, + 0x78, 0x6d, 0x71, 0x59, 0x42, 0x72, 0x78, 0x6c, + 0x6c, 0x52, 0x75, 0x55, 0x57, 0x52, 0x49, 0x41, + 0x75, 0x41, 0x48, 0x56, 0x70, 0x64, 0x79, 0x55, + 0x6e, 0x75, 0x4c, 0x41, 0x7a, 0x77, 0x5a, 0x37, + 0x56, 0x54, 0x36, 0x62, 0x55, 0x63, 0x7a, 0x62, + 0x55, 0x69, 0x30, 0x39, 0x71, 0x6d, 0x6e, 0x6a, + 0x42, 0x33, 0x75, 0x34, 0x7a, 0x4f, 0x67, 0x2b, + 0x39, 0x54, 0x66, 0x76, 0x30, 0x77, 0x67, 0x78, + 0x6d, 0x79, 0x67, 0x34, 0x48, 0x53, 0x55, 0x4e, + 0x72, 0x52, 0x49, 0x55, 0x67, 0x57, 0x57, 0x65, + 0x39, 0x44, 0x4a, 0x4b, 0x49, 0x4f, 0x4a, 0x70, + 0x68, 0x41, 0x31, 0x51, 0x79, 0x65, 0x63, 0x47, + 0x46, 0x70, 0x54, 0x49, 0x62, 0x63, 0x41, 0x46, + 0x75, 0x42, 0x57, 0x79, 0x6a, 0x50, 0x31, 0x43, + 0x75, 0x7a, 0x36, 0x54, 0x4f, 0x41, 0x6f, 0x66, + 0x37, 0x34, 0x4f, 0x30, 0x4b, 0x4f, 0x6b, 0x70, + 0x45, 0x67, 0x42, 0x37, 0x74, 0x4d, 0x66, 0x4a, + 0x75, 0x78, 0x43, 0x63, 0x45, 0x47, 0x75, 0x52, + 0x53, 0x62, 0x5a, 0x35, 0x59, 0x2b, 0x6d, 0x2b, + 0x67, 0x6a, 0x73, 0x6f, 0x79, 0x2f, 0x55, 0x59, + 0x49, 0x6e, 0x70, 0x4c, 0x63, 0x49, 0x52, 0x75, + 0x50, 0x32, 0x2b, 0x63, 0x47, 0x36, 0x62, 0x46, + 0x77, 0x55, 0x47, 0x6c, 0x66, 0x4b, 0x59, 0x2b, + 0x61, 0x2b, 0x71, 0x7a, 0x75, 0x36, 0x56, 0x69, + 0x4b, 0x76, 0x46, 0x2b, 0x31, 0x52, 0x36, 0x59, + 0x77, 0x48, 0x56, 0x4f, 0x52, 0x46, 0x6c, 0x30, + 0x30, 0x31, 0x71, 0x69, 0x70, 0x63, 0x50, 0x70, + 0x79, 0x70, 0x35, 0x65, 0x69, 0x32, 0x2b, 0x41, + 0x55, 0x43, 0x46, 0x67, 0x4d, 0x69, 0x42, 0x46, + 0x70, 0x69, 0x48, 0x6e, 0x6b, 0x73, 0x43, 0x46, + 0x48, 0x61, 0x57, 0x47, 0x44, 0x51, 0x49, 0x44, + 0x41, 0x51, 0x41, 0x42, 0x6f, 0x79, 0x38, 0x77, + 0x4c, 0x54, 0x41, 0x72, 0x42, 0x67, 0x4e, 0x56, + 0x48, 0x52, 0x45, 0x45, 0x4a, 0x44, 0x41, 0x69, + 0x67, 0x67, 0x31, 0x36, 0x59, 0x57, 0x4a, 0x69, + 0x62, 0x47, 0x55, 0x75, 0x65, 0x6d, 0x6c, 0x69, + 0x59, 0x6d, 0x78, 0x6c, 0x67, 0x68, 0x46, 0x33, + 0x64, 0x33, 0x63, 0x75, 0x65, 0x6d, 0x46, 0x69, + 0x59, 0x6d, 0x78, 0x6c, 0x4c, 0x6e, 0x70, 0x70, + 0x59, 0x6d, 0x4a, 0x73, 0x5a, 0x54, 0x41, 0x4e, + 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x73, 0x46, + 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, + 0x65, 0x52, 0x55, 0x33, 0x6b, 0x4b, 0x79, 0x48, + 0x75, 0x33, 0x39, 0x4f, 0x36, 0x4e, 0x47, 0x63, + 0x52, 0x74, 0x72, 0x63, 0x66, 0x35, 0x71, 0x4f, + 0x49, 0x51, 0x66, 0x67, 0x4b, 0x6c, 0x53, 0x52, + 0x65, 0x69, 0x56, 0x63, 0x45, 0x76, 0x76, 0x32, + 0x45, 0x59, 0x4c, 0x73, 0x46, 0x57, 0x2f, 0x51, + 0x75, 0x71, 0x70, 0x51, 0x45, 0x37, 0x48, 0x64, + 0x73, 0x61, 0x48, 0x78, 0x39, 0x4e, 0x6d, 0x49, + 0x4a, 0x6d, 0x64, 0x42, 0x34, 0x48, 0x54, 0x6a, + 0x6a, 0x53, 0x49, 0x37, 0x32, 0x52, 0x67, 0x49, + 0x4f, 0x59, 0x73, 0x64, 0x64, 0x41, 0x6a, 0x65, + 0x75, 0x78, 0x75, 0x56, 0x79, 0x43, 0x75, 0x72, + 0x51, 0x4d, 0x53, 0x54, 0x56, 0x67, 0x51, 0x53, + 0x69, 0x49, 0x57, 0x79, 0x35, 0x32, 0x79, 0x66, + 0x2f, 0x4a, 0x33, 0x2f, 0x2f, 0x33, 0x66, 0x37, + 0x6b, 0x53, 0x34, 0x4d, 0x56, 0x69, 0x69, 0x4a, + 0x74, 0x50, 0x35, 0x62, 0x57, 0x64, 0x4b, 0x32, + 0x79, 0x64, 0x54, 0x68, 0x74, 0x6a, 0x4b, 0x31, + 0x2f, 0x62, 0x79, 0x58, 0x7a, 0x77, 0x34, 0x77, + 0x70, 0x61, 0x59, 0x64, 0x58, 0x2b, 0x7a, 0x7a, + 0x5a, 0x66, 0x55, 0x70, 0x4c, 0x6c, 0x4b, 0x33, + 0x66, 0x38, 0x44, 0x47, 0x61, 0x73, 0x39, 0x67, + 0x76, 0x2b, 0x48, 0x5a, 0x38, 0x33, 0x66, 0x63, + 0x5a, 0x48, 0x38, 0x72, 0x52, 0x69, 0x61, 0x72, + 0x70, 0x4f, 0x56, 0x66, 0x68, 0x55, 0x30, 0x75, + 0x41, 0x4e, 0x79, 0x44, 0x35, 0x36, 0x72, 0x50, + 0x4c, 0x6c, 0x67, 0x30, 0x4c, 0x47, 0x36, 0x35, + 0x76, 0x4c, 0x58, 0x46, 0x31, 0x5a, 0x58, 0x2b, + 0x68, 0x75, 0x45, 0x79, 0x77, 0x54, 0x75, 0x63, + 0x36, 0x6f, 0x68, 0x32, 0x2f, 0x4a, 0x58, 0x51, + 0x70, 0x47, 0x72, 0x44, 0x76, 0x74, 0x74, 0x70, + 0x62, 0x69, 0x42, 0x4e, 0x6f, 0x33, 0x44, 0x35, + 0x71, 0x51, 0x61, 0x46, 0x49, 0x34, 0x4a, 0x71, + 0x66, 0x33, 0x79, 0x58, 0x6a, 0x4b, 0x76, 0x62, + 0x51, 0x76, 0x73, 0x37, 0x45, 0x4b, 0x72, 0x51, + 0x6a, 0x2b, 0x47, 0x49, 0x50, 0x32, 0x4b, 0x6c, + 0x7a, 0x73, 0x76, 0x2b, 0x62, 0x74, 0x45, 0x46, + 0x37, 0x55, 0x46, 0x63, 0x61, 0x53, 0x6d, 0x73, + 0x31, 0x2f, 0x43, 0x33, 0x52, 0x67, 0x35, 0x41, + 0x4c, 0x37, 0x77, 0x78, 0x68, 0x39, 0x52, 0x39, + 0x7a, 0x4e, 0x58, 0x73, 0x45, 0x72, 0x46, 0x70, + 0x65, 0x43, 0x4d, 0x2f, 0x6e, 0x62, 0x47, 0x6d, + 0x35, 0x53, 0x6c, 0x75, 0x58, 0x41, 0x3d, 0x3d, + 0x5c, 0x6e, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, + 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, + 0x65, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, + 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x22, 0x3a, + 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, + 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x30, 0x22, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x61, + 0x64, 0x22, 0x3a, 0x20, 0x66, 0x61, 0x6c, 0x73, + 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x72, + 0x69, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x74, 0x72, + 0x75, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x22, 0x3a, 0x20, 0x66, 0x61, 0x6c, + 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, + 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x22, 0x3a, 0x20, 0x22, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x72, 0x65, 0x61, 0x64, 0x22, 0x3a, 0x20, 0x74, + 0x72, 0x75, 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x3a, 0x20, + 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x65, 0x22, 0x3a, 0x20, + 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6c, 0x69, + 0x6e, 0x65, 0x61, 0x72, 0x2d, 0x72, 0x65, 0x67, + 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, + 0x77, 0x61, 0x73, 0x6d, 0x22, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x72, 0x65, 0x61, 0x64, 0x22, 0x3a, + 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x77, 0x72, 0x69, 0x74, 0x65, + 0x22, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x69, 0x64, 0x22, 0x3a, 0x20, 0x30, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, + 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x6d, + 0x65, 0x78, 0x69, 0x63, 0x6f, 0x5f, 0x63, 0x69, + 0x74, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, + 0x6e, 0x69, 0x74, 0x72, 0x6f, 0x22, 0x3a, 0x20, + 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x0a, 0x20, 0x20, + 0x22, 0x6d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x5f, + 0x63, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x61, 0x73, + 0x68, 0x5f, 0x73, 0x67, 0x78, 0x22, 0x3a, 0x20, + 0x22, 0x34, 0x66, 0x66, 0x32, 0x62, 0x31, 0x61, + 0x30, 0x33, 0x36, 0x66, 0x65, 0x66, 0x62, 0x65, + 0x35, 0x31, 0x66, 0x65, 0x65, 0x37, 0x61, 0x39, + 0x31, 0x65, 0x39, 0x66, 0x30, 0x34, 0x61, 0x35, + 0x39, 0x66, 0x62, 0x32, 0x32, 0x64, 0x62, 0x31, + 0x33, 0x33, 0x38, 0x35, 0x38, 0x33, 0x61, 0x35, + 0x36, 0x64, 0x38, 0x34, 0x62, 0x30, 0x37, 0x36, + 0x64, 0x64, 0x33, 0x32, 0x65, 0x37, 0x37, 0x39, + 0x32, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x6d, + 0x65, 0x78, 0x69, 0x63, 0x6f, 0x5f, 0x63, 0x69, + 0x74, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, + 0x74, 0x7a, 0x22, 0x3a, 0x20, 0x22, 0x34, 0x66, + 0x66, 0x32, 0x62, 0x31, 0x61, 0x30, 0x33, 0x36, + 0x66, 0x65, 0x66, 0x62, 0x65, 0x35, 0x31, 0x66, + 0x65, 0x65, 0x37, 0x61, 0x39, 0x31, 0x65, 0x39, + 0x66, 0x30, 0x34, 0x61, 0x35, 0x39, 0x66, 0x62, + 0x32, 0x32, 0x64, 0x62, 0x31, 0x33, 0x33, 0x38, + 0x35, 0x38, 0x33, 0x61, 0x35, 0x36, 0x64, 0x38, + 0x34, 0x62, 0x30, 0x37, 0x36, 0x64, 0x64, 0x33, + 0x32, 0x65, 0x37, 0x37, 0x39, 0x32, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x73, 0x22, 0x3a, 0x20, 0x5b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, + 0x6c, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, + 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x22, + 0x3a, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x20, + 0x22, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x30, + 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, + 0x61, 0x64, 0x22, 0x3a, 0x20, 0x74, 0x72, 0x75, + 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x72, + 0x69, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x66, 0x61, + 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x66, 0x61, + 0x6c, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x72, 0x65, 0x61, 0x64, 0x22, 0x3a, 0x20, + 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, + 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x69, 0x64, 0x22, 0x3a, 0x20, 0x30, 0x2c, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x70, 0x69, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, + 0x3a, 0x20, 0x22, 0x63, 0x31, 0x30, 0x38, 0x34, + 0x30, 0x37, 0x66, 0x64, 0x62, 0x36, 0x66, 0x66, + 0x64, 0x35, 0x35, 0x38, 0x33, 0x64, 0x65, 0x30, + 0x37, 0x65, 0x33, 0x65, 0x62, 0x35, 0x39, 0x34, + 0x39, 0x35, 0x63, 0x36, 0x30, 0x31, 0x66, 0x66, + 0x66, 0x30, 0x65, 0x34, 0x35, 0x62, 0x39, 0x33, + 0x62, 0x38, 0x34, 0x30, 0x61, 0x32, 0x32, 0x31, + 0x33, 0x61, 0x36, 0x34, 0x34, 0x38, 0x65, 0x31, + 0x62, 0x33, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6f, + 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x66, 0x69, 0x6c, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, + 0x20, 0x22, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, + 0x2d, 0x72, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x2e, 0x77, 0x61, 0x73, 0x6d, + 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, + 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x22, + 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x61, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x75, 0x72, 0x6c, 0x22, 0x3a, 0x20, 0x22, + 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, + 0x31, 0x3a, 0x33, 0x30, 0x31, 0x30, 0x22, 0x2c, + 0x0a, 0x20, 0x20, 0x22, 0x73, 0x69, 0x6e, 0x61, + 0x6c, 0x6f, 0x61, 0x5f, 0x75, 0x72, 0x6c, 0x22, + 0x3a, 0x20, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, + 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x33, 0x30, 0x31, + 0x32, 0x22, 0x0a, 0x7d, +}; diff --git a/policy.h b/policy.h new file mode 100644 index 000000000..27b662c8c --- /dev/null +++ b/policy.h @@ -0,0 +1,12 @@ +//// AUTOGENERATED //// +#ifndef VERACRUZ_POLICY_H +#define VERACRUZ_POLICY_H + +#include + +extern uint8_t _veracruz_policy_raw[]; + +#define VERACRUZ_POLICY_HASH "2713d3d5a1b4570049f971cc8ca53f95cd4010710ba09f6622baefca127281ff" +#define VERACRUZ_POLICY_RAW _veracruz_policy_raw + +#endif diff --git a/policy_to_header.py b/policy_to_header.py new file mode 100755 index 000000000..39458ef4d --- /dev/null +++ b/policy_to_header.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +import json +import hashlib + +def main(in_json, out_h, out_c): + with open(in_json) as f: + policy_raw = f.read() + policy_hash = hashlib.sha256(policy_raw.encode('utf-8')).hexdigest() + policy = json.loads(policy_raw) + + print('Generating %s for policy %s' % (out_h, policy_hash)) + with open(out_h, 'w') as f: + _write = f.write + def write(s='', **args): + _write(s % args) + def writeln(s='', **args): + _write(s % args) + _write('\n') + f.write = write + f.writeln = writeln + + f.writeln('//// AUTOGENERATED ////') + f.writeln('#ifndef VERACRUZ_POLICY_H') + f.writeln('#define VERACRUZ_POLICY_H') + f.writeln() + f.writeln('#include ') + f.writeln() + f.writeln('extern uint8_t _veracruz_policy_raw[];') + f.writeln() + f.writeln('#define VERACRUZ_POLICY_HASH "%(hash)s"', + hash=policy_hash) + f.writeln('#define VERACRUZ_POLICY_RAW _veracruz_policy_raw') + f.writeln() + f.writeln('#endif') + + print('Generating %s for policy %s' % (out_c, policy_hash)) + with open(out_c, 'w') as f: + _write = f.write + def write(s='', **args): + _write(s % args) + def writeln(s='', **args): + _write(s % args) + _write('\n') + f.write = write + f.writeln = writeln + + f.writeln('//// AUTOGENERATED ////') + f.writeln() + f.writeln('uint8_t _veracruz_policy_raw[] = {') + for i in range(0, len(policy_raw), 8): + f.writeln(' %(raw)s', + raw=' '.join('0x%02x,' % ord(x) + for x in policy_raw[i : min(i+8, len(policy_raw))])) + f.writeln('};') + +if __name__ == "__main__": + import sys + main(*sys.argv[1:]) diff --git a/prj.conf b/prj.conf index 4a75f720f..25f11c5ff 100644 --- a/prj.conf +++ b/prj.conf @@ -1,5 +1,6 @@ # General config CONFIG_NEWLIB_LIBC=y +CONFIG_MAIN_STACK_SIZE=4096 # Networking config CONFIG_NETWORKING=y @@ -23,6 +24,8 @@ CONFIG_DNS_SERVER_IP_ADDRESSES=y #CONFIG_DNS_SERVER1="192.0.2.2" CONFIG_DNS_SERVER1="8.8.8.8" +CONFIG_HTTP_CLIENT=y + # Network driver config CONFIG_TEST_RANDOM_GENERATOR=y @@ -37,5 +40,9 @@ CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" #CONFIG_NET_CONFIG_PEER_IPV4_ADDR="172.17.2.2" #CONFIG_NET_CONFIG_MY_IPV4_GW="172.17.2.2" -# Network debug config +# Logging config +CONFIG_LOG=y +CONFIG_LOG_IMMEDIATE=y CONFIG_NET_LOG=y +CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=y +CONFIG_NET_HTTP_LOG_LEVEL_DBG=y From 8208300901e9b22068d8aa8a01bfa1223c550e25 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 10 Mar 2021 11:51:24 -0600 Subject: [PATCH 06/56] Properly fleshed out HTTP GET, mostly handling some Zephyr oddities --- main.c | 89 ++++++++++++++++++++++++++++++++++++++++++++------------ prj.conf | 2 +- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/main.c b/main.c index 919e20516..2e773eff7 100644 --- a/main.c +++ b/main.c @@ -18,6 +18,7 @@ #include #include +#include #include // TODO log? @@ -70,21 +71,41 @@ #define HTTP_PORT 3017 #endif +struct http_get_state { + uint8_t *buf; + size_t buf_len; + size_t pos; +}; + static void http_get_cb( struct http_response *rsp, - enum http_final_call state, + enum http_final_call final, void *udata) { - // TODO probably handle this? - if (state != HTTP_DATA_MORE) { - printf("http rsp state != HTTP_DATA_FINAL (%d), " - "should handle this\n", state); - } +// // TODO probably handle this? +// if (state != HTTP_DATA_MORE) { +// printf("http rsp state != HTTP_DATA_FINAL (%d), " +// "should handle this\n", state); +// } printf("rsp = %s (%d bytes)\n", rsp->http_status, rsp->data_len); + + struct http_get_state *state = udata; + uint8_t *start = (rsp->body_start) ? rsp->body_start : rsp->recv_buf; + size_t len = rsp->data_len; + + if (state->pos + len > state->buf_len) { + printf("http get buffer overflow! truncating (%d > %d)\n", + state->pos + len, state->buf_len); + + len = state->buf_len - state->pos; + } + + memcpy(&state->buf[state->pos], start, len); + state->pos += len; } // TODO TLS? https_get? -int http_get( +ssize_t http_get( struct sockaddr *server_addr, socklen_t server_addr_len, // TODO dedup this? @@ -92,23 +113,41 @@ int http_get( const char *path, // TODO headers? uint8_t *buf, - size_t buf_size, + size_t buf_len, int32_t timeout) { + // allocate packet buffer for managing http connection + void *pbuf = malloc(256); + size_t pbuf_len = 256; + if (!pbuf) { + printf("http malloc failed (-ENOMEM)\n"); + return -ENOMEM; + } + // create socket and connect to server // TODO IPv6? can use net_sin(addr)->sin_family here // DNS? Take in a string more useful API? int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock < 0) { printf("http socket open failed (%d)\n", -errno); + free(pbuf); return -errno; } int res = connect(sock, server_addr, server_addr_len); if (res < 0) { printf("http connect failed (%d)\n", -errno); + free(pbuf); + close(sock); return -errno; } + // state for filling in buffer + struct http_get_state state = { + .buf = buf, + .buf_len = buf_len, + .pos = 0, + }; + // perform client request, Zephyr handles most of this for us struct http_request req; memset(&req, 0, sizeof(req)); @@ -117,21 +156,24 @@ int http_get( req.host = host; req.protocol = "HTTP/1.1"; req.response = http_get_cb; - req.recv_buf = buf; - req.recv_buf_len = buf_size; + req.recv_buf = pbuf; + req.recv_buf_len = pbuf_len; - int err = http_client_req(sock, &req, timeout, NULL); + int err = http_client_req(sock, &req, timeout, &state); if (err < 0) { printf("http req failed (%d)\n", err); + free(pbuf); + close(sock); return err; } // done, close + free(pbuf); close(sock); - return 0; + return state.pos; } -uint8_t buffer[1024]; +uint8_t buffer[10*1024]; // hack to exit QEMU //__attribute__((noreturn)) @@ -145,6 +187,12 @@ void qemu_exit(void) { void main(void) { + // get random challenge + // TODO Zephyr notes this is not cryptographically secure, is that an + // issue? This will be an area to explore + uint8_t challenge[32]; + sys_rand_get(challenge, sizeof(challenge)); + // setup address, TODO use DNS? struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); @@ -154,7 +202,7 @@ void main(void) // perform GET printf("connecting to %s:%d...\n", HTTP_SERVER, HTTP_PORT); - int err = http_get( + ssize_t res = http_get( (struct sockaddr*)&addr, sizeof(addr), HTTP_SERVER, @@ -162,14 +210,19 @@ void main(void) buffer, sizeof(buffer), 3000); - if (err) { - printf("http_get failed (%d)\n", err); + if (res < 0) { + printf("http_get failed (%d)\n", res); qemu_exit(); } - printf("get worked?"); - printf("%s", buffer); + printf("http get -> %d\n", res); + for (int i = 0; i < res; i++) { + if (buffer[i] != '\r') { + printf("%c", buffer[i]); + } + } printf("\n"); + // // while (1) { // int len = recv(sock, response, sizeof(response) - 1, 0); diff --git a/prj.conf b/prj.conf index 25f11c5ff..40260443f 100644 --- a/prj.conf +++ b/prj.conf @@ -44,5 +44,5 @@ CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" CONFIG_LOG=y CONFIG_LOG_IMMEDIATE=y CONFIG_NET_LOG=y -CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=y +CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=n CONFIG_NET_HTTP_LOG_LEVEL_DBG=y From 5fd491424a9587523f39417b50562171882f97db Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 12 Mar 2021 14:02:10 -0600 Subject: [PATCH 07/56] Added simple protobuf test --- .gitignore | 4 + .gitmodules | 3 + CMakeLists.txt | 4 +- Dockerfile | 2 + Makefile | 12 +- main.c | 57 +++++- nanopb | 1 + policy.c | 367 ------------------------------------- policy.h | 12 -- policy.json | 64 +++++++ policy_to_header.py | 2 + transport_protocol.options | 3 + transport_protocol.proto | 7 + 13 files changed, 155 insertions(+), 383 deletions(-) create mode 100644 .gitmodules create mode 160000 nanopb delete mode 100644 policy.c delete mode 100644 policy.h create mode 100644 policy.json create mode 100644 transport_protocol.options create mode 100644 transport_protocol.proto diff --git a/.gitignore b/.gitignore index 567609b12..6cac3c3cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ build/ +transport_protocol.pb.c +transport_protocol.pb.h +policy.c +policy.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..72bcab209 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "nanopb"] + path = nanopb + url = https://github.com/nanopb/nanopb diff --git a/CMakeLists.txt b/CMakeLists.txt index 2018e967d..8091ed708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,9 @@ cmake_minimum_required(VERSION 3.13.1) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(mini_durango) -target_sources(app PRIVATE main.c) +file(GLOB SRC "*.c" "nanopb/*.c") +target_sources(app PRIVATE ${SRC}) +target_include_directories(app PRIVATE nanopb) include(${ZEPHYR_BASE}/samples/net/common/common.cmake) diff --git a/Dockerfile b/Dockerfile index 8466dc81d..e4f4173a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,8 +37,10 @@ RUN apt-get update && \ libsdl2-dev \ make \ ninja-build \ + protobuf-compiler \ python3-dev \ python3-pip \ + python3-protobuf \ python3-setuptools \ python3-tk \ python3-wheel \ diff --git a/Makefile b/Makefile index 8ff7a13cc..8bf646b85 100644 --- a/Makefile +++ b/Makefile @@ -43,16 +43,26 @@ update: west update # TODO move these into west? -policy.h policy.c: policy.json +# Generate policy.h/c +policy.h policy.c: policy.json policy_to_header.py ./policy_to_header.py $< policy.h policy.c +# Generate transport_protocol.pb.h/c +transport_protocol.pb.h transport_protocol.pb.c: \ + transport_protocol.proto transport_protocol.options + ./nanopb/generator/nanopb_generator.py $< -f $(word 2,$^) + .DEFAULT_GOAL := .PHONY: build build: policy.h policy.c +build: transport_protocol.pb.h transport_protocol.pb.c +build: west build -p auto -b qemu_cortex_m3 .PHONY: clean clean: + rm -rf policy.h policy.c + rm -rf transport_protocol.pb.h transport_protocol.pb.c rm -rf build .PHONY: rom_report diff --git a/main.c b/main.c index 2e773eff7..b7047d505 100644 --- a/main.c +++ b/main.c @@ -21,6 +21,11 @@ #include #include +#include "nanopb/pb.h" +#include "nanopb/pb_encode.h" +#include "nanopb/pb_decode.h" +#include "transport_protocol.pb.h" + // TODO log? //#include //LOG_MODULE_REGISTER(http, CONFIG_FOO_LOG_LEVEL); @@ -185,13 +190,61 @@ void qemu_exit(void) { ); } +void xxd(const void *pbuf, size_t len) { + const uint8_t *buf = pbuf; + + for (int i = 0; i < len; i += 16) { + printf("%08x: ", i); + + for (int j = 0; j < 16; j++) { + if (i+j < len) { + printf("%02x ", buf[i+j]); + } else { + printf(" "); + } + } + + printf(" "); + + for (int j = 0; j < 16 && i+j < len; j++) { + if (i+j < len) { + if (buf[i+j] >= ' ' && buf[i+j] <= '~') { + printf("%c", buf[i+j]); + } else { + printf("."); + } + } else { + printf(" "); + } + } + + printf("\n"); + } +} + void main(void) { + // construct attestation token request + RequestProxyPsaAttestationToken psa_request; + // get random challenge // TODO Zephyr notes this is not cryptographically secure, is that an // issue? This will be an area to explore - uint8_t challenge[32]; - sys_rand_get(challenge, sizeof(challenge)); + sys_rand_get(psa_request.challenge, sizeof(psa_request.challenge)); + + // TODO log? can we incrementally log? + printf("attest: challenge: "); + for (int i = 0; i < sizeof(psa_request.challenge); i++) { + printf("%02x", psa_request.challenge[i]); + } + printf("\n"); + + // encode + // TODO this could be smaller, but instead could we tie protobuf encoding + // directly into our GET function? + uint8_t prbuf[256]; + pb_ostream_t prstream = pb_ostream_from_buffer(prbuf, sizeof(prbuf)); + pb_encode(&prstream, &RequestProxyPsaAttestationToken_msg, &psa_request); // setup address, TODO use DNS? struct sockaddr_in addr; diff --git a/nanopb b/nanopb new file mode 160000 index 000000000..29b8ee492 --- /dev/null +++ b/nanopb @@ -0,0 +1 @@ +Subproject commit 29b8ee492ec33dbd6f2f25203705803cb70b28e4 diff --git a/policy.c b/policy.c deleted file mode 100644 index 5d36d8efc..000000000 --- a/policy.c +++ /dev/null @@ -1,367 +0,0 @@ -//// AUTOGENERATED //// - -uint8_t _veracruz_policy_raw[] = { - 0x7b, 0x0a, 0x20, 0x20, 0x22, 0x63, 0x69, 0x70, - 0x68, 0x65, 0x72, 0x73, 0x75, 0x69, 0x74, 0x65, - 0x22, 0x3a, 0x20, 0x22, 0x54, 0x4c, 0x53, 0x5f, - 0x45, 0x43, 0x44, 0x48, 0x45, 0x5f, 0x45, 0x43, - 0x44, 0x53, 0x41, 0x5f, 0x57, 0x49, 0x54, 0x48, - 0x5f, 0x43, 0x48, 0x41, 0x43, 0x48, 0x41, 0x32, - 0x30, 0x5f, 0x50, 0x4f, 0x4c, 0x59, 0x31, 0x33, - 0x30, 0x35, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x35, - 0x36, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x62, 0x75, 0x67, 0x22, 0x3a, 0x20, 0x66, - 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x20, - 0x22, 0x65, 0x6e, 0x63, 0x6c, 0x61, 0x76, 0x65, - 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x65, 0x78, - 0x70, 0x69, 0x72, 0x79, 0x22, 0x3a, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x61, - 0x79, 0x22, 0x3a, 0x20, 0x39, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x68, 0x6f, 0x75, 0x72, - 0x22, 0x3a, 0x20, 0x31, 0x36, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x6d, 0x69, 0x6e, 0x75, - 0x74, 0x65, 0x22, 0x3a, 0x20, 0x31, 0x37, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6d, 0x6f, - 0x6e, 0x74, 0x68, 0x22, 0x3a, 0x20, 0x37, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x79, 0x65, - 0x61, 0x72, 0x22, 0x3a, 0x20, 0x32, 0x30, 0x32, - 0x31, 0x0a, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, - 0x20, 0x22, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, - 0x74, 0x65, 0x67, 0x79, 0x22, 0x3a, 0x20, 0x22, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x22, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3a, - 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, - 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, - 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x5c, 0x6e, 0x4d, 0x49, 0x49, 0x44, - 0x77, 0x7a, 0x43, 0x43, 0x41, 0x71, 0x75, 0x67, - 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x55, - 0x4f, 0x6e, 0x6e, 0x41, 0x4b, 0x42, 0x4f, 0x2b, - 0x4f, 0x52, 0x72, 0x68, 0x76, 0x5a, 0x64, 0x54, - 0x65, 0x56, 0x47, 0x71, 0x77, 0x54, 0x55, 0x58, - 0x6f, 0x4e, 0x30, 0x77, 0x44, 0x51, 0x59, 0x4a, - 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, - 0x41, 0x51, 0x45, 0x4c, 0x42, 0x51, 0x41, 0x77, - 0x67, 0x59, 0x49, 0x78, 0x43, 0x7a, 0x41, 0x4a, - 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x59, 0x54, - 0x41, 0x6c, 0x56, 0x54, 0x4d, 0x51, 0x34, 0x77, - 0x44, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x49, - 0x44, 0x41, 0x56, 0x55, 0x5a, 0x58, 0x68, 0x68, - 0x63, 0x7a, 0x45, 0x50, 0x4d, 0x41, 0x30, 0x47, - 0x41, 0x31, 0x55, 0x45, 0x42, 0x77, 0x77, 0x47, - 0x51, 0x58, 0x56, 0x7a, 0x64, 0x47, 0x6c, 0x75, - 0x4d, 0x52, 0x59, 0x77, 0x46, 0x41, 0x59, 0x44, - 0x56, 0x51, 0x51, 0x4b, 0x44, 0x41, 0x31, 0x61, - 0x61, 0x57, 0x4a, 0x69, 0x62, 0x47, 0x55, 0x67, - 0x57, 0x6d, 0x46, 0x69, 0x59, 0x6d, 0x78, 0x6c, - 0x4d, 0x53, 0x4d, 0x77, 0x49, 0x51, 0x59, 0x4a, - 0x4b, 0x6f, 0x5a, 0x49, 0x68, 0x76, 0x63, 0x4e, - 0x41, 0x51, 0x6b, 0x42, 0x46, 0x68, 0x52, 0x36, - 0x61, 0x57, 0x4a, 0x69, 0x62, 0x47, 0x56, 0x41, - 0x65, 0x6d, 0x46, 0x69, 0x59, 0x6d, 0x78, 0x6c, - 0x4c, 0x6e, 0x70, 0x70, 0x59, 0x6d, 0x4a, 0x73, - 0x5a, 0x54, 0x45, 0x56, 0x4d, 0x42, 0x4d, 0x47, - 0x41, 0x31, 0x55, 0x45, 0x41, 0x77, 0x77, 0x4d, - 0x65, 0x6d, 0x6c, 0x69, 0x59, 0x6d, 0x78, 0x6c, - 0x65, 0x6d, 0x46, 0x69, 0x59, 0x6d, 0x78, 0x6c, - 0x4d, 0x42, 0x34, 0x58, 0x44, 0x54, 0x49, 0x77, - 0x4d, 0x44, 0x6b, 0x78, 0x4e, 0x6a, 0x45, 0x35, - 0x4e, 0x44, 0x6b, 0x7a, 0x4d, 0x56, 0x6f, 0x58, - 0x44, 0x54, 0x4d, 0x77, 0x4d, 0x44, 0x6b, 0x78, - 0x4e, 0x44, 0x45, 0x35, 0x4e, 0x44, 0x6b, 0x7a, - 0x4d, 0x56, 0x6f, 0x77, 0x67, 0x59, 0x49, 0x78, - 0x43, 0x7a, 0x41, 0x4a, 0x42, 0x67, 0x4e, 0x56, - 0x42, 0x41, 0x59, 0x54, 0x41, 0x6c, 0x56, 0x54, - 0x4d, 0x51, 0x34, 0x77, 0x44, 0x41, 0x59, 0x44, - 0x56, 0x51, 0x51, 0x49, 0x44, 0x41, 0x56, 0x55, - 0x5a, 0x58, 0x68, 0x68, 0x63, 0x7a, 0x45, 0x50, - 0x4d, 0x41, 0x30, 0x47, 0x41, 0x31, 0x55, 0x45, - 0x42, 0x77, 0x77, 0x47, 0x51, 0x58, 0x56, 0x7a, - 0x64, 0x47, 0x6c, 0x75, 0x4d, 0x52, 0x59, 0x77, - 0x46, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x4b, - 0x44, 0x41, 0x31, 0x61, 0x61, 0x57, 0x4a, 0x69, - 0x62, 0x47, 0x55, 0x67, 0x57, 0x6d, 0x46, 0x69, - 0x59, 0x6d, 0x78, 0x6c, 0x4d, 0x53, 0x4d, 0x77, - 0x49, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, - 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x6b, 0x42, - 0x46, 0x68, 0x52, 0x36, 0x61, 0x57, 0x4a, 0x69, - 0x62, 0x47, 0x56, 0x41, 0x65, 0x6d, 0x46, 0x69, - 0x59, 0x6d, 0x78, 0x6c, 0x4c, 0x6e, 0x70, 0x70, - 0x59, 0x6d, 0x4a, 0x73, 0x5a, 0x54, 0x45, 0x56, - 0x4d, 0x42, 0x4d, 0x47, 0x41, 0x31, 0x55, 0x45, - 0x41, 0x77, 0x77, 0x4d, 0x65, 0x6d, 0x6c, 0x69, - 0x59, 0x6d, 0x78, 0x6c, 0x65, 0x6d, 0x46, 0x69, - 0x59, 0x6d, 0x78, 0x6c, 0x4d, 0x49, 0x49, 0x42, - 0x49, 0x6a, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, - 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, - 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x4f, 0x43, - 0x41, 0x51, 0x38, 0x41, 0x4d, 0x49, 0x49, 0x42, - 0x43, 0x67, 0x4b, 0x43, 0x41, 0x51, 0x45, 0x41, - 0x73, 0x72, 0x4c, 0x52, 0x59, 0x4f, 0x6c, 0x59, - 0x4d, 0x52, 0x41, 0x44, 0x6d, 0x4d, 0x2f, 0x73, - 0x6c, 0x33, 0x72, 0x59, 0x62, 0x4b, 0x71, 0x6d, - 0x2f, 0x68, 0x6c, 0x2f, 0x4b, 0x31, 0x78, 0x47, - 0x52, 0x51, 0x2f, 0x73, 0x42, 0x4d, 0x44, 0x66, - 0x61, 0x33, 0x74, 0x6c, 0x2b, 0x6a, 0x53, 0x4b, - 0x36, 0x38, 0x4b, 0x78, 0x64, 0x4f, 0x71, 0x58, - 0x71, 0x46, 0x56, 0x58, 0x77, 0x59, 0x46, 0x4e, - 0x68, 0x38, 0x6b, 0x2b, 0x37, 0x57, 0x77, 0x43, - 0x43, 0x73, 0x69, 0x63, 0x2f, 0x33, 0x67, 0x64, - 0x54, 0x7a, 0x76, 0x74, 0x43, 0x4e, 0x44, 0x6a, - 0x6f, 0x42, 0x35, 0x70, 0x38, 0x67, 0x61, 0x58, - 0x78, 0x6d, 0x71, 0x59, 0x42, 0x72, 0x78, 0x6c, - 0x6c, 0x52, 0x75, 0x55, 0x57, 0x52, 0x49, 0x41, - 0x75, 0x41, 0x48, 0x56, 0x70, 0x64, 0x79, 0x55, - 0x6e, 0x75, 0x4c, 0x41, 0x7a, 0x77, 0x5a, 0x37, - 0x56, 0x54, 0x36, 0x62, 0x55, 0x63, 0x7a, 0x62, - 0x55, 0x69, 0x30, 0x39, 0x71, 0x6d, 0x6e, 0x6a, - 0x42, 0x33, 0x75, 0x34, 0x7a, 0x4f, 0x67, 0x2b, - 0x39, 0x54, 0x66, 0x76, 0x30, 0x77, 0x67, 0x78, - 0x6d, 0x79, 0x67, 0x34, 0x48, 0x53, 0x55, 0x4e, - 0x72, 0x52, 0x49, 0x55, 0x67, 0x57, 0x57, 0x65, - 0x39, 0x44, 0x4a, 0x4b, 0x49, 0x4f, 0x4a, 0x70, - 0x68, 0x41, 0x31, 0x51, 0x79, 0x65, 0x63, 0x47, - 0x46, 0x70, 0x54, 0x49, 0x62, 0x63, 0x41, 0x46, - 0x75, 0x42, 0x57, 0x79, 0x6a, 0x50, 0x31, 0x43, - 0x75, 0x7a, 0x36, 0x54, 0x4f, 0x41, 0x6f, 0x66, - 0x37, 0x34, 0x4f, 0x30, 0x4b, 0x4f, 0x6b, 0x70, - 0x45, 0x67, 0x42, 0x37, 0x74, 0x4d, 0x66, 0x4a, - 0x75, 0x78, 0x43, 0x63, 0x45, 0x47, 0x75, 0x52, - 0x53, 0x62, 0x5a, 0x35, 0x59, 0x2b, 0x6d, 0x2b, - 0x67, 0x6a, 0x73, 0x6f, 0x79, 0x2f, 0x55, 0x59, - 0x49, 0x6e, 0x70, 0x4c, 0x63, 0x49, 0x52, 0x75, - 0x50, 0x32, 0x2b, 0x63, 0x47, 0x36, 0x62, 0x46, - 0x77, 0x55, 0x47, 0x6c, 0x66, 0x4b, 0x59, 0x2b, - 0x61, 0x2b, 0x71, 0x7a, 0x75, 0x36, 0x56, 0x69, - 0x4b, 0x76, 0x46, 0x2b, 0x31, 0x52, 0x36, 0x59, - 0x77, 0x48, 0x56, 0x4f, 0x52, 0x46, 0x6c, 0x30, - 0x30, 0x31, 0x71, 0x69, 0x70, 0x63, 0x50, 0x70, - 0x79, 0x70, 0x35, 0x65, 0x69, 0x32, 0x2b, 0x41, - 0x55, 0x43, 0x46, 0x67, 0x4d, 0x69, 0x42, 0x46, - 0x70, 0x69, 0x48, 0x6e, 0x6b, 0x73, 0x43, 0x46, - 0x48, 0x61, 0x57, 0x47, 0x44, 0x51, 0x49, 0x44, - 0x41, 0x51, 0x41, 0x42, 0x6f, 0x79, 0x38, 0x77, - 0x4c, 0x54, 0x41, 0x72, 0x42, 0x67, 0x4e, 0x56, - 0x48, 0x52, 0x45, 0x45, 0x4a, 0x44, 0x41, 0x69, - 0x67, 0x67, 0x31, 0x36, 0x59, 0x57, 0x4a, 0x69, - 0x62, 0x47, 0x55, 0x75, 0x65, 0x6d, 0x6c, 0x69, - 0x59, 0x6d, 0x78, 0x6c, 0x67, 0x68, 0x46, 0x33, - 0x64, 0x33, 0x63, 0x75, 0x65, 0x6d, 0x46, 0x69, - 0x59, 0x6d, 0x78, 0x6c, 0x4c, 0x6e, 0x70, 0x70, - 0x59, 0x6d, 0x4a, 0x73, 0x5a, 0x54, 0x41, 0x4e, - 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, - 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x73, 0x46, - 0x41, 0x41, 0x4f, 0x43, 0x41, 0x51, 0x45, 0x41, - 0x65, 0x52, 0x55, 0x33, 0x6b, 0x4b, 0x79, 0x48, - 0x75, 0x33, 0x39, 0x4f, 0x36, 0x4e, 0x47, 0x63, - 0x52, 0x74, 0x72, 0x63, 0x66, 0x35, 0x71, 0x4f, - 0x49, 0x51, 0x66, 0x67, 0x4b, 0x6c, 0x53, 0x52, - 0x65, 0x69, 0x56, 0x63, 0x45, 0x76, 0x76, 0x32, - 0x45, 0x59, 0x4c, 0x73, 0x46, 0x57, 0x2f, 0x51, - 0x75, 0x71, 0x70, 0x51, 0x45, 0x37, 0x48, 0x64, - 0x73, 0x61, 0x48, 0x78, 0x39, 0x4e, 0x6d, 0x49, - 0x4a, 0x6d, 0x64, 0x42, 0x34, 0x48, 0x54, 0x6a, - 0x6a, 0x53, 0x49, 0x37, 0x32, 0x52, 0x67, 0x49, - 0x4f, 0x59, 0x73, 0x64, 0x64, 0x41, 0x6a, 0x65, - 0x75, 0x78, 0x75, 0x56, 0x79, 0x43, 0x75, 0x72, - 0x51, 0x4d, 0x53, 0x54, 0x56, 0x67, 0x51, 0x53, - 0x69, 0x49, 0x57, 0x79, 0x35, 0x32, 0x79, 0x66, - 0x2f, 0x4a, 0x33, 0x2f, 0x2f, 0x33, 0x66, 0x37, - 0x6b, 0x53, 0x34, 0x4d, 0x56, 0x69, 0x69, 0x4a, - 0x74, 0x50, 0x35, 0x62, 0x57, 0x64, 0x4b, 0x32, - 0x79, 0x64, 0x54, 0x68, 0x74, 0x6a, 0x4b, 0x31, - 0x2f, 0x62, 0x79, 0x58, 0x7a, 0x77, 0x34, 0x77, - 0x70, 0x61, 0x59, 0x64, 0x58, 0x2b, 0x7a, 0x7a, - 0x5a, 0x66, 0x55, 0x70, 0x4c, 0x6c, 0x4b, 0x33, - 0x66, 0x38, 0x44, 0x47, 0x61, 0x73, 0x39, 0x67, - 0x76, 0x2b, 0x48, 0x5a, 0x38, 0x33, 0x66, 0x63, - 0x5a, 0x48, 0x38, 0x72, 0x52, 0x69, 0x61, 0x72, - 0x70, 0x4f, 0x56, 0x66, 0x68, 0x55, 0x30, 0x75, - 0x41, 0x4e, 0x79, 0x44, 0x35, 0x36, 0x72, 0x50, - 0x4c, 0x6c, 0x67, 0x30, 0x4c, 0x47, 0x36, 0x35, - 0x76, 0x4c, 0x58, 0x46, 0x31, 0x5a, 0x58, 0x2b, - 0x68, 0x75, 0x45, 0x79, 0x77, 0x54, 0x75, 0x63, - 0x36, 0x6f, 0x68, 0x32, 0x2f, 0x4a, 0x58, 0x51, - 0x70, 0x47, 0x72, 0x44, 0x76, 0x74, 0x74, 0x70, - 0x62, 0x69, 0x42, 0x4e, 0x6f, 0x33, 0x44, 0x35, - 0x71, 0x51, 0x61, 0x46, 0x49, 0x34, 0x4a, 0x71, - 0x66, 0x33, 0x79, 0x58, 0x6a, 0x4b, 0x76, 0x62, - 0x51, 0x76, 0x73, 0x37, 0x45, 0x4b, 0x72, 0x51, - 0x6a, 0x2b, 0x47, 0x49, 0x50, 0x32, 0x4b, 0x6c, - 0x7a, 0x73, 0x76, 0x2b, 0x62, 0x74, 0x45, 0x46, - 0x37, 0x55, 0x46, 0x63, 0x61, 0x53, 0x6d, 0x73, - 0x31, 0x2f, 0x43, 0x33, 0x52, 0x67, 0x35, 0x41, - 0x4c, 0x37, 0x77, 0x78, 0x68, 0x39, 0x52, 0x39, - 0x7a, 0x4e, 0x58, 0x73, 0x45, 0x72, 0x46, 0x70, - 0x65, 0x43, 0x4d, 0x2f, 0x6e, 0x62, 0x47, 0x6d, - 0x35, 0x53, 0x6c, 0x75, 0x58, 0x41, 0x3d, 0x3d, - 0x5c, 0x6e, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, - 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, - 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, - 0x65, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, 0x20, - 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x22, 0x3a, - 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, - 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x30, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x61, - 0x64, 0x22, 0x3a, 0x20, 0x66, 0x61, 0x6c, 0x73, - 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x74, 0x72, - 0x75, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x65, 0x22, 0x3a, 0x20, 0x66, 0x61, 0x6c, - 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, - 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x22, 0x3a, 0x20, 0x22, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x72, 0x65, 0x61, 0x64, 0x22, 0x3a, 0x20, 0x74, - 0x72, 0x75, 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, 0x3a, 0x20, - 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x65, 0x22, 0x3a, 0x20, - 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6c, 0x69, - 0x6e, 0x65, 0x61, 0x72, 0x2d, 0x72, 0x65, 0x67, - 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, - 0x77, 0x61, 0x73, 0x6d, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x72, 0x65, 0x61, 0x64, 0x22, 0x3a, - 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x22, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x69, 0x64, 0x22, 0x3a, 0x20, 0x30, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, - 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x6d, - 0x65, 0x78, 0x69, 0x63, 0x6f, 0x5f, 0x63, 0x69, - 0x74, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, - 0x6e, 0x69, 0x74, 0x72, 0x6f, 0x22, 0x3a, 0x20, - 0x6e, 0x75, 0x6c, 0x6c, 0x2c, 0x0a, 0x20, 0x20, - 0x22, 0x6d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x5f, - 0x63, 0x69, 0x74, 0x79, 0x5f, 0x68, 0x61, 0x73, - 0x68, 0x5f, 0x73, 0x67, 0x78, 0x22, 0x3a, 0x20, - 0x22, 0x34, 0x66, 0x66, 0x32, 0x62, 0x31, 0x61, - 0x30, 0x33, 0x36, 0x66, 0x65, 0x66, 0x62, 0x65, - 0x35, 0x31, 0x66, 0x65, 0x65, 0x37, 0x61, 0x39, - 0x31, 0x65, 0x39, 0x66, 0x30, 0x34, 0x61, 0x35, - 0x39, 0x66, 0x62, 0x32, 0x32, 0x64, 0x62, 0x31, - 0x33, 0x33, 0x38, 0x35, 0x38, 0x33, 0x61, 0x35, - 0x36, 0x64, 0x38, 0x34, 0x62, 0x30, 0x37, 0x36, - 0x64, 0x64, 0x33, 0x32, 0x65, 0x37, 0x37, 0x39, - 0x32, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x22, 0x6d, - 0x65, 0x78, 0x69, 0x63, 0x6f, 0x5f, 0x63, 0x69, - 0x74, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, - 0x74, 0x7a, 0x22, 0x3a, 0x20, 0x22, 0x34, 0x66, - 0x66, 0x32, 0x62, 0x31, 0x61, 0x30, 0x33, 0x36, - 0x66, 0x65, 0x66, 0x62, 0x65, 0x35, 0x31, 0x66, - 0x65, 0x65, 0x37, 0x61, 0x39, 0x31, 0x65, 0x39, - 0x66, 0x30, 0x34, 0x61, 0x35, 0x39, 0x66, 0x62, - 0x32, 0x32, 0x64, 0x62, 0x31, 0x33, 0x33, 0x38, - 0x35, 0x38, 0x33, 0x61, 0x35, 0x36, 0x64, 0x38, - 0x34, 0x62, 0x30, 0x37, 0x36, 0x64, 0x64, 0x33, - 0x32, 0x65, 0x37, 0x37, 0x39, 0x32, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x73, 0x22, 0x3a, 0x20, 0x5b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, - 0x6c, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3a, - 0x20, 0x5b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x22, - 0x3a, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x6c, 0x65, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x20, - 0x22, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2d, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, - 0x61, 0x64, 0x22, 0x3a, 0x20, 0x74, 0x72, 0x75, - 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x66, 0x61, - 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x65, 0x22, 0x3a, 0x20, 0x66, 0x61, - 0x6c, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x22, 0x3a, 0x20, 0x22, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x72, 0x65, 0x61, 0x64, 0x22, 0x3a, 0x20, - 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x77, 0x72, 0x69, 0x74, 0x65, 0x22, - 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5d, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x69, 0x64, 0x22, 0x3a, 0x20, 0x30, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x70, 0x69, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x22, - 0x3a, 0x20, 0x22, 0x63, 0x31, 0x30, 0x38, 0x34, - 0x30, 0x37, 0x66, 0x64, 0x62, 0x36, 0x66, 0x66, - 0x64, 0x35, 0x35, 0x38, 0x33, 0x64, 0x65, 0x30, - 0x37, 0x65, 0x33, 0x65, 0x62, 0x35, 0x39, 0x34, - 0x39, 0x35, 0x63, 0x36, 0x30, 0x31, 0x66, 0x66, - 0x66, 0x30, 0x65, 0x34, 0x35, 0x62, 0x39, 0x33, - 0x62, 0x38, 0x34, 0x30, 0x61, 0x32, 0x32, 0x31, - 0x33, 0x61, 0x36, 0x34, 0x34, 0x38, 0x65, 0x31, - 0x62, 0x33, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6f, - 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x66, 0x69, 0x6c, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, - 0x20, 0x22, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, - 0x2d, 0x72, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x2e, 0x77, 0x61, 0x73, 0x6d, - 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, - 0x20, 0x20, 0x5d, 0x2c, 0x0a, 0x20, 0x20, 0x22, - 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x61, 0x74, - 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x5f, 0x75, 0x72, 0x6c, 0x22, 0x3a, 0x20, 0x22, - 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, - 0x31, 0x3a, 0x33, 0x30, 0x31, 0x30, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x22, 0x73, 0x69, 0x6e, 0x61, - 0x6c, 0x6f, 0x61, 0x5f, 0x75, 0x72, 0x6c, 0x22, - 0x3a, 0x20, 0x22, 0x31, 0x32, 0x37, 0x2e, 0x30, - 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x33, 0x30, 0x31, - 0x32, 0x22, 0x0a, 0x7d, -}; diff --git a/policy.h b/policy.h deleted file mode 100644 index 27b662c8c..000000000 --- a/policy.h +++ /dev/null @@ -1,12 +0,0 @@ -//// AUTOGENERATED //// -#ifndef VERACRUZ_POLICY_H -#define VERACRUZ_POLICY_H - -#include - -extern uint8_t _veracruz_policy_raw[]; - -#define VERACRUZ_POLICY_HASH "2713d3d5a1b4570049f971cc8ca53f95cd4010710ba09f6622baefca127281ff" -#define VERACRUZ_POLICY_RAW _veracruz_policy_raw - -#endif diff --git a/policy.json b/policy.json new file mode 100644 index 000000000..9176fd500 --- /dev/null +++ b/policy.json @@ -0,0 +1,64 @@ +{ + "ciphersuite": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "debug": false, + "enclave_cert_expiry": { + "day": 9, + "hour": 16, + "minute": 17, + "month": 7, + "year": 2021 + }, + "execution_strategy": "Interpretation", + "identities": [ + { + "certificate": "-----BEGIN CERTIFICATE-----\nMIIDwzCCAqugAwIBAgIUOnnAKBO+ORrhvZdTeVGqwTUXoN0wDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMRYwFAYDVQQKDA1aaWJibGUgWmFiYmxlMSMwIQYJKoZIhvcNAQkBFhR6aWJibGVAemFiYmxlLnppYmJsZTEVMBMGA1UEAwwMemliYmxlemFiYmxlMB4XDTIwMDkxNjE5NDkzMVoXDTMwMDkxNDE5NDkzMVowgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMRYwFAYDVQQKDA1aaWJibGUgWmFiYmxlMSMwIQYJKoZIhvcNAQkBFhR6aWJibGVAemFiYmxlLnppYmJsZTEVMBMGA1UEAwwMemliYmxlemFiYmxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrLRYOlYMRADmM/sl3rYbKqm/hl/K1xGRQ/sBMDfa3tl+jSK68KxdOqXqFVXwYFNh8k+7WwCCsic/3gdTzvtCNDjoB5p8gaXxmqYBrxllRuUWRIAuAHVpdyUnuLAzwZ7VT6bUczbUi09qmnjB3u4zOg+9Tfv0wgxmyg4HSUNrRIUgWWe9DJKIOJphA1QyecGFpTIbcAFuBWyjP1Cuz6TOAof74O0KOkpEgB7tMfJuxCcEGuRSbZ5Y+m+gjsoy/UYInpLcIRuP2+cG6bFwUGlfKY+a+qzu6ViKvF+1R6YwHVORFl001qipcPpyp5ei2+AUCFgMiBFpiHnksCFHaWGDQIDAQABoy8wLTArBgNVHREEJDAigg16YWJibGUuemliYmxlghF3d3cuemFiYmxlLnppYmJsZTANBgkqhkiG9w0BAQsFAAOCAQEAeRU3kKyHu39O6NGcRtrcf5qOIQfgKlSReiVcEvv2EYLsFW/QuqpQE7HdsaHx9NmIJmdB4HTjjSI72RgIOYsddAjeuxuVyCurQMSTVgQSiIWy52yf/J3//3f7kS4MViiJtP5bWdK2ydThtjK1/byXzw4wpaYdX+zzZfUpLlK3f8DGas9gv+HZ83fcZH8rRiarpOVfhU0uANyD56rPLlg0LG65vLXF1ZX+huEywTuc6oh2/JXQpGrDvttpbiBNo3D5qQaFI4Jqf3yXjKvbQvs7EKrQj+GIP2Klzsv+btEF7UFcaSms1/C3Rg5AL7wxh9R9zNXsErFpeCM/nbGm5SluXA==\n-----END CERTIFICATE-----", + "file_permissions": [ + { + "execute": false, + "file_name": "input-0", + "read": false, + "write": true + }, + { + "execute": false, + "file_name": "output", + "read": true, + "write": false + }, + { + "execute": true, + "file_name": "linear-regression.wasm", + "read": false, + "write": true + } + ], + "id": 0 + } + ], + "mexico_city_hash_nitro": null, + "mexico_city_hash_sgx": "4ff2b1a036fefbe51fee7a91e9f04a59fb22db1338583a56d84b076dd32e7792", + "mexico_city_hash_tz": "4ff2b1a036fefbe51fee7a91e9f04a59fb22db1338583a56d84b076dd32e7792", + "programs": [ + { + "file_permissions": [ + { + "execute": false, + "file_name": "input-0", + "read": true, + "write": false + }, + { + "execute": false, + "file_name": "output", + "read": false, + "write": true + } + ], + "id": 0, + "pi_hash": "c108407fdb6ffd5583de07e3eb59495c601fff0e45b93b840a2213a6448e1b38", + "program_file_name": "linear-regression.wasm" + } + ], + "proxy_attestation_server_url": "127.0.0.1:3010", + "sinaloa_url": "127.0.0.1:3012" +} \ No newline at end of file diff --git a/policy_to_header.py b/policy_to_header.py index 39458ef4d..d76c1f542 100755 --- a/policy_to_header.py +++ b/policy_to_header.py @@ -47,6 +47,8 @@ def writeln(s='', **args): f.writeln('//// AUTOGENERATED ////') f.writeln() + f.writeln('#include ') + f.writeln() f.writeln('uint8_t _veracruz_policy_raw[] = {') for i in range(0, len(policy_raw), 8): f.writeln(' %(raw)s', diff --git a/transport_protocol.options b/transport_protocol.options new file mode 100644 index 000000000..abeb35afd --- /dev/null +++ b/transport_protocol.options @@ -0,0 +1,3 @@ +# Options for internal representation of protobufs + +RequestProxyPsaAttestationToken.challenge max_size:32 fixed_length:true diff --git a/transport_protocol.proto b/transport_protocol.proto new file mode 100644 index 000000000..98ddef34f --- /dev/null +++ b/transport_protocol.proto @@ -0,0 +1,7 @@ +// Protocol buffers for Veracruz's transport protocol messages + +syntax = "proto3"; + +message RequestProxyPsaAttestationToken { + bytes challenge = 1; +} From 94af11f3f7d8e04eca4d2c62e0043f2018d78268 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 15 Apr 2021 13:43:11 -0500 Subject: [PATCH 08/56] Simple PAT request working --- http_test.py | 17 +- main.c | 411 ++++++++++++++++++++++++++++++++----- policy.json | 84 ++++++-- transport_protocol.options | 5 +- transport_protocol.proto | 225 +++++++++++++++++++- 5 files changed, 664 insertions(+), 78 deletions(-) diff --git a/http_test.py b/http_test.py index 0a22e160e..130cbb4fe 100755 --- a/http_test.py +++ b/http_test.py @@ -1,7 +1,20 @@ #!/usr/bin/env python3 import http.client +import base64 + +data = [ + 0x0a, 0x20, 0xbf, 0x5d, 0xd2, 0xb2, 0xc6, 0x28, 0x6d, 0xee, 0xe2, 0xf2, 0x07, 0x2a, 0xfb, 0xbc, + 0xa2, 0x65, 0x15, 0x87, 0x3d, 0xa1, 0x2e, 0x51, 0xd8, 0xdc, 0x48, 0x1b, 0x73, 0x18, 0x61, 0xe5, + 0x0d, 0x54 +] + +http.client.HTTPConnection.debuglevel = 1 c = http.client.HTTPConnection('172.17.0.2', 3017, timeout=3) -c.request("GET", "/") -print(c.getresponse()) +c.request("POST", "/sinaloa", body=base64.b64encode(b''.join(c.to_bytes(1, 'little') for c in data))) +print(repr(c.getresponse())) + +#c = http.client.HTTPConnection('172.17.0.2', 3017, timeout=3) +#c.request("GET", "/") +#print(c.getresponse()) diff --git a/main.c b/main.c index b7047d505..53a75726b 100644 --- a/main.c +++ b/main.c @@ -43,10 +43,10 @@ //#define HTTP_HOST "172.17.0.2" ///* Port to connect to, as string */ //#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) -//#define HTTP_PORT "443" +//#define VERACRUZ_SERVER_PORT "443" //#else -////#define HTTP_PORT "80" -//#define HTTP_PORT "3017" +////#define VERACRUZ_SERVER_PORT "80" +//#define VERACRUZ_SERVER_PORT "3017" //#endif ///* HTTP path to request */ //#define HTTP_PATH "/" @@ -68,12 +68,28 @@ // ((struct sockaddr_in *)ai->ai_addr)->sin_port); //} -#ifndef HTTP_SERVER -#define HTTP_SERVER "172.17.0.2" +//// HTTP stuff //// + +// TODO use generated policy.h +#ifndef VERACRUZ_SERVER_HOST +#define VERACRUZ_SERVER_HOST "172.17.0.2" +#endif + +#ifndef VERACRUZ_SERVER_PORT +#define VERACRUZ_SERVER_PORT 3017 +#endif + +// TODO common prefix for veracruz client functions? +#ifndef VERACRUZ_TIMEOUT +#define VERACRUZ_TIMEOUT 3000 #endif -#ifndef HTTP_PORT -#define HTTP_PORT 3017 +#ifndef PROXY_ATTESTATION_SERVER_HOST +#define PROXY_ATTESTATION_SERVER_HOST "172.17.0.2" +#endif + +#ifndef PROXY_ATTESTATION_SERVER_PORT +#define PROXY_ATTESTATION_SERVER_PORT 3010 #endif struct http_get_state { @@ -96,7 +112,7 @@ static void http_get_cb( struct http_get_state *state = udata; uint8_t *start = (rsp->body_start) ? rsp->body_start : rsp->recv_buf; - size_t len = rsp->data_len; + size_t len = rsp->processed - state->pos; if (state->pos + len > state->buf_len) { printf("http get buffer overflow! truncating (%d > %d)\n", @@ -111,15 +127,19 @@ static void http_get_cb( // TODO TLS? https_get? ssize_t http_get( - struct sockaddr *server_addr, - socklen_t server_addr_len, - // TODO dedup this? const char *host, + uint16_t port, const char *path, // TODO headers? uint8_t *buf, - size_t buf_len, - int32_t timeout) { + size_t buf_len) { + // setup address, TODO use DNS? + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, host, &addr.sin_addr); + // allocate packet buffer for managing http connection void *pbuf = malloc(256); size_t pbuf_len = 256; @@ -138,7 +158,7 @@ ssize_t http_get( return -errno; } - int res = connect(sock, server_addr, server_addr_len); + int res = connect(sock, (struct sockaddr*)&addr, sizeof(addr)); if (res < 0) { printf("http connect failed (%d)\n", -errno); free(pbuf); @@ -154,17 +174,17 @@ ssize_t http_get( }; // perform client request, Zephyr handles most of this for us - struct http_request req; - memset(&req, 0, sizeof(req)); - req.method = HTTP_GET; - req.url = path; - req.host = host; - req.protocol = "HTTP/1.1"; - req.response = http_get_cb; - req.recv_buf = pbuf; - req.recv_buf_len = pbuf_len; - - int err = http_client_req(sock, &req, timeout, &state); + struct http_request req = { + .method = HTTP_GET, + .url = path, + .host = host, + .protocol = "HTTP/1.1", + .response = http_get_cb, + .recv_buf = pbuf, + .recv_buf_len = pbuf_len, + }; + + int err = http_client_req(sock, &req, VERACRUZ_TIMEOUT, &state); if (err < 0) { printf("http req failed (%d)\n", err); free(pbuf); @@ -178,7 +198,206 @@ ssize_t http_get( return state.pos; } +ssize_t http_post( + const char *host, + uint16_t port, + const char *path, + // TODO headers? + const uint8_t *payload_buf, + size_t payload_buf_len, + uint8_t *resp_buf, + size_t resp_buf_len) { + // setup address, TODO use DNS? + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, host, &addr.sin_addr); + + // allocate packet buffer for managing http connection + void *pbuf = malloc(256); + size_t pbuf_len = 256; + if (!pbuf) { + printf("http malloc failed (-ENOMEM)\n"); + return -ENOMEM; + } + + // create socket and connect to server + // TODO IPv6? can use net_sin(addr)->sin_family here + // DNS? Take in a string more useful API? + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + printf("http socket open failed (%d)\n", -errno); + free(pbuf); + return -errno; + } + + int res = connect(sock, (struct sockaddr*)&addr, sizeof(addr)); + if (res < 0) { + printf("http connect failed (%d)\n", -errno); + free(pbuf); + close(sock); + return -errno; + } + + // state for filling in buffer + struct http_get_state state = { + .buf = resp_buf, + .buf_len = resp_buf_len, + .pos = 0, + }; + + // perform client request, Zephyr handles most of this for us + struct http_request req = { + .method = HTTP_POST, + .url = path, + .host = host, + .protocol = "HTTP/1.1", + .response = http_get_cb, + .payload = payload_buf, + .payload_len = payload_buf_len, + .recv_buf = pbuf, + .recv_buf_len = pbuf_len, + }; + + int err = http_client_req(sock, &req, VERACRUZ_TIMEOUT, &state); + if (err < 0) { + printf("http req failed (%d)\n", err); + free(pbuf); + close(sock); + return err; + } + + // done, close + free(pbuf); + close(sock); + return state.pos; +} + +//// base64 //// + +size_t base64_encode_size(size_t in_len) { + size_t x = in_len; + if (in_len % 3 != 0) { + x += 3 - (in_len % 3); + } + + return 4*(x/3); +} + +static const char BASE64_ENCODE[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +ssize_t base64_encode( + const uint8_t *in, size_t in_len, + char *out, size_t out_len) { + size_t e_len = base64_encode_size(in_len); + if (e_len+1 > out_len) { + return -EOVERFLOW; + } + out[e_len] = '\0'; + + for (size_t i=0, j=0; i < in_len; i += 3, j += 4) { + size_t v = in[i]; + v = i+1 < e_len ? (v << 8 | in[i+1]) : (v << 8); + v = i+2 < e_len ? (v << 8 | in[i+2]) : (v << 8); + + out[j] = BASE64_ENCODE[(v >> 18) & 0x3f]; + out[j+1] = BASE64_ENCODE[(v >> 12) & 0x3f]; + + if (i+1 < in_len) { + out[j+2] = BASE64_ENCODE[(v >> 6) & 0x3f]; + } else { + out[j+2] = '='; + } + + if (i+2 < in_len) { + out[j+3] = BASE64_ENCODE[v & 0x3f]; + } else { + out[j+3] = '='; + } + } + + return e_len; +} + +size_t base64_decode_size(const char *in) { + size_t in_len = strlen(in); + + size_t x = 3*(in_len/4); + for (size_t i = 0; i < in_len && in[in_len-i-1] == '='; i++) { + x -= 1; + } + + return x; +} + +static const int8_t BASE64_DECODE[] = { + 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, + -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +}; + +static bool base64_isvalid(char c) { + if (c >= '0' && c <= '9') { + return true; + } else if (c >= 'A' && c <= 'Z') { + return true; + } else if (c >= 'a' && c <= 'z') { + return true; + } else if (c == '+' || c == '/' || c == '=') { + return true; + } else { + return false; + } +} + +ssize_t base64_decode( + const char *in, + char *out, size_t out_len) { + size_t in_len = strlen(in); + if (in_len % 4 != 0) { + return -EINVAL; + } + + size_t d_len = base64_decode_size(in); + if (d_len > out_len) { + return -EOVERFLOW; + } + + for (size_t i = 0; i < in_len; i++) { + if (!base64_isvalid(in[i])) { + return -EILSEQ; + } + } + + for (size_t i=0, j=0; i < in_len; i += 4, j += 3) { + size_t v = BASE64_DECODE[in[i]-43]; + v = (v << 6) | BASE64_DECODE[in[i+1]-43]; + v = in[i+2] == '=' ? (v << 6) : ((v << 6) | BASE64_DECODE[in[i+2]-43]); + v = in[i+3] == '=' ? (v << 6) : ((v << 6) | BASE64_DECODE[in[i+3]-43]); + + out[j] = (v >> 16) & 0xff; + + if (in[i+2] != '=') { + out[j+1] = (v >> 8) & 0xff; + } + + if (in[i+3] != '=') { + out[j+2] = v & 0xff; + } + } + + return d_len; +} + + + + +//// application logic //// + uint8_t buffer[10*1024]; +uint8_t buffer2[10*1024]; // hack to exit QEMU //__attribute__((noreturn)) @@ -224,57 +443,137 @@ void xxd(const void *pbuf, size_t len) { void main(void) { - // construct attestation token request - RequestProxyPsaAttestationToken psa_request; - // get random challenge // TODO Zephyr notes this is not cryptographically secure, is that an // issue? This will be an area to explore - sys_rand_get(psa_request.challenge, sizeof(psa_request.challenge)); + uint8_t challenge[32]; + sys_rand_get(challenge, sizeof(challenge)); // TODO log? can we incrementally log? printf("attest: challenge: "); - for (int i = 0; i < sizeof(psa_request.challenge); i++) { - printf("%02x", psa_request.challenge[i]); + for (int i = 0; i < sizeof(challenge); i++) { + printf("%02x", challenge[i]); } printf("\n"); + // construct attestation token request + Tp_RuntimeManagerRequest request = { + .which_message_oneof = Tp_RuntimeManagerRequest_request_proxy_psa_attestation_token_tag + }; + memcpy(request.message_oneof.request_proxy_psa_attestation_token.challenge, + challenge, sizeof(challenge)); + // encode // TODO this could be smaller, but instead could we tie protobuf encoding // directly into our GET function? - uint8_t prbuf[256]; - pb_ostream_t prstream = pb_ostream_from_buffer(prbuf, sizeof(prbuf)); - pb_encode(&prstream, &RequestProxyPsaAttestationToken_msg, &psa_request); + uint8_t request_buf[256]; + pb_ostream_t request_stream = pb_ostream_from_buffer( + request_buf, sizeof(request_buf)); + pb_encode(&request_stream, &Tp_RuntimeManagerRequest_msg, &request); + + // convert base64 + // TODO if base64 was reversed this could operate in-place + char request_b64_buf[256]; + ssize_t request_b64_len = base64_encode( + request_buf, request_stream.bytes_written, + request_b64_buf, sizeof(request_b64_buf)); + if (request_b64_len < 0) { + printf("base64_encode failed (%d)\n", request_b64_len); + qemu_exit(); + } - // setup address, TODO use DNS? - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(HTTP_PORT); - inet_pton(AF_INET, HTTP_SERVER, &addr.sin_addr); - - // perform GET - printf("connecting to %s:%d...\n", HTTP_SERVER, HTTP_PORT); - ssize_t res = http_get( - (struct sockaddr*)&addr, - sizeof(addr), - HTTP_SERVER, - "/", + printf("request:\n"); + xxd(request_b64_buf, request_b64_len); + + // POST challenge + // TODO get from policy.h + printf("connecting to %s:%d...\n", + VERACRUZ_SERVER_HOST, + VERACRUZ_SERVER_PORT); + ssize_t pat_len = http_post( + VERACRUZ_SERVER_HOST, + VERACRUZ_SERVER_PORT, + "/sinaloa", + request_b64_buf, + request_b64_len, + buffer, + sizeof(buffer)); + if (pat_len < 0) { + printf("http_post failed (%d)\n", pat_len); + qemu_exit(); + } + + printf("http_post -> %d\n", pat_len); + printf("attest: challenge response:\n"); + xxd(buffer, pat_len); + + // forward to proxy attestation server + printf("connecting to %s:%d...\n", + PROXY_ATTESTATION_SERVER_HOST, + PROXY_ATTESTATION_SERVER_PORT); + ssize_t res = http_post( + PROXY_ATTESTATION_SERVER_HOST, + PROXY_ATTESTATION_SERVER_PORT, + "/VerifyPAT", buffer, - sizeof(buffer), - 3000); + pat_len, + buffer2, + sizeof(buffer2)); if (res < 0) { - printf("http_get failed (%d)\n", res); + printf("http_post failed (%d)\n", res); + qemu_exit(); + } + + printf("http_post -> %d\n", res); + printf("attest: PAT response:\n"); + xxd(buffer2, res); + + // back to buffer1, TODO in-place base64? + // TODO use strnlen... + ssize_t verif_len = base64_decode(buffer2, buffer, sizeof(buffer)); + if (verif_len < 0) { + printf("base64_decode failed (%d)\n", verif_len); + qemu_exit(); + } + + printf("attest: PAT decoded response:\n"); + xxd(buffer, res); + + if (verif_len < 131) { + printf("pat response too small\n"); qemu_exit(); } - printf("http get -> %d\n", res); - for (int i = 0; i < res; i++) { - if (buffer[i] != '\r') { - printf("%c", buffer[i]); + // check that challenge matches + if (memcmp(challenge, &buffer[8], 32) != 0) { + printf("challenge mismatch\n"); + printf("expected: "); + for (int i = 0; i < sizeof(challenge); i++) { + printf("%02x", challenge[i]); } + printf("\n"); + printf("recieved: "); + for (int i = 0; i < 32; i++) { + printf("%02x", buffer[8+i]); + } + printf("\n"); + qemu_exit(); } - printf("\n"); + + // TODO check these against policy + printf("enclave hash:\n"); + xxd(&buffer[47], 32); + printf("enclave cert hash:\n"); + xxd(&buffer[86], 32); + printf("enclave name: %.*s\n", 7, &buffer[124]); + +// +// for (int i = 0; i < res; i++) { +// if (buffer[i] != '\r') { +// printf("%c", buffer[i]); +// } +// } +// printf("\n"); // // while (1) { diff --git a/policy.json b/policy.json index 9176fd500..2e073de57 100644 --- a/policy.json +++ b/policy.json @@ -2,37 +2,73 @@ "ciphersuite": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "debug": false, "enclave_cert_expiry": { - "day": 9, - "hour": 16, - "minute": 17, - "month": 7, + "day": 17, + "hour": 12, + "minute": 23, + "month": 6, "year": 2021 }, "execution_strategy": "Interpretation", "identities": [ { - "certificate": "-----BEGIN CERTIFICATE-----\nMIIDwzCCAqugAwIBAgIUOnnAKBO+ORrhvZdTeVGqwTUXoN0wDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMRYwFAYDVQQKDA1aaWJibGUgWmFiYmxlMSMwIQYJKoZIhvcNAQkBFhR6aWJibGVAemFiYmxlLnppYmJsZTEVMBMGA1UEAwwMemliYmxlemFiYmxlMB4XDTIwMDkxNjE5NDkzMVoXDTMwMDkxNDE5NDkzMVowgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMRYwFAYDVQQKDA1aaWJibGUgWmFiYmxlMSMwIQYJKoZIhvcNAQkBFhR6aWJibGVAemFiYmxlLnppYmJsZTEVMBMGA1UEAwwMemliYmxlemFiYmxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsrLRYOlYMRADmM/sl3rYbKqm/hl/K1xGRQ/sBMDfa3tl+jSK68KxdOqXqFVXwYFNh8k+7WwCCsic/3gdTzvtCNDjoB5p8gaXxmqYBrxllRuUWRIAuAHVpdyUnuLAzwZ7VT6bUczbUi09qmnjB3u4zOg+9Tfv0wgxmyg4HSUNrRIUgWWe9DJKIOJphA1QyecGFpTIbcAFuBWyjP1Cuz6TOAof74O0KOkpEgB7tMfJuxCcEGuRSbZ5Y+m+gjsoy/UYInpLcIRuP2+cG6bFwUGlfKY+a+qzu6ViKvF+1R6YwHVORFl001qipcPpyp5ei2+AUCFgMiBFpiHnksCFHaWGDQIDAQABoy8wLTArBgNVHREEJDAigg16YWJibGUuemliYmxlghF3d3cuemFiYmxlLnppYmJsZTANBgkqhkiG9w0BAQsFAAOCAQEAeRU3kKyHu39O6NGcRtrcf5qOIQfgKlSReiVcEvv2EYLsFW/QuqpQE7HdsaHx9NmIJmdB4HTjjSI72RgIOYsddAjeuxuVyCurQMSTVgQSiIWy52yf/J3//3f7kS4MViiJtP5bWdK2ydThtjK1/byXzw4wpaYdX+zzZfUpLlK3f8DGas9gv+HZ83fcZH8rRiarpOVfhU0uANyD56rPLlg0LG65vLXF1ZX+huEywTuc6oh2/JXQpGrDvttpbiBNo3D5qQaFI4Jqf3yXjKvbQvs7EKrQj+GIP2Klzsv+btEF7UFcaSms1/C3Rg5AL7wxh9R9zNXsErFpeCM/nbGm5SluXA==\n-----END CERTIFICATE-----", + "certificate": "-----BEGIN CERTIFICATE-----\nMIIDzTCCArWgAwIBAgIUHP/u8iA1GmAaN+kglF5AQLluS34wDQYJKoZIhvcNAQELBQAwgYcxCzAJBgNVBAYTAk14MREwDwYDVQQIDAhWZXJhY3J1ejERMA8GA1UEBwwIVmVyYWNydXoxFjAUBgNVBAoMDVppYmJsZSBaYWJibGUxIzAhBgkqhkiG9w0BCQEWFHppYmJsZUB6YWJibGUuemliYmxlMRUwEwYDVQQDDAx6aWJibGV6YWJibGUwHhcNMjEwMzA4MTc0ODUzWhcNMzEwMzA2MTc0ODUzWjCBhzELMAkGA1UEBhMCTXgxETAPBgNVBAgMCFZlcmFjcnV6MREwDwYDVQQHDAhWZXJhY3J1ejEWMBQGA1UECgwNWmliYmxlIFphYmJsZTEjMCEGCSqGSIb3DQEJARYUemliYmxlQHphYmJsZS56aWJibGUxFTATBgNVBAMMDHppYmJsZXphYmJsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVnPY9LpsYM0Yr1x58qYX4mSuU5AsADUrpfFigRHmcd1ZCIlT6jVClxF/IyFT4n/ym0XiQo1AiNjThWqaCefG0H9S+8VKky4vZlsMTowh0JKC6ret7TNpwnevR84BobTzS5gbwBmmpLBGAFD/XBfoPve+tJD/ViBsYgXM8SAnqjNomgCmjTMUcrcaSlPho6kacSth1ajcaQxLLnUxvM/YXXb3ZRK9D+FGfnn7YuUGRNNd/kI64ACQdYyb39TbXD8Tn54Ya8HDnEmlptqC/y+kXn4WCQQUsN+oexPFXaxtmc8/rpW/SOL+y0dZBMQxvZ8qcOGqaZ9RxV0LduaF603OMCAwEAAaMvMC0wKwYDVR0RBCQwIoINemFiYmxlLnppYmJsZYIRd3d3LnphYmJsZS56aWJibGUwDQYJKoZIhvcNAQELBQADggEBAMKsPO2TRnkUqRPdsIwlu3sx0GfxSD8gL6nkMXAlAR2fxtCFaYpOmlXiheMoLtxMVfxk8blOHSRV6Rd3WSqCQ6UGSRrrkXiiXKOuRWZyteiuiXYupxE/9cZ75vaNGzI1hvGC6leOgUompFCqwuBCPWHQLtNAoUyy8zaz/eipquKo/ofjenRCeI6VhqcAZVi0bpxn5kIDKJIHydrIaKDak9yQEZcXUL5n57Qgl87qD26zisvqCAW2Gb1tXrk8Plvr6OU9SfbOciCjZua8RIPsjSgLAZ5I2hwcmbEmEO2o7sCyvBHbPAsb4SSm76WP9RAhlF5FWe2LlWprgGLxliu9bMc=\n-----END CERTIFICATE-----", + "file_permissions": [ + { + "execute": true, + "file_name": "example-binary.wasm", + "read": false, + "write": true + } + ], + "id": 0 + }, + { + "certificate": "-----BEGIN CERTIFICATE-----\nMIIDzTCCArWgAwIBAgIUVirizhzBk1Z+psxK26AJyj+K0WowDQYJKoZIhvcNAQELBQAwgYcxCzAJBgNVBAYTAk14MREwDwYDVQQIDAhWZXJhY3J1ejERMA8GA1UEBwwIVmVyYWNydXoxFjAUBgNVBAoMDVppYmJsZSBaYWJibGUxIzAhBgkqhkiG9w0BCQEWFHppYmJsZUB6YWJibGUuemliYmxlMRUwEwYDVQQDDAx6aWJibGV6YWJibGUwHhcNMjEwMzA4MTc0OTIwWhcNMzEwMzA2MTc0OTIwWjCBhzELMAkGA1UEBhMCTXgxETAPBgNVBAgMCFZlcmFjcnV6MREwDwYDVQQHDAhWZXJhY3J1ejEWMBQGA1UECgwNWmliYmxlIFphYmJsZTEjMCEGCSqGSIb3DQEJARYUemliYmxlQHphYmJsZS56aWJibGUxFTATBgNVBAMMDHppYmJsZXphYmJsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJxq0tjNGsiflNGOfogKchEs4sVm0p2efNb9OU2yLeI7La03Ez6WAdYQFvcwCgQpGBV5QEVjGhx93cPfMREs9lhma4y0nc/rHoe6nuHjqFBDhc+ruf6zdt5ZT3bGZYC08Cxu+AEmO80w4YMScV0dwL5mhw0FRu0v4SEAE2FZA8BiiYzLzaa1QWeK/AuwwqMNVKNUEdD87GVcF6I/7CRlwjh2G5ZoDiyXGmZCPhev0KYMcLnms89YWrFpH7VNXl3mLAus6YOgD4mGcEa2VvdsGjq2iSwWl6bgQ+qeyV/GUo4Jgt6x4pFRyCm2vtJIubhJe0LK4nTxZ5bkjDNlYjHNixUCAwEAAaMvMC0wKwYDVR0RBCQwIoINemFiYmxlLnppYmJsZYIRd3d3LnphYmJsZS56aWJibGUwDQYJKoZIhvcNAQELBQADggEBAI6zcyGramC42rwnp3SwJNnmaasmU1opsduQHdMfqyHoJwcOPymgcHYKJwUT5Q/m8zv5xx6MSSmBXTQWAfikX2D4T365vLvmj5mKX//bNc+4IPFSmMlpqYbGm8ptxNU6dtFIzMhI8+AlpEcJpJLtGSb/anVKp7JM/jadiRSohhVBuqf646KWgy56yNewvMEDoZYYBA9Kd2VgTXFsbo8Ol0DWcHYropTqVaftNSZ7YU+ZprigoBN8r8KMR74c/s6TaPnrBKJ817J6ao4s6/5hzmACEDtHYZgLku+cXbT6ttNVgba7wsLawBTjT60OMwQbLCQw/SvxKHEeVVYXp5lqO28=\n-----END CERTIFICATE-----", "file_permissions": [ { "execute": false, "file_name": "input-0", "read": false, "write": true - }, + } + ], + "id": 1 + }, + { + "certificate": "-----BEGIN CERTIFICATE-----\nMIIDzTCCArWgAwIBAgIUAfkSQsCPYNiqEjqpPU1lndub06UwDQYJKoZIhvcNAQELBQAwgYcxCzAJBgNVBAYTAk14MREwDwYDVQQIDAhWZXJhY3J1ejERMA8GA1UEBwwIVmVyYWNydXoxFjAUBgNVBAoMDVppYmJsZSBaYWJibGUxIzAhBgkqhkiG9w0BCQEWFHppYmJsZUB6YWJibGUuemliYmxlMRUwEwYDVQQDDAx6aWJibGV6YWJibGUwHhcNMjEwMzA4MTc0OTMzWhcNMzEwMzA2MTc0OTMzWjCBhzELMAkGA1UEBhMCTXgxETAPBgNVBAgMCFZlcmFjcnV6MREwDwYDVQQHDAhWZXJhY3J1ejEWMBQGA1UECgwNWmliYmxlIFphYmJsZTEjMCEGCSqGSIb3DQEJARYUemliYmxlQHphYmJsZS56aWJibGUxFTATBgNVBAMMDHppYmJsZXphYmJsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKcYfVJGNtUwIM0bi72QRDtKaUjExy+dkqyAxnyjK+pKMh1/RSRxmz2abyF3DQDe8n4eL4hwhVG0LC9z7n4DjRDbMFLfltZQucweLrh+O3uP4ZHCx5/gN0Iqryx+F/qOtN5MzyvyyZdzIQHKxuP4e2ovgap4c1Bp/foW/tH+E26VUirU3wM4zihaN7+hJNR5IOz53Un/OblqCPthGfn9GEoqy9DQPt22EOJgkR5S0MXgrrohL7nRAPwJ5/Ev472JVYPv6uvFNPzcZ235P0aKoGSGj2idPt8Nr/L648FmeI6XtBhXgtbrRv02xzSvChVFibDOwA4Yra7PqkphmJeNehkCAwEAAaMvMC0wKwYDVR0RBCQwIoINemFiYmxlLnppYmJsZYIRd3d3LnphYmJsZS56aWJibGUwDQYJKoZIhvcNAQELBQADggEBAKAQwG0fDYIyQe00tN5wPa6C4EIlAGzXaPTjmBEbQ4KcCexh25OZh1RM6klHQ2v2gF/iLGp+D2DscBTSBCeFKpgGfkSS3I2WBBueoMR5YVpOnpTwjQehupnFe941EMZDM+lzCX+dJPRDaRsZG4iNloKWrL/g59uRGLYJk2sK4CRz1oHIdO+WJvHpJSWsdCbixVUWYNUHrKAJ4Ma8th017ViiIm2vhvhwzTB7xLw6ZGQdMrY6yRnFQe2K4ZBuD4MHY4bAUBYPnUGkkood+SYMdKuLQhHs18HdVzKK/h08qqdQZMTZsEve2cJ6NMAmxYf2/n/olPhkX13lipMJfCtWtnQ=\n-----END CERTIFICATE-----", + "file_permissions": [ { "execute": false, - "file_name": "output", - "read": true, - "write": false - }, + "file_name": "input-1", + "read": false, + "write": true + } + ], + "id": 2 + }, + { + "certificate": "-----BEGIN CERTIFICATE-----\nMIIDzTCCArWgAwIBAgIUawMQl9xrI35fLUn+CrnLGfMhS/EwDQYJKoZIhvcNAQELBQAwgYcxCzAJBgNVBAYTAk14MREwDwYDVQQIDAhWZXJhY3J1ejERMA8GA1UEBwwIVmVyYWNydXoxFjAUBgNVBAoMDVppYmJsZSBaYWJibGUxIzAhBgkqhkiG9w0BCQEWFHppYmJsZUB6YWJibGUuemliYmxlMRUwEwYDVQQDDAx6aWJibGV6YWJibGUwHhcNMjEwMzA4MTc0OTQwWhcNMzEwMzA2MTc0OTQwWjCBhzELMAkGA1UEBhMCTXgxETAPBgNVBAgMCFZlcmFjcnV6MREwDwYDVQQHDAhWZXJhY3J1ejEWMBQGA1UECgwNWmliYmxlIFphYmJsZTEjMCEGCSqGSIb3DQEJARYUemliYmxlQHphYmJsZS56aWJibGUxFTATBgNVBAMMDHppYmJsZXphYmJsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANEyHoMUwENhSf3rjrlK16MDMI0RAicf+E4vfcGQ1e8Cn4wO8SADAIdHyMAQHTBoFUbR+yuVUcqvh1PaTPVKH5viAiOC1rQHTz7kK+LxBPYnwYvLYGxuZTYno1Cdc1y8l1ka2jOhGTJaHJdgA0QeMkKwp+8QOqlk2jBtlnsmt8FLQi0xEsZNWnx8dS/mfBw4OcGVeSPFtbtvE2JsdnXMII53+KCvZgW4dFQNSXGNzYjkyz/iOE0q71/OsDmm1AOlWv1SubfxJep/MYCAyxxHBLX3UW0eUxG1h/B31iOlJJ+5bgNhATbSDRjKBRQ6Og7/AbgFInSAj4yk7d6MxoQ7In8CAwEAAaMvMC0wKwYDVR0RBCQwIoINemFiYmxlLnppYmJsZYIRd3d3LnphYmJsZS56aWJibGUwDQYJKoZIhvcNAQELBQADggEBAEZ6tDksqUnPO9Pd+YiAya/8XkHm0fM8CkGYqfaUDv8UP5NVMNoxaAYWsHjWxlc6bDfjorL7w+45Yv7/DZUMshvTm/ZR0UbwqO9nG0K6UlevJHiVqTGIwFCID2WU+NxyaUS2WRACgpE01cFzhX1Lquehx5YWS157Z19Jsban2zdwjb2tiTExUSxXbuuU5HJgwOcUKGNIwrv37phcp5vK8qyk7sdM0nRFoZCVJCPkBtf3mos+KKss/mw8pbo+ShajEAMJ0YN/8v/cWZOm7DExwicBxPADdpI+/30VhVok11lYJkiKzIzyV5+CTtAiQq2QSX8a5ghQQUoONr8n8Aq+ufc=\n-----END CERTIFICATE-----", + "file_permissions": [ { - "execute": true, - "file_name": "linear-regression.wasm", + "execute": false, + "file_name": "input-2", "read": false, "write": true } ], - "id": 0 + "id": 3 + }, + { + "certificate": "-----BEGIN CERTIFICATE-----\nMIIDzTCCArWgAwIBAgIUOYkZBQ0q4Lnjo47Zg8jgzCroUBgwDQYJKoZIhvcNAQELBQAwgYcxCzAJBgNVBAYTAk14MREwDwYDVQQIDAhWZXJhY3J1ejERMA8GA1UEBwwIVmVyYWNydXoxFjAUBgNVBAoMDVppYmJsZSBaYWJibGUxIzAhBgkqhkiG9w0BCQEWFHppYmJsZUB6YWJibGUuemliYmxlMRUwEwYDVQQDDAx6aWJibGV6YWJibGUwHhcNMjEwMzA4MTc0OTQ2WhcNMzEwMzA2MTc0OTQ2WjCBhzELMAkGA1UEBhMCTXgxETAPBgNVBAgMCFZlcmFjcnV6MREwDwYDVQQHDAhWZXJhY3J1ejEWMBQGA1UECgwNWmliYmxlIFphYmJsZTEjMCEGCSqGSIb3DQEJARYUemliYmxlQHphYmJsZS56aWJibGUxFTATBgNVBAMMDHppYmJsZXphYmJsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKfQzlabOlIxu7if9kkFShJ5bvOdKzv+0w56v2zx9xP7oDG6CTxYIso1sPM2P+uKsZxZQg6DRY+L8xBSExc5QRIZiuLRsmdEqlxNJPgUDMKl5LjffA0hTBNQZ9RTYh1Y5fXV8/IBixEOsjock94LbMUxgG7WTbYaOMmenYbCq1ovtdzqYuGTqNahsHywFvOirKpoq5PVAcdF3Nf7bOwULl06aDPDqGTznRoj7USUgCaNFOOegxifDhPxaPK9f7uACmfSjtmoGtrmKxO3vCW3ugpCeP4CVLwrbh4dTogZ4JjlsslMEIx83synqq31bs2LBR+84aAQxlzRH4a6fjzy2V0CAwEAAaMvMC0wKwYDVR0RBCQwIoINemFiYmxlLnppYmJsZYIRd3d3LnphYmJsZS56aWJibGUwDQYJKoZIhvcNAQELBQADggEBAGcloZiuTznfYx819DDjiysGD53QLOKfxD9cZ0puONxwDNeQtkhmvrM4nOLtVT+lgaDx20yZ/rhT1IXGbmupW0M8cu9Z0VTZldC6qBEvL+NaoFczu4fm8GJbghDfQ+5KHtVdXt5YcyJuk1SQADlv34TsbVzcjDiNHBFAA1WCk19KDCjY6jwjasY96t8VG8pFX2Z9C+RnmoaYqDTiR4SscHqWJ2F+ueM64H1NX254GOZMu3Ijnvf40DZ3ikwCg6cIr9VPCAx+WMJONwxabjZ9z1srVBL6Ugnd4saaXHjxcbr+lQFqTxK1p7ohmZ14MBnD95ayyTF4aCuNNvb5RRITifM=\n-----END CERTIFICATE-----", + "file_permissions": [ + { + "execute": false, + "file_name": "output", + "read": true, + "write": false + } + ], + "id": 4 } ], "mexico_city_hash_nitro": null, @@ -47,6 +83,18 @@ "read": true, "write": false }, + { + "execute": false, + "file_name": "input-1", + "read": true, + "write": false + }, + { + "execute": false, + "file_name": "input-2", + "read": true, + "write": false + }, { "execute": false, "file_name": "output", @@ -55,10 +103,10 @@ } ], "id": 0, - "pi_hash": "c108407fdb6ffd5583de07e3eb59495c601fff0e45b93b840a2213a6448e1b38", - "program_file_name": "linear-regression.wasm" + "pi_hash": "a6f4a34c3dbee07d002e32f5647ed196a27613ac614be279da9ca5ef014722de", + "program_file_name": "example-binary.wasm" } ], - "proxy_attestation_server_url": "127.0.0.1:3010", - "sinaloa_url": "127.0.0.1:3012" -} \ No newline at end of file + "proxy_attestation_server_url": "172.17.0.2:3010", + "sinaloa_url": "172.17.0.2:3017" +} diff --git a/transport_protocol.options b/transport_protocol.options index abeb35afd..0e7fc3845 100644 --- a/transport_protocol.options +++ b/transport_protocol.options @@ -1,3 +1,6 @@ # Options for internal representation of protobufs -RequestProxyPsaAttestationToken.challenge max_size:32 fixed_length:true +# use smaller prefix for whole proto +transport_protocol.proto package:"Tp" +# efficiency tweaks to C representation +Tp.RequestProxyPsaAttestationToken.challenge max_size:32 fixed_length:true diff --git a/transport_protocol.proto b/transport_protocol.proto index 98ddef34f..e4cf9d940 100644 --- a/transport_protocol.proto +++ b/transport_protocol.proto @@ -1,7 +1,230 @@ -// Protocol buffers for Veracruz's transport protocol messages +//! Protocol buffers for Veracruz transport protocol messages +//! +//! ## Authors +//! +//! The Veracruz Development Team. +//! +//! ## Licensing and copyright notice +//! +//! See the `LICENSE.markdown` file in the Veracruz root directory for +//! information on licensing and copyright. syntax = "proto3"; +package transport_protocol; + +message SgxEc256Public { + bytes gx = 1; + bytes gy = 2; +} +message SgxEc256Signature { + repeated uint32 x = 1; + repeated uint32 y = 2; +} + +message StartMsg { + string protocol = 1; + string firmware_version = 2; +} + +message SgxMsg1 { + SgxEc256Public g_a = 1; + bytes gid = 2; + int32 device_id = 3; +} + +message SgxMsg2 { + SgxEc256Public g_b = 1; + bytes spid = 2; + uint32 quote_type = 3; + uint32 kdf_id = 4; + SgxEc256Signature sign_gb_ga = 5; + bytes mac = 6; + uint32 sig_rl_size = 7; +} + +message SgxMsg3 { + bytes mac = 1; + SgxEc256Public g_a = 2; + bytes ps_sec_prop = 3; + int32 device_id = 4; +} + +message SgxAttributes { + uint64 flags = 1; + uint64 xfrm = 2; +} +message SgxReportBody { + bytes cpu_svn = 1; + uint32 misc_select = 2; + bytes reserve1 = 3; + bytes isv_ext_prod_id = 4; + SgxAttributes attributes = 5; + bytes mr_enclave = 6; + bytes reserved2 = 7; + bytes mr_signer = 8; + bytes reserved3 = 9; + bytes config_id = 10; + uint32 isv_prod_id = 11; + uint32 isv_svn = 12; + uint32 config_svn = 13; + bytes reserved4 = 14; + bytes isv_family_id = 15; + bytes report_data = 16; +} + +message SgxQuote { + uint32 version = 1; + uint32 sign_type = 2; + bytes epid_group_id = 3; + uint32 qe_svn = 4; + uint32 pce_svn = 5; + uint32 xeid = 6; + bytes basename = 7; + SgxReportBody report_body = 8; + uint32 signature_len = 9; +} + +message Program { + bytes code = 1; +} + +message Data { + uint32 package_id = 1; + bytes data = 2; +} + +message SgxAttestationInit { + bytes public_key = 1; + int32 device_id = 2; +} + +message SgxAttestationChallenge { + SgxMsg2 msg2 = 1; + bytes challenge = 2; +} + +message SgxAttestationTokens { + SgxMsg3 msg3 = 1; + SgxQuote msg3_quote = 2; + bytes msg3_sig = 3; + SgxQuote pubkey_quote = 4; + bytes pubkey_sig = 5; +} + +message PsaAttestationInit { + bytes challenge = 1; + int32 device_id = 2; +} + +enum ResponseStatus { + UNSET = 0; + SUCCESS = 1; + FAILED_INVALID_ROLE = 2; + FAILED_NOT_READY = 3; + FAILED_GENERIC = 4; + FAILED_VM_ERROR = 5; + FAILED_ERROR_CODE_RETURNED = 6; + FAILED_INVALID_REQUEST = 7; +} + +message Result { + bytes data = 1; +} + +message ErrorCode { + bytes error = 1; +} + +message PiHash { + bytes data = 1; +} + +message State { + bytes state = 1; +} + +message RequestPiHash { +} + +message RequestResult { +} message RequestProxyPsaAttestationToken { bytes challenge = 1; } + +message NativePsaAttestationToken { + bytes token = 1; + int32 device_id = 2; +} + +message ProxyPsaAttestationToken { + bytes token = 1; + bytes pubkey = 2; + int32 device_id = 3; +} +message RequestShutdown { +} + +message RequestNextRound { +} + +message RequestState { +} + +message RequestPolicyHash { +} + +message PolicyHash { + bytes data = 1; +} + + +message ProxyAttestationServerRequest { + oneof message_oneof { + StartMsg start_msg = 2; + NativePsaAttestationToken native_psa_attestation_token = 3; + ProxyPsaAttestationToken proxy_psa_attestation_token = 4; + SgxMsg1 msg1 = 5; + SgxAttestationTokens sgx_attestation_tokens = 6; + } + uint32 context = 1; +} + +message ProxyAttestationServerResponse { + oneof message_oneof { + PsaAttestationInit psa_attestation_init = 2; + SgxAttestationInit sgx_attestation_init = 3; + SgxAttestationChallenge sgx_attestation_challenge = 4; + } + uint32 context = 1; +} + +message RuntimeManagerRequest { + oneof message_oneof { + Data data = 2; + Program program = 3; + RequestPiHash request_pi_hash = 4; + RequestResult request_result = 5; + RequestProxyPsaAttestationToken request_proxy_psa_attestation_token = 6; + RequestState request_state = 7; + RequestShutdown request_shutdown = 8; + PsaAttestationInit psa_attestation_init = 9; + RequestPolicyHash request_policy_hash = 10; + Data stream = 11; + RequestNextRound request_next_round = 12; + } + uint32 context = 1; +} + +message RuntimeManagerResponse { + ResponseStatus status = 1; + oneof message_oneof { + Result result = 4; + PiHash pi_hash = 5; + ErrorCode error = 6; + State state = 10; + PolicyHash policy_hash = 11; + } + uint32 context = 8; +} From df9327d9be344ef26c59dc473a543f02f3b8d493 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 16 Apr 2021 14:08:20 -0500 Subject: [PATCH 09/56] Got mvp attestation working --- main.c | 64 ++++++++++++++++++++++++++------------------- policy_to_header.py | 29 ++++++++++++++++++-- 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/main.c b/main.c index 53a75726b..36bfd283c 100644 --- a/main.c +++ b/main.c @@ -26,6 +26,8 @@ #include "nanopb/pb_decode.h" #include "transport_protocol.pb.h" +#include "policy.h" + // TODO log? //#include //LOG_MODULE_REGISTER(http, CONFIG_FOO_LOG_LEVEL); @@ -70,26 +72,9 @@ //// HTTP stuff //// -// TODO use generated policy.h -#ifndef VERACRUZ_SERVER_HOST -#define VERACRUZ_SERVER_HOST "172.17.0.2" -#endif - -#ifndef VERACRUZ_SERVER_PORT -#define VERACRUZ_SERVER_PORT 3017 -#endif - // TODO common prefix for veracruz client functions? -#ifndef VERACRUZ_TIMEOUT -#define VERACRUZ_TIMEOUT 3000 -#endif - -#ifndef PROXY_ATTESTATION_SERVER_HOST -#define PROXY_ATTESTATION_SERVER_HOST "172.17.0.2" -#endif - -#ifndef PROXY_ATTESTATION_SERVER_PORT -#define PROXY_ATTESTATION_SERVER_PORT 3010 +#ifndef HTTP_TIMEOUT +#define HTTP_TIMEOUT 3000 #endif struct http_get_state { @@ -184,7 +169,7 @@ ssize_t http_get( .recv_buf_len = pbuf_len, }; - int err = http_client_req(sock, &req, VERACRUZ_TIMEOUT, &state); + int err = http_client_req(sock, &req, HTTP_TIMEOUT, &state); if (err < 0) { printf("http req failed (%d)\n", err); free(pbuf); @@ -260,7 +245,7 @@ ssize_t http_post( .recv_buf_len = pbuf_len, }; - int err = http_client_req(sock, &req, VERACRUZ_TIMEOUT, &state); + int err = http_client_req(sock, &req, HTTP_TIMEOUT, &state); if (err < 0) { printf("http req failed (%d)\n", err); free(pbuf); @@ -441,6 +426,13 @@ void xxd(const void *pbuf, size_t len) { } } +void hex(const void *pbuf, size_t len) { + const uint8_t *buf = pbuf; + for (int i = 0; i < len; i++) { + printf("%02x", buf[i]); + } +} + void main(void) { // get random challenge @@ -537,7 +529,7 @@ void main(void) } printf("attest: PAT decoded response:\n"); - xxd(buffer, res); + xxd(buffer, verif_len); if (verif_len < 131) { printf("pat response too small\n"); @@ -560,12 +552,30 @@ void main(void) qemu_exit(); } - // TODO check these against policy - printf("enclave hash:\n"); - xxd(&buffer[47], 32); - printf("enclave cert hash:\n"); - xxd(&buffer[86], 32); + // check that enclave hash matches policy + if (memcmp(&buffer[47], RUNTIME_MANAGER_HASH, sizeof(RUNTIME_MANAGER_HASH)) != 0) { + printf("enclave hash mismatch\n"); + printf("expected: "); + for (int i = 0; i < sizeof(RUNTIME_MANAGER_HASH); i++) { + printf("%02x", RUNTIME_MANAGER_HASH[i]); + } + printf("\n"); + printf("recieved: "); + for (int i = 0; i < 32; i++) { + printf("%02x", buffer[8+i]); + } + printf("\n"); + qemu_exit(); + } + + // recieved values printf("enclave name: %.*s\n", 7, &buffer[124]); + printf("enclave hash: "); + hex(&buffer[47], 32); + printf("\n"); + printf("enclave cert hash: "); + hex(&buffer[86], 32); + printf("\n"); // // for (int i = 0; i < res; i++) { diff --git a/policy_to_header.py b/policy_to_header.py index d76c1f542..a9d4cb453 100755 --- a/policy_to_header.py +++ b/policy_to_header.py @@ -2,6 +2,7 @@ import json import hashlib +import base64 def main(in_json, out_h, out_c): with open(in_json) as f: @@ -26,12 +27,28 @@ def writeln(s='', **args): f.writeln() f.writeln('#include ') f.writeln() - f.writeln('extern uint8_t _veracruz_policy_raw[];') - f.writeln() + f.writeln('// general policy things') f.writeln('#define VERACRUZ_POLICY_HASH "%(hash)s"', hash=policy_hash) + f.writeln('extern uint8_t _veracruz_policy_raw[];') f.writeln('#define VERACRUZ_POLICY_RAW _veracruz_policy_raw') f.writeln() + f.writeln('// various hashes') + # TODO choose between platforms? + f.writeln('extern uint8_t _runtime_manager_hash[%(size)d];', + size=len(policy['mexico_city_hash_sgx'])/2) + f.writeln('#define RUNTIME_MANAGER_HASH _runtime_manager_hash') + f.writeln() + f.writeln('// server info') + f.writeln('#define VERACRUZ_SERVER_HOST "%(host)s"', + host=policy['sinaloa_url'].split(':')[0]) + f.writeln('#define VERACRUZ_SERVER_PORT %(port)s', + port=policy['sinaloa_url'].split(':')[1]) + f.writeln('#define PROXY_ATTESTATION_SERVER_HOST "%(host)s"', + host=policy['proxy_attestation_server_url'].split(':')[0]) + f.writeln('#define PROXY_ATTESTATION_SERVER_PORT %(port)s', + port=policy['proxy_attestation_server_url'].split(':')[1]) + f.writeln() f.writeln('#endif') print('Generating %s for policy %s' % (out_c, policy_hash)) @@ -55,6 +72,14 @@ def writeln(s='', **args): raw=' '.join('0x%02x,' % ord(x) for x in policy_raw[i : min(i+8, len(policy_raw))])) f.writeln('};') + f.writeln() + f.writeln('uint8_t _runtime_manager_hash[] = {') + runtime_manager_hash = policy['mexico_city_hash_sgx'] + for i in range(0, len(runtime_manager_hash)//2, 8): + f.writeln(' %(hash)s', + hash=' '.join('0x%02x,' % int(runtime_manager_hash[2*j:2*j+2], 16) + for j in range(i, min(i+8, len(runtime_manager_hash)//2)))) + f.writeln('};') if __name__ == "__main__": import sys From 18d702191429ee550727282123fa7a09e7651024 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sat, 17 Apr 2021 15:49:54 -0500 Subject: [PATCH 10/56] Cleanup, organized into files --- base64.c | 132 ++++++++++++++ base64.h | 28 +++ http.c | 197 +++++++++++++++++++++ http.h | 38 ++++ main.c | 523 +------------------------------------------------------ qemu.h | 19 ++ xxd.c | 48 +++++ xxd.h | 18 ++ 8 files changed, 486 insertions(+), 517 deletions(-) create mode 100644 base64.c create mode 100644 base64.h create mode 100644 http.c create mode 100644 http.h create mode 100644 qemu.h create mode 100644 xxd.c create mode 100644 xxd.h diff --git a/base64.c b/base64.c new file mode 100644 index 000000000..0d3b0250e --- /dev/null +++ b/base64.c @@ -0,0 +1,132 @@ +/* + * base64 utilities + * + */ + +#include "base64.h" +#include +#include + +// compute size after encoding +size_t base64_encode_size(size_t in_len) { + size_t x = in_len; + if (in_len % 3 != 0) { + x += 3 - (in_len % 3); + } + + return 4*(x/3); +} + +static const char BASE64_ENCODE[] = ( + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"); + +// encode base64 +ssize_t base64_encode( + const uint8_t *in, size_t in_len, + char *out, size_t out_len) { + size_t e_len = base64_encode_size(in_len); + if (e_len+1 > out_len) { + return -EOVERFLOW; + } + out[e_len] = '\0'; + + for (size_t i=0, j=0; i < in_len; i += 3, j += 4) { + size_t v = in[i]; + v = i+1 < e_len ? (v << 8 | in[i+1]) : (v << 8); + v = i+2 < e_len ? (v << 8 | in[i+2]) : (v << 8); + + out[j] = BASE64_ENCODE[(v >> 18) & 0x3f]; + out[j+1] = BASE64_ENCODE[(v >> 12) & 0x3f]; + + if (i+1 < in_len) { + out[j+2] = BASE64_ENCODE[(v >> 6) & 0x3f]; + } else { + out[j+2] = '='; + } + + if (i+2 < in_len) { + out[j+3] = BASE64_ENCODE[v & 0x3f]; + } else { + out[j+3] = '='; + } + } + + return e_len; +} + +// compute size after decoding +size_t base64_decode_size(const char *in) { + size_t in_len = strlen(in); + + size_t x = 3*(in_len/4); + for (size_t i = 0; i < in_len && in[in_len-i-1] == '='; i++) { + x -= 1; + } + + return x; +} + +static const int8_t BASE64_DECODE[] = { + 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, + -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +}; + +static bool base64_isvalid(char c) { + if (c >= '0' && c <= '9') { + return true; + } else if (c >= 'A' && c <= 'Z') { + return true; + } else if (c >= 'a' && c <= 'z') { + return true; + } else if (c == '+' || c == '/' || c == '=') { + return true; + } else { + return false; + } +} + +// decode base64 +ssize_t base64_decode( + const char *in, + char *out, size_t out_len) { + size_t in_len = strlen(in); + if (in_len % 4 != 0) { + return -EINVAL; + } + + size_t d_len = base64_decode_size(in); + if (d_len > out_len) { + return -EOVERFLOW; + } + + for (size_t i = 0; i < in_len; i++) { + if (!base64_isvalid(in[i])) { + return -EILSEQ; + } + } + + for (size_t i=0, j=0; i < in_len; i += 4, j += 3) { + size_t v = BASE64_DECODE[in[i]-43]; + v = (v << 6) | BASE64_DECODE[in[i+1]-43]; + v = in[i+2] == '=' ? (v << 6) : ((v << 6) | BASE64_DECODE[in[i+2]-43]); + v = in[i+3] == '=' ? (v << 6) : ((v << 6) | BASE64_DECODE[in[i+3]-43]); + + out[j] = (v >> 16) & 0xff; + + if (in[i+2] != '=') { + out[j+1] = (v >> 8) & 0xff; + } + + if (in[i+3] != '=') { + out[j+2] = v & 0xff; + } + } + + return d_len; +} + diff --git a/base64.h b/base64.h new file mode 100644 index 000000000..d92fda92e --- /dev/null +++ b/base64.h @@ -0,0 +1,28 @@ +/* + * base64 utilities + * + */ + +#include +#include + +#ifndef BASE64_H +#define BASE64_H + +// compute size after encoding +size_t base64_encode_size(size_t in_len); + +// encode base64 +ssize_t base64_encode( + const uint8_t *in, size_t in_len, + char *out, size_t out_len); + +//compute size after decoding +size_t base64_decode_size(const char *in); + +// decode base64 +ssize_t base64_decode( + const char *in, + char *out, size_t out_len); + +#endif diff --git a/http.c b/http.c new file mode 100644 index 000000000..f403659f8 --- /dev/null +++ b/http.c @@ -0,0 +1,197 @@ +/* + * HTTP convenience functions, these just wrap the low-level HTTP API + * that Zephyr provides + * + */ + +#include "http.h" + +#include +#include + + +struct http_get_state { + uint8_t *buf; + size_t buf_len; + size_t pos; +}; + +static void http_get_cb( + struct http_response *rsp, + enum http_final_call final, + void *udata) { +// // TODO probably handle this? +// if (state != HTTP_DATA_MORE) { +// printf("http rsp state != HTTP_DATA_FINAL (%d), " +// "should handle this\n", state); +// } + + printf("rsp = %s (%d bytes)\n", rsp->http_status, rsp->data_len); + + struct http_get_state *state = udata; + uint8_t *start = (rsp->body_start) ? rsp->body_start : rsp->recv_buf; + size_t len = rsp->processed - state->pos; + + if (state->pos + len > state->buf_len) { + printf("http get buffer overflow! truncating (%d > %d)\n", + state->pos + len, state->buf_len); + + len = state->buf_len - state->pos; + } + + memcpy(&state->buf[state->pos], start, len); + state->pos += len; +} + +// HTTP GET operation +// TODO TLS? https_get? +ssize_t http_get( + const char *host, + uint16_t port, + const char *path, + // TODO headers? + uint8_t *buf, + size_t buf_len) { + // setup address, TODO use DNS? + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, host, &addr.sin_addr); + + // allocate packet buffer for managing http connection + void *pbuf = malloc(256); + size_t pbuf_len = 256; + if (!pbuf) { + printf("http malloc failed (-ENOMEM)\n"); + return -ENOMEM; + } + + // create socket and connect to server + // TODO IPv6? can use net_sin(addr)->sin_family here + // DNS? Take in a string more useful API? + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + printf("http socket open failed (%d)\n", -errno); + free(pbuf); + return -errno; + } + + int res = connect(sock, (struct sockaddr*)&addr, sizeof(addr)); + if (res < 0) { + printf("http connect failed (%d)\n", -errno); + free(pbuf); + close(sock); + return -errno; + } + + // state for filling in buffer + struct http_get_state state = { + .buf = buf, + .buf_len = buf_len, + .pos = 0, + }; + + // perform client request, Zephyr handles most of this for us + struct http_request req = { + .method = HTTP_GET, + .url = path, + .host = host, + .protocol = "HTTP/1.1", + .response = http_get_cb, + .recv_buf = pbuf, + .recv_buf_len = pbuf_len, + }; + + int err = http_client_req(sock, &req, HTTP_TIMEOUT, &state); + if (err < 0) { + printf("http req failed (%d)\n", err); + free(pbuf); + close(sock); + return err; + } + + // done, close + free(pbuf); + close(sock); + return state.pos; +} + +// HTTP POST operation +// TODO deduplicate? +ssize_t http_post( + const char *host, + uint16_t port, + const char *path, + // TODO headers? + const uint8_t *payload_buf, + size_t payload_buf_len, + uint8_t *resp_buf, + size_t resp_buf_len) { + // setup address, TODO use DNS? + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, host, &addr.sin_addr); + + // allocate packet buffer for managing http connection + void *pbuf = malloc(256); + size_t pbuf_len = 256; + if (!pbuf) { + printf("http malloc failed (-ENOMEM)\n"); + return -ENOMEM; + } + + // create socket and connect to server + // TODO IPv6? can use net_sin(addr)->sin_family here + // DNS? Take in a string more useful API? + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + printf("http socket open failed (%d)\n", -errno); + free(pbuf); + return -errno; + } + + int res = connect(sock, (struct sockaddr*)&addr, sizeof(addr)); + if (res < 0) { + printf("http connect failed (%d)\n", -errno); + free(pbuf); + close(sock); + return -errno; + } + + // state for filling in buffer + struct http_get_state state = { + .buf = resp_buf, + .buf_len = resp_buf_len, + .pos = 0, + }; + + // perform client request, Zephyr handles most of this for us + struct http_request req = { + .method = HTTP_POST, + .url = path, + .host = host, + .protocol = "HTTP/1.1", + .response = http_get_cb, + .payload = payload_buf, + .payload_len = payload_buf_len, + .recv_buf = pbuf, + .recv_buf_len = pbuf_len, + }; + + int err = http_client_req(sock, &req, HTTP_TIMEOUT, &state); + if (err < 0) { + printf("http req failed (%d)\n", err); + free(pbuf); + close(sock); + return err; + } + + // done, close + free(pbuf); + close(sock); + return state.pos; +} + diff --git a/http.h b/http.h new file mode 100644 index 000000000..f058168fc --- /dev/null +++ b/http.h @@ -0,0 +1,38 @@ +/* + * HTTP convenience functions, these just wrap the low-level HTTP API + * that Zephyr provides + * + */ + +#include +#include + +#ifndef HTTP_H +#define HTTP_H + +#ifndef HTTP_TIMEOUT +#define HTTP_TIMEOUT 3000 +#endif + +// HTTP GET operation +// TODO TLS? https_get? +ssize_t http_get( + const char *host, + uint16_t port, + const char *path, + // TODO headers? + uint8_t *buf, + size_t buf_len); + +// HTTP POST operation +ssize_t http_post( + const char *host, + uint16_t port, + const char *path, + // TODO headers? + const uint8_t *payload_buf, + size_t payload_buf_len, + uint8_t *resp_buf, + size_t resp_buf_len); + +#endif diff --git a/main.c b/main.c index 36bfd283c..52b290656 100644 --- a/main.c +++ b/main.c @@ -6,18 +6,6 @@ #include #include -//#if !defined(__ZEPHYR__) || defined(CONFIG_POSIX_API) -// -//#include -//#include -//#include -//#include -//#include -// -//#else - -#include -#include #include #include @@ -27,356 +15,10 @@ #include "transport_protocol.pb.h" #include "policy.h" - -// TODO log? -//#include -//LOG_MODULE_REGISTER(http, CONFIG_FOO_LOG_LEVEL); - -//#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) -//#include -//#include "ca_certificate.h" -//#endif -// -//#endif - -///* HTTP server to connect to */ -////#define HTTP_HOST "google.com" -////#define HTTP_HOST "172.217.1.228" -//#define HTTP_HOST "172.17.0.2" -///* Port to connect to, as string */ -//#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) -//#define VERACRUZ_SERVER_PORT "443" -//#else -////#define VERACRUZ_SERVER_PORT "80" -//#define VERACRUZ_SERVER_PORT "3017" -//#endif -///* HTTP path to request */ -//#define HTTP_PATH "/" -// -// -//#define SSTRLEN(s) (sizeof(s) - 1) -//#define CHECK(r) { if (r == -1) { printf("Error: " #r "\n"); exit(1); } } -// -//#define REQUEST "GET " HTTP_PATH " HTTP/1.0\r\nHost: " HTTP_HOST "\r\n\r\n" -// -//static char response[1024]; -// -//void dump_addrinfo(const struct addrinfo *ai) -//{ -// printf("addrinfo @%p: ai_family=%d, ai_socktype=%d, ai_protocol=%d, " -// "sa_family=%d, sin_port=%x\n", -// ai, ai->ai_family, ai->ai_socktype, ai->ai_protocol, -// ai->ai_addr->sa_family, -// ((struct sockaddr_in *)ai->ai_addr)->sin_port); -//} - -//// HTTP stuff //// - -// TODO common prefix for veracruz client functions? -#ifndef HTTP_TIMEOUT -#define HTTP_TIMEOUT 3000 -#endif - -struct http_get_state { - uint8_t *buf; - size_t buf_len; - size_t pos; -}; - -static void http_get_cb( - struct http_response *rsp, - enum http_final_call final, - void *udata) { -// // TODO probably handle this? -// if (state != HTTP_DATA_MORE) { -// printf("http rsp state != HTTP_DATA_FINAL (%d), " -// "should handle this\n", state); -// } - - printf("rsp = %s (%d bytes)\n", rsp->http_status, rsp->data_len); - - struct http_get_state *state = udata; - uint8_t *start = (rsp->body_start) ? rsp->body_start : rsp->recv_buf; - size_t len = rsp->processed - state->pos; - - if (state->pos + len > state->buf_len) { - printf("http get buffer overflow! truncating (%d > %d)\n", - state->pos + len, state->buf_len); - - len = state->buf_len - state->pos; - } - - memcpy(&state->buf[state->pos], start, len); - state->pos += len; -} - -// TODO TLS? https_get? -ssize_t http_get( - const char *host, - uint16_t port, - const char *path, - // TODO headers? - uint8_t *buf, - size_t buf_len) { - // setup address, TODO use DNS? - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - inet_pton(AF_INET, host, &addr.sin_addr); - - // allocate packet buffer for managing http connection - void *pbuf = malloc(256); - size_t pbuf_len = 256; - if (!pbuf) { - printf("http malloc failed (-ENOMEM)\n"); - return -ENOMEM; - } - - // create socket and connect to server - // TODO IPv6? can use net_sin(addr)->sin_family here - // DNS? Take in a string more useful API? - int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (sock < 0) { - printf("http socket open failed (%d)\n", -errno); - free(pbuf); - return -errno; - } - - int res = connect(sock, (struct sockaddr*)&addr, sizeof(addr)); - if (res < 0) { - printf("http connect failed (%d)\n", -errno); - free(pbuf); - close(sock); - return -errno; - } - - // state for filling in buffer - struct http_get_state state = { - .buf = buf, - .buf_len = buf_len, - .pos = 0, - }; - - // perform client request, Zephyr handles most of this for us - struct http_request req = { - .method = HTTP_GET, - .url = path, - .host = host, - .protocol = "HTTP/1.1", - .response = http_get_cb, - .recv_buf = pbuf, - .recv_buf_len = pbuf_len, - }; - - int err = http_client_req(sock, &req, HTTP_TIMEOUT, &state); - if (err < 0) { - printf("http req failed (%d)\n", err); - free(pbuf); - close(sock); - return err; - } - - // done, close - free(pbuf); - close(sock); - return state.pos; -} - -ssize_t http_post( - const char *host, - uint16_t port, - const char *path, - // TODO headers? - const uint8_t *payload_buf, - size_t payload_buf_len, - uint8_t *resp_buf, - size_t resp_buf_len) { - // setup address, TODO use DNS? - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - inet_pton(AF_INET, host, &addr.sin_addr); - - // allocate packet buffer for managing http connection - void *pbuf = malloc(256); - size_t pbuf_len = 256; - if (!pbuf) { - printf("http malloc failed (-ENOMEM)\n"); - return -ENOMEM; - } - - // create socket and connect to server - // TODO IPv6? can use net_sin(addr)->sin_family here - // DNS? Take in a string more useful API? - int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (sock < 0) { - printf("http socket open failed (%d)\n", -errno); - free(pbuf); - return -errno; - } - - int res = connect(sock, (struct sockaddr*)&addr, sizeof(addr)); - if (res < 0) { - printf("http connect failed (%d)\n", -errno); - free(pbuf); - close(sock); - return -errno; - } - - // state for filling in buffer - struct http_get_state state = { - .buf = resp_buf, - .buf_len = resp_buf_len, - .pos = 0, - }; - - // perform client request, Zephyr handles most of this for us - struct http_request req = { - .method = HTTP_POST, - .url = path, - .host = host, - .protocol = "HTTP/1.1", - .response = http_get_cb, - .payload = payload_buf, - .payload_len = payload_buf_len, - .recv_buf = pbuf, - .recv_buf_len = pbuf_len, - }; - - int err = http_client_req(sock, &req, HTTP_TIMEOUT, &state); - if (err < 0) { - printf("http req failed (%d)\n", err); - free(pbuf); - close(sock); - return err; - } - - // done, close - free(pbuf); - close(sock); - return state.pos; -} - -//// base64 //// - -size_t base64_encode_size(size_t in_len) { - size_t x = in_len; - if (in_len % 3 != 0) { - x += 3 - (in_len % 3); - } - - return 4*(x/3); -} - -static const char BASE64_ENCODE[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -ssize_t base64_encode( - const uint8_t *in, size_t in_len, - char *out, size_t out_len) { - size_t e_len = base64_encode_size(in_len); - if (e_len+1 > out_len) { - return -EOVERFLOW; - } - out[e_len] = '\0'; - - for (size_t i=0, j=0; i < in_len; i += 3, j += 4) { - size_t v = in[i]; - v = i+1 < e_len ? (v << 8 | in[i+1]) : (v << 8); - v = i+2 < e_len ? (v << 8 | in[i+2]) : (v << 8); - - out[j] = BASE64_ENCODE[(v >> 18) & 0x3f]; - out[j+1] = BASE64_ENCODE[(v >> 12) & 0x3f]; - - if (i+1 < in_len) { - out[j+2] = BASE64_ENCODE[(v >> 6) & 0x3f]; - } else { - out[j+2] = '='; - } - - if (i+2 < in_len) { - out[j+3] = BASE64_ENCODE[v & 0x3f]; - } else { - out[j+3] = '='; - } - } - - return e_len; -} - -size_t base64_decode_size(const char *in) { - size_t in_len = strlen(in); - - size_t x = 3*(in_len/4); - for (size_t i = 0; i < in_len && in[in_len-i-1] == '='; i++) { - x -= 1; - } - - return x; -} - -static const int8_t BASE64_DECODE[] = { - 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, - -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 -}; - -static bool base64_isvalid(char c) { - if (c >= '0' && c <= '9') { - return true; - } else if (c >= 'A' && c <= 'Z') { - return true; - } else if (c >= 'a' && c <= 'z') { - return true; - } else if (c == '+' || c == '/' || c == '=') { - return true; - } else { - return false; - } -} - -ssize_t base64_decode( - const char *in, - char *out, size_t out_len) { - size_t in_len = strlen(in); - if (in_len % 4 != 0) { - return -EINVAL; - } - - size_t d_len = base64_decode_size(in); - if (d_len > out_len) { - return -EOVERFLOW; - } - - for (size_t i = 0; i < in_len; i++) { - if (!base64_isvalid(in[i])) { - return -EILSEQ; - } - } - - for (size_t i=0, j=0; i < in_len; i += 4, j += 3) { - size_t v = BASE64_DECODE[in[i]-43]; - v = (v << 6) | BASE64_DECODE[in[i+1]-43]; - v = in[i+2] == '=' ? (v << 6) : ((v << 6) | BASE64_DECODE[in[i+2]-43]); - v = in[i+3] == '=' ? (v << 6) : ((v << 6) | BASE64_DECODE[in[i+3]-43]); - - out[j] = (v >> 16) & 0xff; - - if (in[i+2] != '=') { - out[j+1] = (v >> 8) & 0xff; - } - - if (in[i+3] != '=') { - out[j+2] = v & 0xff; - } - } - - return d_len; -} - - +#include "xxd.h" +#include "base64.h" +#include "http.h" +#include "qemu.h" //// application logic //// @@ -384,57 +26,7 @@ ssize_t base64_decode( uint8_t buffer[10*1024]; uint8_t buffer2[10*1024]; -// hack to exit QEMU -//__attribute__((noreturn)) -void qemu_exit(void) { - __asm__ volatile ( - "mov r0, #0x18 \n\t" - "ldr r1, =#0x20026 \n\t" - "bkpt #0xab \n\t" - ); -} - -void xxd(const void *pbuf, size_t len) { - const uint8_t *buf = pbuf; - - for (int i = 0; i < len; i += 16) { - printf("%08x: ", i); - - for (int j = 0; j < 16; j++) { - if (i+j < len) { - printf("%02x ", buf[i+j]); - } else { - printf(" "); - } - } - - printf(" "); - - for (int j = 0; j < 16 && i+j < len; j++) { - if (i+j < len) { - if (buf[i+j] >= ' ' && buf[i+j] <= '~') { - printf("%c", buf[i+j]); - } else { - printf("."); - } - } else { - printf(" "); - } - } - - printf("\n"); - } -} - -void hex(const void *pbuf, size_t len) { - const uint8_t *buf = pbuf; - for (int i = 0; i < len; i++) { - printf("%02x", buf[i]); - } -} - -void main(void) -{ +void main(void) { // get random challenge // TODO Zephyr notes this is not cryptographically secure, is that an // issue? This will be an area to explore @@ -443,9 +35,7 @@ void main(void) // TODO log? can we incrementally log? printf("attest: challenge: "); - for (int i = 0; i < sizeof(challenge); i++) { - printf("%02x", challenge[i]); - } + hex(challenge, sizeof(challenge)); printf("\n"); // construct attestation token request @@ -577,106 +167,5 @@ void main(void) hex(&buffer[86], 32); printf("\n"); -// -// for (int i = 0; i < res; i++) { -// if (buffer[i] != '\r') { -// printf("%c", buffer[i]); -// } -// } -// printf("\n"); - -// -// while (1) { -// int len = recv(sock, response, sizeof(response) - 1, 0); -// -// if (len < 0) { -// printf("Error reading response\n"); -// return; -// } -// -// if (len == 0) { -// break; -// } -// -// response[len] = 0; -// printf("%s", response); -// } -// -// printf("\n"); -// -// -// static struct addrinfo hints; -// struct addrinfo *res; -// int st, sock; -// -//#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) -// tls_credential_add(CA_CERTIFICATE_TAG, TLS_CREDENTIAL_CA_CERTIFICATE, -// ca_certificate, sizeof(ca_certificate)); -//#endif -// -// printf("Preparing HTTP GET request for http://" HTTP_HOST -// ":" HTTP_PORT HTTP_PATH "\n"); -// -// hints.ai_family = AF_INET; -// hints.ai_socktype = SOCK_STREAM; -// st = getaddrinfo(HTTP_HOST, HTTP_PORT, &hints, &res); -// printf("getaddrinfo status: %d\n", st); -// -// if (st != 0) { -// printf("Unable to resolve address, quitting\n"); -// return; -// } -// -//#if 0 -// for (; res; res = res->ai_next) { -// dump_addrinfo(res); -// } -//#endif -// -// dump_addrinfo(res); -// -//#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) -// sock = socket(res->ai_family, res->ai_socktype, IPPROTO_TLS_1_2); -//#else -// sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); -//#endif -// CHECK(sock); -// printf("sock = %d\n", sock); -// -//#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) -// sec_tag_t sec_tag_opt[] = { -// CA_CERTIFICATE_TAG, -// }; -// CHECK(setsockopt(sock, SOL_TLS, TLS_SEC_TAG_LIST, -// sec_tag_opt, sizeof(sec_tag_opt))); -// -// CHECK(setsockopt(sock, SOL_TLS, TLS_HOSTNAME, -// HTTP_HOST, sizeof(HTTP_HOST))) -//#endif -// -// CHECK(connect(sock, res->ai_addr, res->ai_addrlen)); -// CHECK(send(sock, REQUEST, SSTRLEN(REQUEST), 0)); -// -// printf("Response:\n\n"); -// -// while (1) { -// int len = recv(sock, response, sizeof(response) - 1, 0); -// -// if (len < 0) { -// printf("Error reading response\n"); -// return; -// } -// -// if (len == 0) { -// break; -// } -// -// response[len] = 0; -// printf("%s", response); -// } -// -// printf("\n"); -// -// (void)close(sock); qemu_exit(); } diff --git a/qemu.h b/qemu.h new file mode 100644 index 000000000..a1d620207 --- /dev/null +++ b/qemu.h @@ -0,0 +1,19 @@ +/* + * QEMU utilities + */ + +#ifndef QEMU_H +#define QEMU_H + +// hack to exit QEMU +// TODO can we integrate into Zephyr somehow? +__attribute__((noreturn)) +static inline void qemu_exit(void) { + __asm__ volatile ( + "mov r0, #0x18 \n\t" + "ldr r1, =#0x20026 \n\t" + "bkpt #0xab \n\t" + ); +} + +#endif diff --git a/xxd.c b/xxd.c new file mode 100644 index 000000000..b1fff46b6 --- /dev/null +++ b/xxd.c @@ -0,0 +1,48 @@ +/* + * Hex dump utilities + * + */ + +#include "xxd.h" + +// hexdump +void xxd(const void *pbuf, size_t len) { + const uint8_t *buf = pbuf; + + for (int i = 0; i < len; i += 16) { + printf("%08x: ", i); + + for (int j = 0; j < 16; j++) { + if (i+j < len) { + printf("%02x ", buf[i+j]); + } else { + printf(" "); + } + } + + printf(" "); + + for (int j = 0; j < 16 && i+j < len; j++) { + if (i+j < len) { + if (buf[i+j] >= ' ' && buf[i+j] <= '~') { + printf("%c", buf[i+j]); + } else { + printf("."); + } + } else { + printf(" "); + } + } + + printf("\n"); + } +} + +// bytes to hex, no newline at the end +void hex(const void *pbuf, size_t len) { + const uint8_t *buf = pbuf; + for (int i = 0; i < len; i++) { + printf("%02x", buf[i]); + } +} + diff --git a/xxd.h b/xxd.h new file mode 100644 index 000000000..0862e85f4 --- /dev/null +++ b/xxd.h @@ -0,0 +1,18 @@ +/* + * Hex dump utilities + * + */ + +#ifndef XXD_H +#define XXD_H + +#include +#include + +// hexdump +void xxd(const void *pbuf, size_t len); + +// bytes to hex, no newline at the end +void hex(const void *pbuf, size_t len); + +#endif From 9b57c61aa466358076547cab350d844ec8636a1b Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 20 Apr 2021 23:43:40 -0500 Subject: [PATCH 11/56] Reversed base64 encode order to allow in-place encoding --- base64.c | 54 ++++++++++++++++++++++++++++++++---------------------- base64.h | 6 +++--- main.c | 28 ++++++++++++---------------- qemu.h | 2 ++ 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/base64.c b/base64.c index 0d3b0250e..2afebfca8 100644 --- a/base64.c +++ b/base64.c @@ -32,36 +32,46 @@ ssize_t base64_encode( } out[e_len] = '\0'; - for (size_t i=0, j=0; i < in_len; i += 3, j += 4) { - size_t v = in[i]; - v = i+1 < e_len ? (v << 8 | in[i+1]) : (v << 8); - v = i+2 < e_len ? (v << 8 | in[i+2]) : (v << 8); + for (size_t i = 0, j = 0; i < in_len; i += 3, j += 4) { + size_t v = in[in_len-i-3]; + v = in_len-i-3+1 < e_len ? (v << 8 | in[in_len-i-3+1]) : (v << 8); + v = in_len-i-3+2 < e_len ? (v << 8 | in[in_len-i-3+2]) : (v << 8); - out[j] = BASE64_ENCODE[(v >> 18) & 0x3f]; - out[j+1] = BASE64_ENCODE[(v >> 12) & 0x3f]; + out[e_len-j-4] = BASE64_ENCODE[(v >> 18) & 0x3f]; + out[e_len-j-4+1] = BASE64_ENCODE[(v >> 12) & 0x3f]; - if (i+1 < in_len) { - out[j+2] = BASE64_ENCODE[(v >> 6) & 0x3f]; + if (in_len-i-3+1 < in_len) { + out[e_len-j-4+2] = BASE64_ENCODE[(v >> 6) & 0x3f]; } else { - out[j+2] = '='; + out[e_len-j-4+2] = '='; } - if (i+2 < in_len) { - out[j+3] = BASE64_ENCODE[v & 0x3f]; + if (in_len-i-3+2 < in_len) { + out[e_len-j-4+3] = BASE64_ENCODE[v & 0x3f]; } else { - out[j+3] = '='; + out[e_len-j-4+3] = '='; } } return e_len; } +static size_t strnlen(const char *s, size_t s_len) { + for (size_t i = 0; i < s_len; i++) { + if (s[i] == '\0') { + return i; + } + } + + return s_len; +} + // compute size after decoding -size_t base64_decode_size(const char *in) { - size_t in_len = strlen(in); +size_t base64_decode_size(const char *in, size_t in_len) { + size_t e_len = strnlen(in, in_len); - size_t x = 3*(in_len/4); - for (size_t i = 0; i < in_len && in[in_len-i-1] == '='; i++) { + size_t x = 3*(e_len/4); + for (size_t i = 0; i < e_len && in[e_len-i-1] == '='; i++) { x -= 1; } @@ -92,25 +102,25 @@ static bool base64_isvalid(char c) { // decode base64 ssize_t base64_decode( - const char *in, + const char *in, size_t in_len, char *out, size_t out_len) { - size_t in_len = strlen(in); - if (in_len % 4 != 0) { + size_t e_len = strnlen(in, in_len); + if (e_len % 4 != 0) { return -EINVAL; } - size_t d_len = base64_decode_size(in); + size_t d_len = base64_decode_size(in, in_len); if (d_len > out_len) { return -EOVERFLOW; } - for (size_t i = 0; i < in_len; i++) { + for (size_t i = 0; i < e_len; i++) { if (!base64_isvalid(in[i])) { return -EILSEQ; } } - for (size_t i=0, j=0; i < in_len; i += 4, j += 3) { + for (size_t i = 0, j = 0; i < e_len; i += 4, j += 3) { size_t v = BASE64_DECODE[in[i]-43]; v = (v << 6) | BASE64_DECODE[in[i+1]-43]; v = in[i+2] == '=' ? (v << 6) : ((v << 6) | BASE64_DECODE[in[i+2]-43]); diff --git a/base64.h b/base64.h index d92fda92e..1d728cf56 100644 --- a/base64.h +++ b/base64.h @@ -17,12 +17,12 @@ ssize_t base64_encode( const uint8_t *in, size_t in_len, char *out, size_t out_len); -//compute size after decoding -size_t base64_decode_size(const char *in); +// compute size after decoding +size_t base64_decode_size(const char *in, size_t in_len); // decode base64 ssize_t base64_decode( - const char *in, + const char *in, size_t in_len, char *out, size_t out_len); #endif diff --git a/main.c b/main.c index 52b290656..51a5c50dc 100644 --- a/main.c +++ b/main.c @@ -54,21 +54,18 @@ void main(void) { pb_encode(&request_stream, &Tp_RuntimeManagerRequest_msg, &request); // convert base64 - // TODO if base64 was reversed this could operate in-place - char request_b64_buf[256]; - ssize_t request_b64_len = base64_encode( + ssize_t request_len = base64_encode( request_buf, request_stream.bytes_written, - request_b64_buf, sizeof(request_b64_buf)); - if (request_b64_len < 0) { - printf("base64_encode failed (%d)\n", request_b64_len); + request_buf, sizeof(request_buf)); + if (request_len < 0) { + printf("base64_encode failed (%d)\n", request_len); qemu_exit(); } printf("request:\n"); - xxd(request_b64_buf, request_b64_len); + xxd(request_buf, request_len); // POST challenge - // TODO get from policy.h printf("connecting to %s:%d...\n", VERACRUZ_SERVER_HOST, VERACRUZ_SERVER_PORT); @@ -76,8 +73,8 @@ void main(void) { VERACRUZ_SERVER_HOST, VERACRUZ_SERVER_PORT, "/sinaloa", - request_b64_buf, - request_b64_len, + request_buf, + request_len, buffer, sizeof(buffer)); if (pat_len < 0) { @@ -99,8 +96,8 @@ void main(void) { "/VerifyPAT", buffer, pat_len, - buffer2, - sizeof(buffer2)); + buffer, + sizeof(buffer)); if (res < 0) { printf("http_post failed (%d)\n", res); qemu_exit(); @@ -108,11 +105,10 @@ void main(void) { printf("http_post -> %d\n", res); printf("attest: PAT response:\n"); - xxd(buffer2, res); + xxd(buffer, res); - // back to buffer1, TODO in-place base64? - // TODO use strnlen... - ssize_t verif_len = base64_decode(buffer2, buffer, sizeof(buffer)); + // decode base64 + ssize_t verif_len = base64_decode(buffer, sizeof(buffer), buffer, sizeof(buffer)); if (verif_len < 0) { printf("base64_decode failed (%d)\n", verif_len); qemu_exit(); diff --git a/qemu.h b/qemu.h index a1d620207..8a51b5c3c 100644 --- a/qemu.h +++ b/qemu.h @@ -14,6 +14,8 @@ static inline void qemu_exit(void) { "ldr r1, =#0x20026 \n\t" "bkpt #0xab \n\t" ); + + __builtin_unreachable(); } #endif From c100627ae268d63485342edab908382773f7b420 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 23 Apr 2021 11:46:26 -0500 Subject: [PATCH 12/56] Moved veracruz logic into vc.h/vc.c --- main.c | 143 +++---------------------------------------------- vc.c | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vc.h | 20 +++++++ 3 files changed, 191 insertions(+), 137 deletions(-) create mode 100644 vc.c create mode 100644 vc.h diff --git a/main.c b/main.c index 51a5c50dc..a205a53dd 100644 --- a/main.c +++ b/main.c @@ -19,149 +19,18 @@ #include "base64.h" #include "http.h" #include "qemu.h" +#include "vc.h" //// application logic //// - -uint8_t buffer[10*1024]; -uint8_t buffer2[10*1024]; - void main(void) { - // get random challenge - // TODO Zephyr notes this is not cryptographically secure, is that an - // issue? This will be an area to explore - uint8_t challenge[32]; - sys_rand_get(challenge, sizeof(challenge)); - - // TODO log? can we incrementally log? - printf("attest: challenge: "); - hex(challenge, sizeof(challenge)); - printf("\n"); - - // construct attestation token request - Tp_RuntimeManagerRequest request = { - .which_message_oneof = Tp_RuntimeManagerRequest_request_proxy_psa_attestation_token_tag - }; - memcpy(request.message_oneof.request_proxy_psa_attestation_token.challenge, - challenge, sizeof(challenge)); - - // encode - // TODO this could be smaller, but instead could we tie protobuf encoding - // directly into our GET function? - uint8_t request_buf[256]; - pb_ostream_t request_stream = pb_ostream_from_buffer( - request_buf, sizeof(request_buf)); - pb_encode(&request_stream, &Tp_RuntimeManagerRequest_msg, &request); - - // convert base64 - ssize_t request_len = base64_encode( - request_buf, request_stream.bytes_written, - request_buf, sizeof(request_buf)); - if (request_len < 0) { - printf("base64_encode failed (%d)\n", request_len); - qemu_exit(); - } - - printf("request:\n"); - xxd(request_buf, request_len); - - // POST challenge - printf("connecting to %s:%d...\n", - VERACRUZ_SERVER_HOST, - VERACRUZ_SERVER_PORT); - ssize_t pat_len = http_post( - VERACRUZ_SERVER_HOST, - VERACRUZ_SERVER_PORT, - "/sinaloa", - request_buf, - request_len, - buffer, - sizeof(buffer)); - if (pat_len < 0) { - printf("http_post failed (%d)\n", pat_len); - qemu_exit(); - } - - printf("http_post -> %d\n", pat_len); - printf("attest: challenge response:\n"); - xxd(buffer, pat_len); - - // forward to proxy attestation server - printf("connecting to %s:%d...\n", - PROXY_ATTESTATION_SERVER_HOST, - PROXY_ATTESTATION_SERVER_PORT); - ssize_t res = http_post( - PROXY_ATTESTATION_SERVER_HOST, - PROXY_ATTESTATION_SERVER_PORT, - "/VerifyPAT", - buffer, - pat_len, - buffer, - sizeof(buffer)); - if (res < 0) { - printf("http_post failed (%d)\n", res); + // attest the Veracruz enclave + int err = vc_attest(); + if (err) { + printf("vc_attest failed (%d)\n", err); qemu_exit(); } - printf("http_post -> %d\n", res); - printf("attest: PAT response:\n"); - xxd(buffer, res); - - // decode base64 - ssize_t verif_len = base64_decode(buffer, sizeof(buffer), buffer, sizeof(buffer)); - if (verif_len < 0) { - printf("base64_decode failed (%d)\n", verif_len); - qemu_exit(); - } - - printf("attest: PAT decoded response:\n"); - xxd(buffer, verif_len); - - if (verif_len < 131) { - printf("pat response too small\n"); - qemu_exit(); - } - - // check that challenge matches - if (memcmp(challenge, &buffer[8], 32) != 0) { - printf("challenge mismatch\n"); - printf("expected: "); - for (int i = 0; i < sizeof(challenge); i++) { - printf("%02x", challenge[i]); - } - printf("\n"); - printf("recieved: "); - for (int i = 0; i < 32; i++) { - printf("%02x", buffer[8+i]); - } - printf("\n"); - qemu_exit(); - } - - // check that enclave hash matches policy - if (memcmp(&buffer[47], RUNTIME_MANAGER_HASH, sizeof(RUNTIME_MANAGER_HASH)) != 0) { - printf("enclave hash mismatch\n"); - printf("expected: "); - for (int i = 0; i < sizeof(RUNTIME_MANAGER_HASH); i++) { - printf("%02x", RUNTIME_MANAGER_HASH[i]); - } - printf("\n"); - printf("recieved: "); - for (int i = 0; i < 32; i++) { - printf("%02x", buffer[8+i]); - } - printf("\n"); - qemu_exit(); - } - - // recieved values - printf("enclave name: %.*s\n", 7, &buffer[124]); - printf("enclave hash: "); - hex(&buffer[47], 32); - printf("\n"); - printf("enclave cert hash: "); - hex(&buffer[86], 32); - printf("\n"); - + printf("success!\n"); qemu_exit(); } diff --git a/vc.c b/vc.c new file mode 100644 index 000000000..262762ab9 --- /dev/null +++ b/vc.c @@ -0,0 +1,165 @@ +/* + * Veracruz client aimed at microcontrollers, built in Zephyr OS + * + */ + +#include "vc.h" + +#include +#include + +#include "nanopb/pb.h" +#include "nanopb/pb_encode.h" +#include "nanopb/pb_decode.h" +#include "transport_protocol.pb.h" + +#include "policy.h" +#include "xxd.h" +#include "base64.h" +#include "http.h" + + +int vc_attest(void) { + // get random challenge + // TODO Zephyr notes this is not cryptographically secure, is that an + // issue? This will be an area to explore + uint8_t challenge[32]; + sys_rand_get(challenge, sizeof(challenge)); + + // TODO log? can we incrementally log? + printf("attest: challenge: "); + hex(challenge, sizeof(challenge)); + printf("\n"); + + // construct attestation token request + Tp_RuntimeManagerRequest request = { + .which_message_oneof = Tp_RuntimeManagerRequest_request_proxy_psa_attestation_token_tag + }; + memcpy(request.message_oneof.request_proxy_psa_attestation_token.challenge, + challenge, sizeof(challenge)); + + // encode + // TODO this could be smaller, but instead could we tie protobuf encoding + // directly into our GET function? + uint8_t request_buf[256]; + pb_ostream_t request_stream = pb_ostream_from_buffer( + request_buf, sizeof(request_buf)); + pb_encode(&request_stream, &Tp_RuntimeManagerRequest_msg, &request); + + // convert base64 + ssize_t request_len = base64_encode( + request_buf, request_stream.bytes_written, + request_buf, sizeof(request_buf)); + if (request_len < 0) { + printf("base64_encode failed (%d)\n", request_len); + return request_len; + } + + printf("request:\n"); + xxd(request_buf, request_len); + + // POST challenge + printf("connecting to %s:%d...\n", + VERACRUZ_SERVER_HOST, + VERACRUZ_SERVER_PORT); + uint8_t pat_buf[1024]; + ssize_t pat_len = http_post( + VERACRUZ_SERVER_HOST, + VERACRUZ_SERVER_PORT, + "/sinaloa", + request_buf, + request_len, + pat_buf, + sizeof(pat_buf)); + if (pat_len < 0) { + printf("http_post failed (%d)\n", pat_len); + return pat_len; + } + + printf("http_post -> %d\n", pat_len); + printf("attest: challenge response:\n"); + xxd(pat_buf, pat_len); + + // forward to proxy attestation server + printf("connecting to %s:%d...\n", + PROXY_ATTESTATION_SERVER_HOST, + PROXY_ATTESTATION_SERVER_PORT); + uint8_t response_buf[256]; + ssize_t response_len = http_post( + PROXY_ATTESTATION_SERVER_HOST, + PROXY_ATTESTATION_SERVER_PORT, + "/VerifyPAT", + pat_buf, + pat_len, + response_buf, + sizeof(response_buf)); + if (response_len < 0) { + printf("http_post failed (%d)\n", response_len); + return response_len; + } + + printf("http_post -> %d\n", response_len); + printf("attest: PAT response:\n"); + xxd(response_buf, response_len); + + // decode base64 + ssize_t verif_len = base64_decode( + response_buf, sizeof(response_buf), + response_buf, sizeof(response_buf)); + if (verif_len < 0) { + printf("base64_decode failed (%d)\n", verif_len); + return verif_len; + } + + printf("attest: PAT decoded response:\n"); + xxd(response_buf, verif_len); + + if (verif_len < 131) { + printf("pat response too small\n"); + return -EOVERFLOW; + } + + // check that challenge matches + if (memcmp(challenge, &response_buf[8], 32) != 0) { + printf("challenge mismatch\n"); + printf("expected: "); + for (int i = 0; i < sizeof(challenge); i++) { + printf("%02x", challenge[i]); + } + printf("\n"); + printf("recieved: "); + for (int i = 0; i < 32; i++) { + printf("%02x", response_buf[8+i]); + } + printf("\n"); + return -EBADE; + } + + // check that enclave hash matches policy + if (memcmp(&response_buf[47], RUNTIME_MANAGER_HASH, + sizeof(RUNTIME_MANAGER_HASH)) != 0) { + printf("enclave hash mismatch\n"); + printf("expected: "); + for (int i = 0; i < sizeof(RUNTIME_MANAGER_HASH); i++) { + printf("%02x", RUNTIME_MANAGER_HASH[i]); + } + printf("\n"); + printf("recieved: "); + for (int i = 0; i < 32; i++) { + printf("%02x", response_buf[8+i]); + } + printf("\n"); + return -EBADE; + } + + // recieved values + printf("enclave name: %.*s\n", 7, &response_buf[124]); + printf("enclave hash: "); + hex(&response_buf[47], 32); + printf("\n"); + printf("enclave cert hash: "); + hex(&response_buf[86], 32); + printf("\n"); + + return 0; +} diff --git a/vc.h b/vc.h new file mode 100644 index 000000000..8f8b1a631 --- /dev/null +++ b/vc.h @@ -0,0 +1,20 @@ +/* + * Veracruz client aimed at microcontrollers, built in Zephyr OS + * + * Note that there are very few arguments to these functions, the Veracruz + * client is based on a static policy file that is expected preprocessed into + * a policy.h file (see policy_to_header.py). + * + * TODO config struct? + * + */ + +#ifndef VC_H +#define VC_H + +#include +#include + +int vc_attest(void); + +#endif From 897cdcd562e0f7a9ed2326880b9267776fd04a65 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sun, 25 Apr 2021 13:53:36 -0500 Subject: [PATCH 13/56] More progress, handshake is getting a response --- main.c | 28 +++--- prj.conf | 12 +++ vc.c | 259 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- vc.h | 54 +++++++++++- 4 files changed, 335 insertions(+), 18 deletions(-) diff --git a/main.c b/main.c index a205a53dd..b1aed56d7 100644 --- a/main.c +++ b/main.c @@ -6,31 +6,31 @@ #include #include -#include #include -#include "nanopb/pb.h" -#include "nanopb/pb_encode.h" -#include "nanopb/pb_decode.h" -#include "transport_protocol.pb.h" - -#include "policy.h" #include "xxd.h" -#include "base64.h" -#include "http.h" #include "qemu.h" #include "vc.h" +// Veracruz client +vc_t vc; -//// application logic //// +// entry point void main(void) { - // attest the Veracruz enclave - int err = vc_attest(); + // Attest and connect to the Veracruz enclave + int err = vc_attest_and_connect(&vc); if (err) { - printf("vc_attest failed (%d)\n", err); + printf("vc_attest_and_connect failed (%d)\n", err); qemu_exit(); } + printf("connected!\n"); - printf("success!\n"); + err = vc_close(&vc); + if (err) { + printf("vc_close failed (%d)\n", err); + qemu_exit(); + } + printf("closed!\n"); + qemu_exit(); } diff --git a/prj.conf b/prj.conf index 40260443f..72d8a4936 100644 --- a/prj.conf +++ b/prj.conf @@ -46,3 +46,15 @@ CONFIG_LOG_IMMEDIATE=y CONFIG_NET_LOG=y CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=n CONFIG_NET_HTTP_LOG_LEVEL_DBG=y + +# MbedTLS config +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_DEBUG=y +CONFIG_MBEDTLS_CIPHER_CHACHA20_ENABLED=y +CONFIG_MBEDTLS_CHACHAPOLY_AEAD_ENABLED=y +CONFIG_MBEDTLS_MAC_SHA256_ENABLED=y +CONFIG_MBEDTLS_MAC_POLY1305_ENABLED=y +CONFIG_MBEDTLS_TLS_VERSION_1_2=y +# TODO is there a specific ECP to use? +CONFIG_MBEDTLS_ECP_ALL_ENABLED=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED=y diff --git a/vc.c b/vc.c index 262762ab9..da3061cd7 100644 --- a/vc.c +++ b/vc.c @@ -8,6 +8,9 @@ #include #include +#include +#include + #include "nanopb/pb.h" #include "nanopb/pb_encode.h" #include "nanopb/pb_decode.h" @@ -17,9 +20,19 @@ #include "xxd.h" #include "base64.h" #include "http.h" +#include "qemu.h" + +//// attestation //// +int vc_attest( + char *enclave_name, size_t enclave_name_len, + uint8_t *enclave_cert_hash, size_t *enclave_cert_hash_len) { + // check buffer sizes here, with the current Veracruz implementation + // these are fixed sizes + if (enclave_name_len < 7+1 || *enclave_cert_hash_len < 32) { + return -EINVAL; + } -int vc_attest(void) { // get random challenge // TODO Zephyr notes this is not cryptographically secure, is that an // issue? This will be an area to explore @@ -153,13 +166,253 @@ int vc_attest(void) { } // recieved values - printf("enclave name: %.*s\n", 7, &response_buf[124]); + // TODO why verify_iat response not a protobuf? + memcpy(enclave_name, &response_buf[124], 7); + enclave_name[7] = '\0'; + memcpy(enclave_cert_hash, &response_buf[86], 32); + *enclave_cert_hash_len = 32; + + printf("enclave name: %s\n", enclave_name); printf("enclave hash: "); hex(&response_buf[47], 32); printf("\n"); printf("enclave cert hash: "); - hex(&response_buf[86], 32); + hex(enclave_cert_hash, *enclave_cert_hash_len); printf("\n"); return 0; } + + +//// Veracruz session handling //// +static void mbedtls_debug(void *ctx, int level, + const char *file, int line, + const char *str) { + const char *basename = file; + for (int i = 0; file[i]; i++) { + if (file[i] == '/') { + basename = &file[i+1]; + } + } + + printf("%s:%d %s", basename, line, str); +} + +static int vc_rawrng(void *p, + uint8_t *buf, size_t len) { + // TODO use cryptographically secure rng? + sys_rand_get(buf, len); + return 0; +} + +static ssize_t vc_rawsend(void *p, + const uint8_t *buf, size_t len) { + vc_t *vc = p; + // encode with base64 + id (0) + ssize_t data_len = 0; + ssize_t res = snprintf(vc->send_buf, VC_SEND_BUFFER_SIZE, + "%d ", vc->session_id); + if (res < 0) { + printf("formatting failed (%d)\n", res); + return res; + } + data_len += res; + // TODO change base64 encode order? + res = base64_encode( + buf, len, + &vc->send_buf[data_len], VC_SEND_BUFFER_SIZE-data_len); + if (res < 0) { + printf("base64_encode failed (%d)\n", res); + return res; + } + data_len += res; + + // send data over HTTP POST + printf("sending to %s:%d:\n", + VERACRUZ_SERVER_HOST, + VERACRUZ_SERVER_PORT); + xxd(vc->send_buf, data_len); + ssize_t recv_len = http_post( + VERACRUZ_SERVER_HOST, + VERACRUZ_SERVER_PORT, + "/mexico_city", + vc->send_buf, + data_len, + vc->recv_buf, + VC_RECV_BUFFER_SIZE); + if (recv_len < 0) { + printf("http_post failed (%d)\n", recv_len); + return recv_len; + } + + printf("http_post -> %d\n", recv_len); + printf("ssl session: recv:\n"); + xxd(vc->recv_buf, recv_len); + + // we have a bit of parsing to do, first decode session id + const uint8_t *parsing = vc->recv_buf; + vc->session_id = strtol(parsing, (char **)&parsing, 10); + if (parsing == vc->recv_buf) { + printf("failed to parse session id\n"); + return -EILSEQ; + } + // skip space + parsing += 1; + printf("session id: %d\n", vc->session_id); + + // parse out base64 blobs, shuffling to front of our buffer + uint8_t *parsed = vc->recv_buf; + while (parsing < &vc->recv_buf[recv_len]) { + int i = 0; + while (parsing[i] && parsing[i] != ' ') { + i += 1; + } + + res = base64_decode( + parsing, i, + parsed, &vc->recv_buf[recv_len]-parsed); + if (res < 0) { + printf("base64_decode failed (%d)\n", res); + return res; + } + + parsing += i; + parsed += res; + + // skip space + if (parsing[0] == ' ') { + parsing += 1; + } + } + + vc->recv_pos = vc->recv_buf; + vc->recv_len = parsed - vc->recv_buf; + + printf("ssl session: parsed:\n"); + xxd(vc->recv_pos, vc->recv_len); + + // done! + return len; +} + +static ssize_t vc_rawrecv(void *p, + uint8_t *buf, size_t len, + uint32_t timeout) { + vc_t *vc = p; + + size_t diff = (len < vc->recv_len) ? len : vc->recv_len; + memcpy(buf, vc->recv_pos, diff); + vc->recv_pos += diff; + vc->recv_len -= diff; + return diff; +} + +int vc_connect(vc_t *vc, + // TODO need enclave name? + const char *enclave_name, + const uint8_t *enclave_cert_hash, size_t enclave_cert_hash_len) { + // some setup + vc->session_id = 0; + vc->recv_len = 0; + + // allocate buffers + vc->send_buf = malloc(VC_SEND_BUFFER_SIZE); + if (!vc->send_buf) { + return -ENOMEM; + } + + vc->recv_buf = malloc(VC_RECV_BUFFER_SIZE); + if (!vc->recv_buf) { + free(vc->send_buf); + return -ENOMEM; + } + + // setup SSL connection + mbedtls_ssl_init(&vc->session); + mbedtls_ssl_config_init(&vc->session_cfg); + + int err = mbedtls_ssl_config_defaults(&vc->session_cfg, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT); + if (err) { + printf("failed to configure SSL (%d)\n", err); + mbedtls_ssl_config_free(&vc->session_cfg); + free(vc->recv_buf); + free(vc->send_buf); + return err; + } + + // TODO fix this, is as is just for testing + mbedtls_ssl_conf_authmode(&vc->session_cfg, MBEDTLS_SSL_VERIFY_NONE); + + mbedtls_ssl_conf_rng(&vc->session_cfg, vc_rawrng, NULL); + + // TODO depend on policy.h generation? this is from policy.json + vc->session_ciphersuites[0] = MBEDTLS_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256; + vc->session_ciphersuites[1] = 0; + mbedtls_ssl_conf_ciphersuites(&vc->session_cfg, vc->session_ciphersuites); + + // TODO remove debugging? hide behind logging? + mbedtls_debug_set_threshold(4); + mbedtls_ssl_conf_dbg(&vc->session_cfg, mbedtls_debug, vc); + + err = mbedtls_ssl_setup(&vc->session, &vc->session_cfg); + if (err) { + printf("failed to setup SSL session (%d)\n", err); + mbedtls_ssl_config_free(&vc->session_cfg); + free(vc->recv_buf); + free(vc->send_buf); + return err; + } + + // setup blocking IO functions + mbedtls_ssl_set_bio(&vc->session, + vc, + vc_rawsend, + NULL, // no non-blocking recv + vc_rawrecv); + + // perform SSL handshake + err = mbedtls_ssl_handshake(&vc->session); + if (err) { + printf("SSL handshake failed (%d)\n", err); + mbedtls_ssl_free(&vc->session); + mbedtls_ssl_config_free(&vc->session_cfg); + free(vc->recv_buf); + free(vc->send_buf); + return err; + } + + return 0; +} + +int vc_close(vc_t *vc) { + mbedtls_ssl_free(&vc->session); + mbedtls_ssl_config_free(&vc->session_cfg); + free(vc->recv_buf); + free(vc->send_buf); + return 0; +} + +int vc_attest_and_connect(vc_t *vc) { + char enclave_name[7+1]; + uint8_t enclave_cert_hash[32]; + size_t enclave_cert_hash_len = 32; + int err = vc_attest( + enclave_name, 7+1, + enclave_cert_hash, &enclave_cert_hash_len); + if (err) { + return err; + } + + err = vc_connect(vc, + enclave_name, + enclave_cert_hash, enclave_cert_hash_len); + if (err) { + return err; + } + + return 0; +} + diff --git a/vc.h b/vc.h index 8f8b1a631..cbb58243a 100644 --- a/vc.h +++ b/vc.h @@ -14,7 +14,59 @@ #include #include +#include -int vc_attest(void); +#ifndef VC_SEND_BUFFER_SIZE +#define VC_SEND_BUFFER_SIZE 1024 +#endif + +#ifndef VC_RECV_BUFFER_SIZE +#define VC_RECV_BUFFER_SIZE (4*1024) +#endif + +// Veracruz client state +typedef struct vc { + // Veracruz state + int session_id; + const uint8_t *recv_pos; + size_t recv_len; + + // TLS state + mbedtls_ssl_context session; + mbedtls_ssl_config session_cfg; + int session_ciphersuites[2]; + + // buffers for shuffling tls data + uint8_t *send_buf; + uint8_t *recv_buf; +} vc_t; + +// Attest and connect to a Veracruz enclave +int vc_attest_and_connect(vc_t *vc); + +// Disconnect and clean up memory +// +// Note even if an error is returned, memory is still cleaned up +// +int vc_close(vc_t *vc); + +// Attest the Veracruz enclave +// +// Note, this does a single standalone attestation without establishing +// a connection to the attested enclave, you likely want vc_attest_and_connect +// if further operations are wanted +// +int vc_attest( + char *enclave_name, size_t enclave_name_len, + uint8_t *enclave_cert_hash, size_t *enclave_cert_hash_len); + +// Connect to an attested Veracruz enclave +// +// Note, this assumes the enclave has already been attested, you likely want +// to use vc_attest_and_connect, which ensures the enclave is attested when +// we connect +int vc_connect(vc_t *vc, + const char *enclave_name, + const uint8_t *enclave_cert_hash, size_t enclave_cert_hash_len); #endif From 7d241da2f9ee5810c89e83045d037d4561e94cde Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 26 Apr 2021 10:08:17 -0500 Subject: [PATCH 14/56] Halfway through handshake, successful back-and-forth over POSTs Still needs work: - Fails on key exchange for some reason - Still using *_VERIFY_NONE - Required significant RAM and stack increases --- Makefile | 9 +- base64.c | 27 +++--- client_cert.pem | 23 +++++ client_key.pem | 27 ++++++ policy.json | 12 +-- policy_to_header.py | 229 ++++++++++++++++++++++++++++++-------------- prj.conf | 2 +- vc.c | 112 +++++++++++++++++----- vc.h | 10 +- xxd.c | 2 +- 10 files changed, 335 insertions(+), 118 deletions(-) create mode 100644 client_cert.pem create mode 100644 client_key.pem diff --git a/Makefile b/Makefile index 8bf646b85..1559364a4 100644 --- a/Makefile +++ b/Makefile @@ -44,8 +44,13 @@ update: # TODO move these into west? # Generate policy.h/c -policy.h policy.c: policy.json policy_to_header.py - ./policy_to_header.py $< policy.h policy.c +policy.h policy.c: policy_to_header.py +policy.h policy.c: policy.json client_cert.pem client_key.pem + $(strip ./policy_to_header.py $< \ + --identity=$(word 2,$^) \ + --key=$(word 3,$^) \ + --header=policy.h \ + --source=policy.c) # Generate transport_protocol.pb.h/c transport_protocol.pb.h transport_protocol.pb.c: \ diff --git a/base64.c b/base64.c index 2afebfca8..1f2c38bc8 100644 --- a/base64.c +++ b/base64.c @@ -7,14 +7,18 @@ #include #include +// convenience functions +static inline uint32_t base64_aligndown(uint32_t a, uint32_t alignment) { + return a - (a % alignment); +} + +static inline uint32_t base64_alignup(uint32_t a, uint32_t alignment) { + return base64_aligndown(a + alignment-1, alignment); +} + // compute size after encoding size_t base64_encode_size(size_t in_len) { - size_t x = in_len; - if (in_len % 3 != 0) { - x += 3 - (in_len % 3); - } - - return 4*(x/3); + return 4*(base64_alignup(in_len, 3) / 3); } static const char BASE64_ENCODE[] = ( @@ -32,21 +36,22 @@ ssize_t base64_encode( } out[e_len] = '\0'; + size_t rin_len = base64_alignup(in_len, 3); for (size_t i = 0, j = 0; i < in_len; i += 3, j += 4) { - size_t v = in[in_len-i-3]; - v = in_len-i-3+1 < e_len ? (v << 8 | in[in_len-i-3+1]) : (v << 8); - v = in_len-i-3+2 < e_len ? (v << 8 | in[in_len-i-3+2]) : (v << 8); + size_t v = in[rin_len-i-3]; + v = rin_len-i-3+1 < e_len ? (v << 8 | in[rin_len-i-3+1]) : (v << 8); + v = rin_len-i-3+2 < e_len ? (v << 8 | in[rin_len-i-3+2]) : (v << 8); out[e_len-j-4] = BASE64_ENCODE[(v >> 18) & 0x3f]; out[e_len-j-4+1] = BASE64_ENCODE[(v >> 12) & 0x3f]; - if (in_len-i-3+1 < in_len) { + if (rin_len-i-3+1 < in_len) { out[e_len-j-4+2] = BASE64_ENCODE[(v >> 6) & 0x3f]; } else { out[e_len-j-4+2] = '='; } - if (in_len-i-3+2 < in_len) { + if (rin_len-i-3+2 < in_len) { out[e_len-j-4+3] = BASE64_ENCODE[v & 0x3f]; } else { out[e_len-j-4+3] = '='; diff --git a/client_cert.pem b/client_cert.pem new file mode 100644 index 000000000..b98f83eaa --- /dev/null +++ b/client_cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzTCCArWgAwIBAgIUVirizhzBk1Z+psxK26AJyj+K0WowDQYJKoZIhvcNAQEL +BQAwgYcxCzAJBgNVBAYTAk14MREwDwYDVQQIDAhWZXJhY3J1ejERMA8GA1UEBwwI +VmVyYWNydXoxFjAUBgNVBAoMDVppYmJsZSBaYWJibGUxIzAhBgkqhkiG9w0BCQEW +FHppYmJsZUB6YWJibGUuemliYmxlMRUwEwYDVQQDDAx6aWJibGV6YWJibGUwHhcN +MjEwMzA4MTc0OTIwWhcNMzEwMzA2MTc0OTIwWjCBhzELMAkGA1UEBhMCTXgxETAP +BgNVBAgMCFZlcmFjcnV6MREwDwYDVQQHDAhWZXJhY3J1ejEWMBQGA1UECgwNWmli +YmxlIFphYmJsZTEjMCEGCSqGSIb3DQEJARYUemliYmxlQHphYmJsZS56aWJibGUx +FTATBgNVBAMMDHppYmJsZXphYmJsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAJxq0tjNGsiflNGOfogKchEs4sVm0p2efNb9OU2yLeI7La03Ez6WAdYQ +FvcwCgQpGBV5QEVjGhx93cPfMREs9lhma4y0nc/rHoe6nuHjqFBDhc+ruf6zdt5Z +T3bGZYC08Cxu+AEmO80w4YMScV0dwL5mhw0FRu0v4SEAE2FZA8BiiYzLzaa1QWeK +/AuwwqMNVKNUEdD87GVcF6I/7CRlwjh2G5ZoDiyXGmZCPhev0KYMcLnms89YWrFp +H7VNXl3mLAus6YOgD4mGcEa2VvdsGjq2iSwWl6bgQ+qeyV/GUo4Jgt6x4pFRyCm2 +vtJIubhJe0LK4nTxZ5bkjDNlYjHNixUCAwEAAaMvMC0wKwYDVR0RBCQwIoINemFi +YmxlLnppYmJsZYIRd3d3LnphYmJsZS56aWJibGUwDQYJKoZIhvcNAQELBQADggEB +AI6zcyGramC42rwnp3SwJNnmaasmU1opsduQHdMfqyHoJwcOPymgcHYKJwUT5Q/m +8zv5xx6MSSmBXTQWAfikX2D4T365vLvmj5mKX//bNc+4IPFSmMlpqYbGm8ptxNU6 +dtFIzMhI8+AlpEcJpJLtGSb/anVKp7JM/jadiRSohhVBuqf646KWgy56yNewvMED +oZYYBA9Kd2VgTXFsbo8Ol0DWcHYropTqVaftNSZ7YU+ZprigoBN8r8KMR74c/s6T +aPnrBKJ817J6ao4s6/5hzmACEDtHYZgLku+cXbT6ttNVgba7wsLawBTjT60OMwQb +LCQw/SvxKHEeVVYXp5lqO28= +-----END CERTIFICATE----- diff --git a/client_key.pem b/client_key.pem new file mode 100644 index 000000000..e2f7983a0 --- /dev/null +++ b/client_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAnGrS2M0ayJ+U0Y5+iApyESzixWbSnZ581v05TbIt4jstrTcT +PpYB1hAW9zAKBCkYFXlARWMaHH3dw98xESz2WGZrjLSdz+seh7qe4eOoUEOFz6u5 +/rN23llPdsZlgLTwLG74ASY7zTDhgxJxXR3AvmaHDQVG7S/hIQATYVkDwGKJjMvN +prVBZ4r8C7DCow1Uo1QR0PzsZVwXoj/sJGXCOHYblmgOLJcaZkI+F6/Qpgxwueaz +z1hasWkftU1eXeYsC6zpg6APiYZwRrZW92waOraJLBaXpuBD6p7JX8ZSjgmC3rHi +kVHIKba+0ki5uEl7QsridPFnluSMM2ViMc2LFQIDAQABAoIBAQCacOtQv2tzrgWd +x+ltX4rzJHOgX38TvDodeJfM/GJLnwzhm6C3yht0Bhz24orxQrYR9c+c81YDzc9a +qBRllhSCglYs7uVnNwrFD2HzPfqhjZogy7lxIDf9IQfSCeCM7Zq69LKRR/QCDQKN +jXc7brYyCT/NfmlipkTOdamtE9wdZmKOLoReHEFnHTqA3yNSLocgZlb5EdGGcwNv +wAf2TuyqLXdSa/LMZ4nABRhH7iJ9PyWZ6E9AJvegqrRXyCZ0SCxEETJDEbFi0e1A +oq4auRm52hhWZtITAhdkiyGY/sKru7aN+lFzsN+cGRTb0z6vKCipNHnfv54+0IjJ +UXPzVqqBAoGBAMzeA7XeCUPhmOIXxZyh8t45iqDWgF4ci9gC+1YbzGaH+yX3ym8G +oTCuzsf/xwbwZnZxeII3PqmHDqHe3trxdf2MAVO24ifkD/SjzXlzFXpziBkoYQdA +o3oOW3nTGp1B/fdh/SV14uRkJ0tnL+70aAHYo9LKKJkzCbVhttGCnXX1AoGBAMN1 +Ga7O7JCdwWL2rWtl9Wxzre69WD9ltAmZ/hOKtcqRkfl4KiLbFbE5ZWZeFRBSYKZ5 +oz7RW3DIvNB018MxAY+YGAXGNNZmO+lZxq1VR2UhxkZUjZG4rfMFmVJDT1xOEK/P +WtxUSTnMfyrbybvcVJ2Oi30DzePRpb2vo1kuF2yhAoGAcS3eQuUtnTDO82lKpOPb +duW3LnASbuu6XlYXYFTvMV3CZBFm5rt05Z5NYrRt0emR0bGSb+3xUlrovIiR+ccX +9kEYopUQkBUws2ijJYtsvD+DWtKx9/2/0riH1N/JfkNs+PRLlBxygtbJ1qBlHNAt +fYefyd4hW7GUlX4sL8bHEtkCgYEAwmC3LOA95MfuJVP6PN9Fxhf4tn74vvuOoynQ +wnBkv9Iq4HR/OvCzzu6sh7QysGusEILRNMyRakVHeOtqE4St77aq1Ts7GqjLFhKh +AYnYmZuQCitWKsAFxbQguO2Vg89iyuSkkI8Fz4QuR1oSck/4mkPDNo1M+S5p7I06 +Hps20mECgYBKYmHN0IfDHhC+KWtq0YtvBbcdEvgTgcanyZNzBPS067t2X4PMADF9 +18lYAq55JITJJvRPCrJyFIiD2QYYeQecxKQ4fxG3XTvGzwzC1saueTCLRYKQiRhY +H4Qsfq3D4SSklGnQTMWgCRcOIKQvnaM4I6SWfSTRqqWGTaIPq0NCYw== +-----END RSA PRIVATE KEY----- diff --git a/policy.json b/policy.json index 2e073de57..777e1d162 100644 --- a/policy.json +++ b/policy.json @@ -2,10 +2,10 @@ "ciphersuite": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "debug": false, "enclave_cert_expiry": { - "day": 17, - "hour": 12, - "minute": 23, - "month": 6, + "day": 4, + "hour": 9, + "minute": 58, + "month": 8, "year": 2021 }, "execution_strategy": "Interpretation", @@ -104,9 +104,9 @@ ], "id": 0, "pi_hash": "a6f4a34c3dbee07d002e32f5647ed196a27613ac614be279da9ca5ef014722de", - "program_file_name": "example-binary.wasm" + "program_file_name": "example/example-binary.wasm" } ], "proxy_attestation_server_url": "172.17.0.2:3010", "sinaloa_url": "172.17.0.2:3017" -} +} \ No newline at end of file diff --git a/policy_to_header.py b/policy_to_header.py index a9d4cb453..42de93ab0 100755 --- a/policy_to_header.py +++ b/policy_to_header.py @@ -1,86 +1,175 @@ #!/usr/bin/env python3 +# TODO should these all have prefixes? + import json import hashlib import base64 -def main(in_json, out_h, out_c): - with open(in_json) as f: +# TODO fully populate this? +CIPHERSUITES = { + 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256': 'MBEDTLS_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256', +} + +def pem_to_der(pem): + lines = pem.strip().split('\n') + lines = ''.join(line.strip() for line in lines[1:-1]) + return base64.b64decode(lines) + +def main(args): + print('loading policy %s' % args.policy) + with open(args.policy) as f: policy_raw = f.read() policy_hash = hashlib.sha256(policy_raw.encode('utf-8')).hexdigest() policy = json.loads(policy_raw) - print('Generating %s for policy %s' % (out_h, policy_hash)) - with open(out_h, 'w') as f: - _write = f.write - def write(s='', **args): - _write(s % args) - def writeln(s='', **args): - _write(s % args) - _write('\n') - f.write = write - f.writeln = writeln + if args.identity: + print('loading identity %s' % args.identity) + with open(args.identity) as f: + identity_pem = f.read() + identity_der = pem_to_der(identity_pem) + + # sanity check that identity is in policy + assert any( + identity['certificate'].replace('\n', '') + == identity_pem.replace('\n', '') + for identity in policy['identities']) + + if args.key: + print('loading key %s' % args.key) + with open(args.key) as f: + key_pem = f.read() + key_der = pem_to_der(key_pem) + + if args.header: + print('generating %s for policy %s' % (args.header, policy_hash)) + with open(args.header, 'w') as f: + _write = f.write + def write(s='', **args): + _write(s % args) + def writeln(s='', **args): + _write(s % args) + _write('\n') + f.write = write + f.writeln = writeln - f.writeln('//// AUTOGENERATED ////') - f.writeln('#ifndef VERACRUZ_POLICY_H') - f.writeln('#define VERACRUZ_POLICY_H') - f.writeln() - f.writeln('#include ') - f.writeln() - f.writeln('// general policy things') - f.writeln('#define VERACRUZ_POLICY_HASH "%(hash)s"', - hash=policy_hash) - f.writeln('extern uint8_t _veracruz_policy_raw[];') - f.writeln('#define VERACRUZ_POLICY_RAW _veracruz_policy_raw') - f.writeln() - f.writeln('// various hashes') - # TODO choose between platforms? - f.writeln('extern uint8_t _runtime_manager_hash[%(size)d];', - size=len(policy['mexico_city_hash_sgx'])/2) - f.writeln('#define RUNTIME_MANAGER_HASH _runtime_manager_hash') - f.writeln() - f.writeln('// server info') - f.writeln('#define VERACRUZ_SERVER_HOST "%(host)s"', - host=policy['sinaloa_url'].split(':')[0]) - f.writeln('#define VERACRUZ_SERVER_PORT %(port)s', - port=policy['sinaloa_url'].split(':')[1]) - f.writeln('#define PROXY_ATTESTATION_SERVER_HOST "%(host)s"', - host=policy['proxy_attestation_server_url'].split(':')[0]) - f.writeln('#define PROXY_ATTESTATION_SERVER_PORT %(port)s', - port=policy['proxy_attestation_server_url'].split(':')[1]) - f.writeln() - f.writeln('#endif') + f.writeln('//// AUTOGENERATED ////') + f.writeln('#ifndef VERACRUZ_POLICY_H') + f.writeln('#define VERACRUZ_POLICY_H') + f.writeln() + f.writeln('#include ') + f.writeln('#include ') + f.writeln() + f.writeln('// general policy things') + f.writeln('#define VERACRUZ_POLICY_HASH "%(hash)s"', + hash=policy_hash) + # f.writeln('extern const uint8_t _VERACRUZ_POLICY_RAW[];') + # f.writeln('#define VERACRUZ_POLICY_RAW _VERACRUZ_POLICY_RAW') + f.writeln() + f.writeln('// various hashes') + # TODO choose between platforms? + f.writeln('extern const uint8_t _RUNTIME_MANAGER_HASH[%(size)d];', + size=len(policy['mexico_city_hash_sgx'])/2) + f.writeln('#define RUNTIME_MANAGER_HASH _RUNTIME_MANAGER_HASH') + f.writeln() + f.writeln('// server info') + f.writeln('#define VERACRUZ_SERVER_HOST "%(host)s"', + host=policy['sinaloa_url'].split(':')[0]) + f.writeln('#define VERACRUZ_SERVER_PORT %(port)s', + port=policy['sinaloa_url'].split(':')[1]) + f.writeln('#define PROXY_ATTESTATION_SERVER_HOST "%(host)s"', + host=policy['proxy_attestation_server_url'].split(':')[0]) + f.writeln('#define PROXY_ATTESTATION_SERVER_PORT %(port)s', + port=policy['proxy_attestation_server_url'].split(':')[1]) + f.writeln() + f.writeln('// ciphersuite requested by the policy, as both a constant') + f.writeln('// and mbedtls-friendly null-terminated array') + f.writeln('#define CIPHERSUITE %(ciphersuite)s', + ciphersuite=CIPHERSUITES[policy['ciphersuite']]) + f.writeln('extern const int _CIPHERSUITES[2];') + f.writeln('#define CIPHERSUITES _CIPHERSUITES') + f.writeln() + f.writeln('// client cert/key') + if args.identity: + f.writeln('extern const uint8_t _CLIENT_CERT_DER[%(len)d];', + len=len(identity_der)) + f.writeln('#define CLIENT_CERT_DER _CLIENT_CERT_DER') + if args.key: + f.writeln('extern const uint8_t _CLIENT_KEY_DER[%(len)d];', + len=len(key_der)) + f.writeln('#define CLIENT_KEY_DER _CLIENT_KEY_DER') + f.writeln() + f.writeln('#endif') - print('Generating %s for policy %s' % (out_c, policy_hash)) - with open(out_c, 'w') as f: - _write = f.write - def write(s='', **args): - _write(s % args) - def writeln(s='', **args): - _write(s % args) - _write('\n') - f.write = write - f.writeln = writeln + if args.source: + print('generating %s for policy %s' % (args.source, policy_hash)) + with open(args.source, 'w') as f: + _write = f.write + def write(s='', **args): + _write(s % args) + def writeln(s='', **args): + _write(s % args) + _write('\n') + f.write = write + f.writeln = writeln - f.writeln('//// AUTOGENERATED ////') - f.writeln() - f.writeln('#include ') - f.writeln() - f.writeln('uint8_t _veracruz_policy_raw[] = {') - for i in range(0, len(policy_raw), 8): - f.writeln(' %(raw)s', - raw=' '.join('0x%02x,' % ord(x) - for x in policy_raw[i : min(i+8, len(policy_raw))])) - f.writeln('};') - f.writeln() - f.writeln('uint8_t _runtime_manager_hash[] = {') - runtime_manager_hash = policy['mexico_city_hash_sgx'] - for i in range(0, len(runtime_manager_hash)//2, 8): - f.writeln(' %(hash)s', - hash=' '.join('0x%02x,' % int(runtime_manager_hash[2*j:2*j+2], 16) - for j in range(i, min(i+8, len(runtime_manager_hash)//2)))) - f.writeln('};') + f.writeln('//// AUTOGENERATED ////') + f.writeln() + f.writeln('#include ') + f.writeln('#include ') + f.writeln() + # f.writeln('const uint8_t _VERACRUZ_POLICY_RAW[] = {') + # for i in range(0, len(policy_raw), 8): + # f.writeln(' %(raw)s', + # raw=' '.join('0x%02x,' % ord(x) + # for x in policy_raw[i : min(i+8, len(policy_raw))])) + # f.writeln('};') + # f.writeln() + f.writeln('const uint8_t _RUNTIME_MANAGER_HASH[32] = {') + runtime_manager_hash = policy['mexico_city_hash_sgx'] + for i in range(0, len(runtime_manager_hash)//2, 8): + f.writeln(' %(hash)s', + hash=' '.join('0x%02x,' % int(runtime_manager_hash[2*j:2*j+2], 16) + for j in range(i, min(i+8, len(runtime_manager_hash)//2)))) + f.writeln('};') + f.writeln() + f.writeln('const int _CIPHERSUITES[2] = {') + f.writeln(' %(ciphersuite)s,', + ciphersuite=CIPHERSUITES[policy['ciphersuite']]) + f.writeln(' 0,') + f.writeln('};') + f.writeln() + if args.identity: + f.writeln('const uint8_t _CLIENT_CERT_DER[%(len)d] = {', + len=len(identity_der)) + for i in range(0, len(identity_der), 8): + f.writeln(' %(der)s', + der=' '.join('0x%02x,' % identity_der[j] + for j in range(i, min(i+8, len(identity_der))))) + f.writeln('};') + if args.key: + f.writeln('const uint8_t _CLIENT_KEY_DER[%(len)d] = {', + len=len(key_der)) + for i in range(0, len(key_der), 8): + f.writeln(' %(der)s', + der=' '.join('0x%02x,' % key_der[j] + for j in range(i, min(i+8, len(key_der))))) + f.writeln('};') if __name__ == "__main__": import sys - main(*sys.argv[1:]) + import argparse + parser = argparse.ArgumentParser( + description='Generate header file from Veracruz policy') + parser.add_argument('policy', + help='Veracruz policy file (.json)') + parser.add_argument('--header', + help='Output header file (.h)') + parser.add_argument('--source', + help='Output source file (.c)') + parser.add_argument('--identity', + help='Identity of client (.pem)') + parser.add_argument('--key', + help='Private key of client (.pem)') + args = parser.parse_args() + main(args) diff --git a/prj.conf b/prj.conf index 72d8a4936..0c0e285d1 100644 --- a/prj.conf +++ b/prj.conf @@ -1,6 +1,6 @@ # General config CONFIG_NEWLIB_LIBC=y -CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_MAIN_STACK_SIZE=16384 # Networking config CONFIG_NETWORKING=y diff --git a/vc.c b/vc.c index da3061cd7..36eb03aba 100644 --- a/vc.c +++ b/vc.c @@ -148,23 +148,24 @@ int vc_attest( return -EBADE; } - // check that enclave hash matches policy - if (memcmp(&response_buf[47], RUNTIME_MANAGER_HASH, - sizeof(RUNTIME_MANAGER_HASH)) != 0) { - printf("enclave hash mismatch\n"); - printf("expected: "); - for (int i = 0; i < sizeof(RUNTIME_MANAGER_HASH); i++) { - printf("%02x", RUNTIME_MANAGER_HASH[i]); - } - printf("\n"); - printf("recieved: "); - for (int i = 0; i < 32; i++) { - printf("%02x", response_buf[8+i]); - } - printf("\n"); - return -EBADE; - } - +// TODO OOOOOOOOOO +// // check that enclave hash matches policy +// if (memcmp(&response_buf[47], RUNTIME_MANAGER_HASH, +// sizeof(RUNTIME_MANAGER_HASH)) != 0) { +// printf("enclave hash mismatch\n"); +// printf("expected: "); +// for (int i = 0; i < sizeof(RUNTIME_MANAGER_HASH); i++) { +// printf("%02x", RUNTIME_MANAGER_HASH[i]); +// } +// printf("\n"); +// printf("recieved: "); +// for (int i = 0; i < 32; i++) { +// printf("%02x", response_buf[8+i]); +// } +// printf("\n"); +// return -EBADE; +// } +// // recieved values // TODO why verify_iat response not a protobuf? memcpy(enclave_name, &response_buf[124], 7); @@ -208,6 +209,11 @@ static int vc_rawrng(void *p, static ssize_t vc_rawsend(void *p, const uint8_t *buf, size_t len) { vc_t *vc = p; + + if (vc->recv_len != 0) { + printf("data left!?!?!?!? %d\n", vc->recv_len); + } + // encode with base64 + id (0) ssize_t data_len = 0; ssize_t res = snprintf(vc->send_buf, VC_SEND_BUFFER_SIZE, @@ -217,7 +223,6 @@ static ssize_t vc_rawsend(void *p, return res; } data_len += res; - // TODO change base64 encode order? res = base64_encode( buf, len, &vc->send_buf[data_len], VC_SEND_BUFFER_SIZE-data_len); @@ -246,6 +251,12 @@ static ssize_t vc_rawsend(void *p, } printf("http_post -> %d\n", recv_len); + + if (recv_len == 0) { + // done, recieved nothing + return len; + } + printf("ssl session: recv:\n"); xxd(vc->recv_buf, recv_len); @@ -300,6 +311,13 @@ static ssize_t vc_rawrecv(void *p, uint32_t timeout) { vc_t *vc = p; + if (vc->recv_len == 0) { + // no data available? since we communicate over POSTs, + // we'll never have data available + printf("recv timeout\n"); + return MBEDTLS_ERR_SSL_TIMEOUT; + } + size_t diff = (len < vc->recv_len) ? len : vc->recv_len; memcpy(buf, vc->recv_pos, diff); vc->recv_pos += diff; @@ -315,6 +333,14 @@ int vc_connect(vc_t *vc, vc->session_id = 0; vc->recv_len = 0; + // check that requested ciphersuite is available, this can fail if + // the ciphersuite isn't enabled in mbedtls's configuration + if (mbedtls_ssl_ciphersuite_from_id(CIPHERSUITE) == NULL) { + printf("required ciphersuite unavailable, " + "is mbedtls configured correctly?\n"); + return -ENOSYS; + } + // allocate buffers vc->send_buf = malloc(VC_SEND_BUFFER_SIZE); if (!vc->send_buf) { @@ -327,17 +353,42 @@ int vc_connect(vc_t *vc, return -ENOMEM; } + // parse client cert/key + mbedtls_x509_crt_init(&vc->client_cert); + int err = mbedtls_x509_crt_parse_der(&vc->client_cert, + CLIENT_CERT_DER, sizeof(CLIENT_CERT_DER)); + if (err) { + printf("failed to parse client cert (%d)\n", err); + free(vc->recv_buf); + free(vc->send_buf); + return err; + } + + mbedtls_pk_init(&vc->client_key); + err = mbedtls_pk_parse_key(&vc->client_key, + CLIENT_KEY_DER, sizeof(CLIENT_KEY_DER), + NULL, 0); + if (err) { + printf("failed to parse client key (%d)\n", err); + mbedtls_x509_crt_free(&vc->client_cert); + free(vc->recv_buf); + free(vc->send_buf); + return err; + } + // setup SSL connection mbedtls_ssl_init(&vc->session); mbedtls_ssl_config_init(&vc->session_cfg); - int err = mbedtls_ssl_config_defaults(&vc->session_cfg, + err = mbedtls_ssl_config_defaults(&vc->session_cfg, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); if (err) { printf("failed to configure SSL (%d)\n", err); mbedtls_ssl_config_free(&vc->session_cfg); + mbedtls_pk_free(&vc->client_key); + mbedtls_x509_crt_free(&vc->client_cert); free(vc->recv_buf); free(vc->send_buf); return err; @@ -348,10 +399,19 @@ int vc_connect(vc_t *vc, mbedtls_ssl_conf_rng(&vc->session_cfg, vc_rawrng, NULL); - // TODO depend on policy.h generation? this is from policy.json - vc->session_ciphersuites[0] = MBEDTLS_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256; - vc->session_ciphersuites[1] = 0; - mbedtls_ssl_conf_ciphersuites(&vc->session_cfg, vc->session_ciphersuites); + mbedtls_ssl_conf_ciphersuites(&vc->session_cfg, CIPHERSUITES); + + err = mbedtls_ssl_conf_own_cert(&vc->session_cfg, + &vc->client_cert, &vc->client_key); + if (err) { + printf("failed to setup SSL session (%d)\n", err); + mbedtls_ssl_config_free(&vc->session_cfg); + mbedtls_pk_free(&vc->client_key); + mbedtls_x509_crt_free(&vc->client_cert); + free(vc->recv_buf); + free(vc->send_buf); + return err; + } // TODO remove debugging? hide behind logging? mbedtls_debug_set_threshold(4); @@ -361,6 +421,8 @@ int vc_connect(vc_t *vc, if (err) { printf("failed to setup SSL session (%d)\n", err); mbedtls_ssl_config_free(&vc->session_cfg); + mbedtls_pk_free(&vc->client_key); + mbedtls_x509_crt_free(&vc->client_cert); free(vc->recv_buf); free(vc->send_buf); return err; @@ -379,6 +441,8 @@ int vc_connect(vc_t *vc, printf("SSL handshake failed (%d)\n", err); mbedtls_ssl_free(&vc->session); mbedtls_ssl_config_free(&vc->session_cfg); + mbedtls_pk_free(&vc->client_key); + mbedtls_x509_crt_free(&vc->client_cert); free(vc->recv_buf); free(vc->send_buf); return err; @@ -390,6 +454,8 @@ int vc_connect(vc_t *vc, int vc_close(vc_t *vc) { mbedtls_ssl_free(&vc->session); mbedtls_ssl_config_free(&vc->session_cfg); + mbedtls_pk_free(&vc->client_key); + mbedtls_x509_crt_free(&vc->client_cert); free(vc->recv_buf); free(vc->send_buf); return 0; diff --git a/vc.h b/vc.h index cbb58243a..87f8ac270 100644 --- a/vc.h +++ b/vc.h @@ -17,7 +17,7 @@ #include #ifndef VC_SEND_BUFFER_SIZE -#define VC_SEND_BUFFER_SIZE 1024 +#define VC_SEND_BUFFER_SIZE (4*1024) #endif #ifndef VC_RECV_BUFFER_SIZE @@ -34,9 +34,11 @@ typedef struct vc { // TLS state mbedtls_ssl_context session; mbedtls_ssl_config session_cfg; - int session_ciphersuites[2]; - // buffers for shuffling tls data + mbedtls_x509_crt client_cert; + mbedtls_pk_context client_key; + + // buffers for shuffling TLS data around uint8_t *send_buf; uint8_t *recv_buf; } vc_t; @@ -64,7 +66,7 @@ int vc_attest( // // Note, this assumes the enclave has already been attested, you likely want // to use vc_attest_and_connect, which ensures the enclave is attested when -// we connect +// the connection is established int vc_connect(vc_t *vc, const char *enclave_name, const uint8_t *enclave_cert_hash, size_t enclave_cert_hash_len); diff --git a/xxd.c b/xxd.c index b1fff46b6..ea6c8ef18 100644 --- a/xxd.c +++ b/xxd.c @@ -22,7 +22,7 @@ void xxd(const void *pbuf, size_t len) { printf(" "); - for (int j = 0; j < 16 && i+j < len; j++) { + for (int j = 0; j < 16; j++) { if (i+j < len) { if (buf[i+j] >= ' ' && buf[i+j] <= '~') { printf("%c", buf[i+j]); From 4a3c36e662bca0e552078ed3c962f4951fb9e893 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 26 Apr 2021 10:29:18 -0500 Subject: [PATCH 15/56] Reverted attestation skip --- vc.c | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/vc.c b/vc.c index 36eb03aba..5a08daf7a 100644 --- a/vc.c +++ b/vc.c @@ -148,24 +148,23 @@ int vc_attest( return -EBADE; } -// TODO OOOOOOOOOO -// // check that enclave hash matches policy -// if (memcmp(&response_buf[47], RUNTIME_MANAGER_HASH, -// sizeof(RUNTIME_MANAGER_HASH)) != 0) { -// printf("enclave hash mismatch\n"); -// printf("expected: "); -// for (int i = 0; i < sizeof(RUNTIME_MANAGER_HASH); i++) { -// printf("%02x", RUNTIME_MANAGER_HASH[i]); -// } -// printf("\n"); -// printf("recieved: "); -// for (int i = 0; i < 32; i++) { -// printf("%02x", response_buf[8+i]); -// } -// printf("\n"); -// return -EBADE; -// } -// + // check that enclave hash matches policy + if (memcmp(&response_buf[47], RUNTIME_MANAGER_HASH, + sizeof(RUNTIME_MANAGER_HASH)) != 0) { + printf("enclave hash mismatch\n"); + printf("expected: "); + for (int i = 0; i < sizeof(RUNTIME_MANAGER_HASH); i++) { + printf("%02x", RUNTIME_MANAGER_HASH[i]); + } + printf("\n"); + printf("recieved: "); + for (int i = 0; i < 32; i++) { + printf("%02x", response_buf[8+i]); + } + printf("\n"); + return -EBADE; + } + // recieved values // TODO why verify_iat response not a protobuf? memcpy(enclave_name, &response_buf[124], 7); From 16eb2aafb15fa240255379cd72d87340c2a14b4f Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 26 Apr 2021 15:12:01 -0500 Subject: [PATCH 16/56] Added debugging, some other minor tweaks --- Makefile | 16 +++++++++++++++- prj.conf | 7 +++++-- vc.h | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 1559364a4..567b57725 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,14 @@ DOCKER_IMAGE ?= $(TARGET) DOCKER_CONTAINER ?= $(TARGET) DOCKER_ROOT ?= $(abspath $(firstword $(MAKEFILE_LIST))/..) +ELF_PATH ?= /zephyr-workspace/$(TARGET)/build/zephyr/zephyr.elf + # QEMU configuration QEMU ?= /opt/zephyr-sdk-0.11.4/sysroots/x86_64-pokysdk-linux/usr/bin/qemu-system-arm QEMU_FLAGS += -cpu cortex-m3 QEMU_FLAGS += -machine lm3s6965evb +#QEMU_FLAGS += -machine mps2-an385 +#QEMU_FLAGS += -m 1M QEMU_FLAGS += -nographic QEMU_FLAGS += -vga none QEMU_FLAGS += -net none @@ -19,9 +23,11 @@ QEMU_FLAGS += -mon chardev=con,mode=readline QEMU_FLAGS += -rtc clock=vm #QEMU_FLAGS += -nic tap,model=stellaris,script=no,downscript=no,ifname=zeth QEMU_FLAGS += -serial unix:/tmp/slip.sock -QEMU_FLAGS += -kernel /zephyr-workspace/$(TARGET)/build/zephyr/zephyr.elf +QEMU_FLAGS += -kernel $(ELF_PATH) QEMU_FLAGS += -semihosting +GDB ?= /opt/zephyr-sdk-0.11.4/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb-no-py + # Run in docker .PHONY: docker @@ -83,6 +89,14 @@ ram_report: build run: build $(QEMU) $(QEMU_FLAGS) +.PHONY: debug +debug: build + $(QEMU) -gdb tcp::1234 -S $(QEMU_FLAGS) & $(GDB) $(ELF_PATH) -ex 'target remote localhost:1234' + +.PHONY: debug-after +debug-after: build + $(QEMU) -gdb tcp::1234 $(QEMU_FLAGS) & sleep 2 ; $(GDB) $(ELF_PATH) -ex 'target remote localhost:1234' + # Network tracing .PHONY: tcpdump tcpdump: diff --git a/prj.conf b/prj.conf index 0c0e285d1..a5b70cf89 100644 --- a/prj.conf +++ b/prj.conf @@ -1,6 +1,8 @@ # General config CONFIG_NEWLIB_LIBC=y -CONFIG_MAIN_STACK_SIZE=16384 +#CONFIG_MAIN_STACK_SIZE=16384 +CONFIG_MAIN_STACK_SIZE=8192 + # Networking config CONFIG_NETWORKING=y @@ -56,5 +58,6 @@ CONFIG_MBEDTLS_MAC_SHA256_ENABLED=y CONFIG_MBEDTLS_MAC_POLY1305_ENABLED=y CONFIG_MBEDTLS_TLS_VERSION_1_2=y # TODO is there a specific ECP to use? -CONFIG_MBEDTLS_ECP_ALL_ENABLED=y +#CONFIG_MBEDTLS_ECP_ALL_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED=y diff --git a/vc.h b/vc.h index 87f8ac270..d24f1bb11 100644 --- a/vc.h +++ b/vc.h @@ -17,7 +17,7 @@ #include #ifndef VC_SEND_BUFFER_SIZE -#define VC_SEND_BUFFER_SIZE (4*1024) +#define VC_SEND_BUFFER_SIZE (2*1024) #endif #ifndef VC_RECV_BUFFER_SIZE From 5c995c578ffbf8fa911d3d59b05afaed5785b91b Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 26 Apr 2021 23:20:33 -0500 Subject: [PATCH 17/56] Fixed len issue in base64 encoding --- base64.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base64.c b/base64.c index 1f2c38bc8..dfca19807 100644 --- a/base64.c +++ b/base64.c @@ -39,8 +39,8 @@ ssize_t base64_encode( size_t rin_len = base64_alignup(in_len, 3); for (size_t i = 0, j = 0; i < in_len; i += 3, j += 4) { size_t v = in[rin_len-i-3]; - v = rin_len-i-3+1 < e_len ? (v << 8 | in[rin_len-i-3+1]) : (v << 8); - v = rin_len-i-3+2 < e_len ? (v << 8 | in[rin_len-i-3+2]) : (v << 8); + v = rin_len-i-3+1 < in_len ? (v << 8 | in[rin_len-i-3+1]) : (v << 8); + v = rin_len-i-3+2 < in_len ? (v << 8 | in[rin_len-i-3+2]) : (v << 8); out[e_len-j-4] = BASE64_ENCODE[(v >> 18) & 0x3f]; out[e_len-j-4+1] = BASE64_ENCODE[(v >> 12) & 0x3f]; From 3021c5ef2a2476e0ec683a6907bd04df7371069d Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 27 Apr 2021 01:49:31 -0500 Subject: [PATCH 18/56] Successfully sent data to Veracruz server --- Makefile | 76 +++++++++++++----------- base64.c | 18 +++--- base64.h | 1 + http.c | 5 ++ http.h | 7 ++- main.c | 15 +++-- policy.json | 6 +- prj.conf | 13 +++-- qemu.h | 21 ------- transport_protocol.proto | 15 +++-- vc.c | 121 +++++++++++++++++++++++++++++++++++++-- vc.h | 8 +++ 12 files changed, 218 insertions(+), 88 deletions(-) delete mode 100644 qemu.h diff --git a/Makefile b/Makefile index 567b57725..fa5eee952 100644 --- a/Makefile +++ b/Makefile @@ -6,27 +6,27 @@ DOCKER_ROOT ?= $(abspath $(firstword $(MAKEFILE_LIST))/..) ELF_PATH ?= /zephyr-workspace/$(TARGET)/build/zephyr/zephyr.elf -# QEMU configuration -QEMU ?= /opt/zephyr-sdk-0.11.4/sysroots/x86_64-pokysdk-linux/usr/bin/qemu-system-arm -QEMU_FLAGS += -cpu cortex-m3 -QEMU_FLAGS += -machine lm3s6965evb -#QEMU_FLAGS += -machine mps2-an385 -#QEMU_FLAGS += -m 1M -QEMU_FLAGS += -nographic -QEMU_FLAGS += -vga none -QEMU_FLAGS += -net none -QEMU_FLAGS += -pidfile qemu.pid -QEMU_FLAGS += -chardev stdio,id=con,mux=on -QEMU_FLAGS += -serial chardev:con -QEMU_FLAGS += -mon chardev=con,mode=readline -#QEMU_FLAGS += -icount shift=6,align=off,sleep=off -QEMU_FLAGS += -rtc clock=vm -#QEMU_FLAGS += -nic tap,model=stellaris,script=no,downscript=no,ifname=zeth -QEMU_FLAGS += -serial unix:/tmp/slip.sock -QEMU_FLAGS += -kernel $(ELF_PATH) -QEMU_FLAGS += -semihosting - -GDB ?= /opt/zephyr-sdk-0.11.4/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb-no-py +## QEMU configuration +#QEMU ?= /opt/zephyr-sdk-0.11.4/sysroots/x86_64-pokysdk-linux/usr/bin/qemu-system-arm +#QEMU_FLAGS += -cpu cortex-m3 +#QEMU_FLAGS += -machine lm3s6965evb +##QEMU_FLAGS += -machine mps2-an385 +##QEMU_FLAGS += -m 1M +#QEMU_FLAGS += -nographic +#QEMU_FLAGS += -vga none +#QEMU_FLAGS += -net none +#QEMU_FLAGS += -pidfile qemu.pid +#QEMU_FLAGS += -chardev stdio,id=con,mux=on +#QEMU_FLAGS += -serial chardev:con +#QEMU_FLAGS += -mon chardev=con,mode=readline +##QEMU_FLAGS += -icount shift=6,align=off,sleep=off +#QEMU_FLAGS += -rtc clock=vm +##QEMU_FLAGS += -nic tap,model=stellaris,script=no,downscript=no,ifname=zeth +#QEMU_FLAGS += -serial unix:/tmp/slip.sock +#QEMU_FLAGS += -kernel $(ELF_PATH) +#QEMU_FLAGS += -semihosting +# +#GDB ?= /opt/zephyr-sdk-0.11.4/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb-no-py # Run in docker @@ -63,12 +63,19 @@ transport_protocol.pb.h transport_protocol.pb.c: \ transport_protocol.proto transport_protocol.options ./nanopb/generator/nanopb_generator.py $< -f $(word 2,$^) +#.DEFAULT_GOAL := +#.PHONY: build +#build: policy.h policy.c +#build: transport_protocol.pb.h transport_protocol.pb.c +#build: +# west build -p auto -b qemu_cortex_m3 + .DEFAULT_GOAL := .PHONY: build build: policy.h policy.c build: transport_protocol.pb.h transport_protocol.pb.c build: - west build -p auto -b qemu_cortex_m3 + west build -p auto -b native_posix .PHONY: clean clean: @@ -84,18 +91,21 @@ rom_report: build ram_report: build west build -t ram_report -# QEMU -.PHONY: run -run: build - $(QEMU) $(QEMU_FLAGS) +## QEMU +#.PHONY: run +#run: build +# $(QEMU) $(QEMU_FLAGS) +# +#.PHONY: debug +#debug: build +# $(QEMU) -gdb tcp::1234 -S $(QEMU_FLAGS) & $(GDB) $(ELF_PATH) -ex 'target remote localhost:1234' +# +#.PHONY: debug-after +#debug-after: build +# $(QEMU) -gdb tcp::1234 $(QEMU_FLAGS) & sleep 2 ; $(GDB) $(ELF_PATH) -ex 'target remote localhost:1234' -.PHONY: debug -debug: build - $(QEMU) -gdb tcp::1234 -S $(QEMU_FLAGS) & $(GDB) $(ELF_PATH) -ex 'target remote localhost:1234' - -.PHONY: debug-after -debug-after: build - $(QEMU) -gdb tcp::1234 $(QEMU_FLAGS) & sleep 2 ; $(GDB) $(ELF_PATH) -ex 'target remote localhost:1234' +run: build + /zephyr-workspace/mini-durango/build/zephyr/zephyr.exe # Network tracing .PHONY: tcpdump diff --git a/base64.c b/base64.c index dfca19807..2b796babb 100644 --- a/base64.c +++ b/base64.c @@ -61,15 +61,15 @@ ssize_t base64_encode( return e_len; } -static size_t strnlen(const char *s, size_t s_len) { - for (size_t i = 0; i < s_len; i++) { - if (s[i] == '\0') { - return i; - } - } - - return s_len; -} +//static size_t strnlen(const char *s, size_t s_len) { +// for (size_t i = 0; i < s_len; i++) { +// if (s[i] == '\0') { +// return i; +// } +// } +// +// return s_len; +//} // compute size after decoding size_t base64_decode_size(const char *in, size_t in_len) { diff --git a/base64.h b/base64.h index 1d728cf56..d8f7d2923 100644 --- a/base64.h +++ b/base64.h @@ -5,6 +5,7 @@ #include #include +#include #ifndef BASE64_H #define BASE64_H diff --git a/http.c b/http.c index f403659f8..51164e0bb 100644 --- a/http.c +++ b/http.c @@ -192,6 +192,11 @@ ssize_t http_post( // done, close free(pbuf); close(sock); + + // There is some sort of overrun in the network stack, sleeping here + // briefly avoids issues + k_sleep(Z_TIMEOUT_MS(100)); + return state.pos; } diff --git a/http.h b/http.h index f058168fc..f843a8cfc 100644 --- a/http.h +++ b/http.h @@ -6,12 +6,17 @@ #include #include +#include #ifndef HTTP_H #define HTTP_H #ifndef HTTP_TIMEOUT -#define HTTP_TIMEOUT 3000 +#define HTTP_TIMEOUT 10000 +#endif + +#ifndef HTTP_RETRIES +#define HTTP_RETRIES 3 #endif // HTTP GET operation diff --git a/main.c b/main.c index b1aed56d7..fc8197476 100644 --- a/main.c +++ b/main.c @@ -9,7 +9,6 @@ #include #include "xxd.h" -#include "qemu.h" #include "vc.h" // Veracruz client @@ -21,16 +20,24 @@ void main(void) { int err = vc_attest_and_connect(&vc); if (err) { printf("vc_attest_and_connect failed (%d)\n", err); - qemu_exit(); + exit(1); } printf("connected!\n"); + // send some data + err = vc_send_data(&vc, "input-0", "hello world!", sizeof("hello world!")); + if (err) { + printf("vc_send_data failed (%d)\n", err); + exit(1); + } + printf("sent data!\n"); + err = vc_close(&vc); if (err) { printf("vc_close failed (%d)\n", err); - qemu_exit(); + exit(1); } printf("closed!\n"); - qemu_exit(); + exit(0); } diff --git a/policy.json b/policy.json index 777e1d162..97c2940ca 100644 --- a/policy.json +++ b/policy.json @@ -2,9 +2,9 @@ "ciphersuite": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "debug": false, "enclave_cert_expiry": { - "day": 4, - "hour": 9, - "minute": 58, + "day": 5, + "hour": 4, + "minute": 29, "month": 8, "year": 2021 }, diff --git a/prj.conf b/prj.conf index a5b70cf89..59dce7396 100644 --- a/prj.conf +++ b/prj.conf @@ -2,12 +2,13 @@ CONFIG_NEWLIB_LIBC=y #CONFIG_MAIN_STACK_SIZE=16384 CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_SIZE_OPTIMIZATIONS=n # Networking config CONFIG_NETWORKING=y CONFIG_NET_IPV4=y -CONFIG_NET_IPV6=y +CONFIG_NET_IPV6=n CONFIG_NET_TCP=y CONFIG_NET_SOCKETS=y CONFIG_NET_SOCKETS_POSIX_NAMES=y @@ -18,8 +19,8 @@ CONFIG_NET_SOCKETS_POSIX_NAMES=y ##CONFIG_NET_SLIP_TAP=n ##CONFIG_SLIP=n -CONFIG_NET_SLIP_TAP=y -CONFIG_SLIP=y +#CONFIG_NET_SLIP_TAP=y +#CONFIG_SLIP=y CONFIG_DNS_RESOLVER=y CONFIG_DNS_SERVER_IP_ADDRESSES=y @@ -34,10 +35,10 @@ CONFIG_TEST_RANDOM_GENERATOR=y # Network address config CONFIG_NET_CONFIG_SETTINGS=y CONFIG_NET_CONFIG_NEED_IPV4=y -CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" -CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV4_ADDR="193.0.2.1" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="193.0.2.2" #CONFIG_NET_CONFIG_PEER_IPV4_ADDR="172.217.1.228" # google -CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV4_GW="193.0.2.2" #CONFIG_NET_CONFIG_MY_IPV4_ADDR="172.17.2.1" #CONFIG_NET_CONFIG_PEER_IPV4_ADDR="172.17.2.2" #CONFIG_NET_CONFIG_MY_IPV4_GW="172.17.2.2" diff --git a/qemu.h b/qemu.h deleted file mode 100644 index 8a51b5c3c..000000000 --- a/qemu.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * QEMU utilities - */ - -#ifndef QEMU_H -#define QEMU_H - -// hack to exit QEMU -// TODO can we integrate into Zephyr somehow? -__attribute__((noreturn)) -static inline void qemu_exit(void) { - __asm__ volatile ( - "mov r0, #0x18 \n\t" - "ldr r1, =#0x20026 \n\t" - "bkpt #0xab \n\t" - ); - - __builtin_unreachable(); -} - -#endif diff --git a/transport_protocol.proto b/transport_protocol.proto index e4cf9d940..009451b5b 100644 --- a/transport_protocol.proto +++ b/transport_protocol.proto @@ -1,4 +1,4 @@ -//! Protocol buffers for Veracruz transport protocol messages +//! Colima, protocol buffer for Veracruz //! //! ## Authors //! @@ -10,7 +10,7 @@ //! information on licensing and copyright. syntax = "proto3"; -package transport_protocol; +package colima; message SgxEc256Public { bytes gx = 1; @@ -85,11 +85,12 @@ message SgxQuote { } message Program { - bytes code = 1; + string file_name = 1; + bytes code = 2; } message Data { - uint32 package_id = 1; + string file_name = 1; bytes data = 2; } @@ -144,9 +145,11 @@ message State { } message RequestPiHash { + string file_name = 1; } message RequestResult { + string file_name = 1; } message RequestProxyPsaAttestationToken { @@ -200,7 +203,7 @@ message ProxyAttestationServerResponse { uint32 context = 1; } -message RuntimeManagerRequest { +message MexicoCityRequest { oneof message_oneof { Data data = 2; Program program = 3; @@ -217,7 +220,7 @@ message RuntimeManagerRequest { uint32 context = 1; } -message RuntimeManagerResponse { +message MexicoCityResponse { ResponseStatus status = 1; oneof message_oneof { Result result = 4; diff --git a/vc.c b/vc.c index 5a08daf7a..b0c90abfe 100644 --- a/vc.c +++ b/vc.c @@ -20,7 +20,6 @@ #include "xxd.h" #include "base64.h" #include "http.h" -#include "qemu.h" //// attestation //// @@ -45,8 +44,8 @@ int vc_attest( printf("\n"); // construct attestation token request - Tp_RuntimeManagerRequest request = { - .which_message_oneof = Tp_RuntimeManagerRequest_request_proxy_psa_attestation_token_tag + Tp_MexicoCityRequest request = { + .which_message_oneof = Tp_MexicoCityRequest_request_proxy_psa_attestation_token_tag }; memcpy(request.message_oneof.request_proxy_psa_attestation_token.challenge, challenge, sizeof(challenge)); @@ -57,7 +56,12 @@ int vc_attest( uint8_t request_buf[256]; pb_ostream_t request_stream = pb_ostream_from_buffer( request_buf, sizeof(request_buf)); - pb_encode(&request_stream, &Tp_RuntimeManagerRequest_msg, &request); + bool success = pb_encode(&request_stream, &Tp_MexicoCityRequest_msg, &request); + if (!success) { + // TODO we can reduce code size by removing these error messages + printf("pb_encode failed (%s)\n", request_stream.errmsg); + return -EILSEQ; + } // convert base64 ssize_t request_len = base64_encode( @@ -259,6 +263,9 @@ static ssize_t vc_rawsend(void *p, printf("ssl session: recv:\n"); xxd(vc->recv_buf, recv_len); + // null terminate to make parsing a bit easier + vc->recv_buf[recv_len] = '\0'; + // we have a bit of parsing to do, first decode session id const uint8_t *parsing = vc->recv_buf; vc->session_id = strtol(parsing, (char **)&parsing, 10); @@ -437,7 +444,7 @@ int vc_connect(vc_t *vc, // perform SSL handshake err = mbedtls_ssl_handshake(&vc->session); if (err) { - printf("SSL handshake failed (%d)\n", err); + printf("mbedtls_ssl_handshake failed (%d)\n", err); mbedtls_ssl_free(&vc->session); mbedtls_ssl_config_free(&vc->session_cfg); mbedtls_pk_free(&vc->client_key); @@ -447,6 +454,7 @@ int vc_connect(vc_t *vc, return err; } + // success! return 0; } @@ -481,3 +489,106 @@ int vc_attest_and_connect(vc_t *vc) { return 0; } +// helper for encoding dynamic-length bytes/strings +struct bytes { + const void *buf; + size_t len; +}; + +static bool vc_encode_bytes( + pb_ostream_t *stream, + const pb_field_iter_t *field, + void *const *arg) { + if (!pb_encode_tag_for_field(stream, field)) + return false; + + struct bytes *b = *arg; + return pb_encode_string(stream, b->buf, b->len); +} + +int vc_send_data(vc_t *vc, + const char *name, + const uint8_t *data, + size_t data_len) { + // construct data protobuf + Tp_MexicoCityRequest send_data = { + .which_message_oneof = Tp_MexicoCityRequest_data_tag, + .message_oneof.data.file_name.funcs.encode = vc_encode_bytes, + .message_oneof.data.file_name.arg = &(struct bytes){ + .buf = name, + .len = strlen(name), + }, + .message_oneof.data.data.funcs.encode = vc_encode_bytes, + .message_oneof.data.data.arg = &(struct bytes){ + .buf = data, + .len = data_len, + }, + }; + + // figure out how much of a buffer to allocate, this needs to hold our + // sent data + the response, response is fairly small and honestly could + // be smaller + size_t encoded_size = 0; + pb_get_encoded_size(&encoded_size, &Tp_MexicoCityRequest_msg, &send_data); + size_t proto_len = (32 > encoded_size) ? 32 : encoded_size; + // heh + uint8_t *proto_buf = malloc(proto_len); + + // encode + pb_ostream_t proto_stream = pb_ostream_from_buffer( + proto_buf, proto_len); + bool success = pb_encode(&proto_stream, &Tp_MexicoCityRequest_msg, &send_data); + if (!success) { + // TODO we can reduce code size by removing these error messages + printf("pb_encode failed (%s)\n", proto_stream.errmsg); + free(proto_buf); + return -EILSEQ; + } + + // TODO log? can we incrementally log? + printf("send_data: %s:\n", name); + xxd(proto_buf, proto_stream.bytes_written); + + // send to Veracruz + int res = mbedtls_ssl_write(&vc->session, + proto_buf, proto_stream.bytes_written); + if (res < 0) { + printf("mbedtls_ssl_write failed (%d)\n", res); + free(proto_buf); + return res; + } + + // get Veracruz's response + res = mbedtls_ssl_read(&vc->session, proto_buf, proto_len); + if (res < 0) { + printf("mbedtls_ssl_read failed (%d)\n", res); + free(proto_buf); + return res; + } + + printf("send_data: response:\n"); + xxd(proto_buf, res); + + // parse + Tp_MexicoCityResponse response; + pb_istream_t resp_stream = pb_istream_from_buffer( + proto_buf, res); + success = pb_decode(&resp_stream, &Tp_MexicoCityResponse_msg, &response); + if (!success) { + printf("pb_decode failed (%s)\n", proto_stream.errmsg); + free(proto_buf); + return -EILSEQ; + } + + free(proto_buf); + + // did server send success? + if (response.status != Tp_ResponseStatus_SUCCESS) { + printf("send_data successfully failed! (%d)\n", response.status); + return -EACCES; + } + + return 0; +} + + diff --git a/vc.h b/vc.h index d24f1bb11..252c2e7c2 100644 --- a/vc.h +++ b/vc.h @@ -71,4 +71,12 @@ int vc_connect(vc_t *vc, const char *enclave_name, const uint8_t *enclave_cert_hash, size_t enclave_cert_hash_len); +// Send data to a Veracruz instance +int vc_send_data(vc_t *vc, + const char *name, + const uint8_t *data, + size_t data_len); + + + #endif From 3c9cb4a48fa6e6b5f05e03788055358924227b2b Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 28 Apr 2021 02:18:52 -0500 Subject: [PATCH 19/56] Got skeleton of demo working, just need to work on presentation --- Makefile | 24 ++- claps.wav | Bin 0 -> 2824236 bytes claps_to_header.py | 186 ++++++++++++++++++++++ client_cert.pem => example-data0-cert.pem | 0 client_key.pem => example-data0-key.pem | 0 example-data1-cert.pem | 23 +++ example-data1-key.pem | 27 ++++ example-data2-cert.pem | 23 +++ example-data2-key.pem | 27 ++++ policy.json => example-policy.json | 10 +- main.c | 105 +++++++++++- prj.conf | 3 +- vc.c | 5 + 13 files changed, 422 insertions(+), 11 deletions(-) create mode 100644 claps.wav create mode 100755 claps_to_header.py rename client_cert.pem => example-data0-cert.pem (100%) rename client_key.pem => example-data0-key.pem (100%) create mode 100644 example-data1-cert.pem create mode 100644 example-data1-key.pem create mode 100644 example-data2-cert.pem create mode 100644 example-data2-key.pem rename policy.json => example-policy.json (96%) diff --git a/Makefile b/Makefile index fa5eee952..9f71891ce 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,11 @@ DOCKER_ROOT ?= $(abspath $(firstword $(MAKEFILE_LIST))/..) ELF_PATH ?= /zephyr-workspace/$(TARGET)/build/zephyr/zephyr.elf +CLAP ?= 1 +CLIENT_CERT ?= client_cert.pem +CLIENT_KEY ?= client_key.pem +POLICY ?= policy.json + ## QEMU configuration #QEMU ?= /opt/zephyr-sdk-0.11.4/sysroots/x86_64-pokysdk-linux/usr/bin/qemu-system-arm #QEMU_FLAGS += -cpu cortex-m3 @@ -51,13 +56,22 @@ update: # TODO move these into west? # Generate policy.h/c policy.h policy.c: policy_to_header.py -policy.h policy.c: policy.json client_cert.pem client_key.pem +policy.h policy.c: $(POLICY) $(CLIENT_CERT) $(CLIENT_KEY) $(strip ./policy_to_header.py $< \ --identity=$(word 2,$^) \ --key=$(word 3,$^) \ --header=policy.h \ --source=policy.c) +clap.h clap.c: claps.wav claps_to_header.py + $(strip ./claps_to_header.py $< \ + -b 100 \ + -B 30 \ + -A 170 \ + -c $(CLAP) \ + --header=clap.h \ + --source=clap.c) + # Generate transport_protocol.pb.h/c transport_protocol.pb.h transport_protocol.pb.c: \ transport_protocol.proto transport_protocol.options @@ -73,23 +87,25 @@ transport_protocol.pb.h transport_protocol.pb.c: \ .DEFAULT_GOAL := .PHONY: build build: policy.h policy.c +build: clap.h clap.c build: transport_protocol.pb.h transport_protocol.pb.c build: - west build -p auto -b native_posix + west build -b native_posix .PHONY: clean clean: + rm -rf clap.h clap.c rm -rf policy.h policy.c rm -rf transport_protocol.pb.h transport_protocol.pb.c rm -rf build .PHONY: rom_report rom_report: build - west build -t rom_report + west build -t rom_report -b qemu_cortex_m3 .PHONY: ram ram_report: build - west build -t ram_report + west build -t ram_report -b qemu_cortex_m3 ## QEMU #.PHONY: run diff --git a/claps.wav b/claps.wav new file mode 100644 index 0000000000000000000000000000000000000000..453bc3b883a8cadb942d89a8f02a1de4165204d0 GIT binary patch literal 2824236 zcmXV&1C*UhvxcjC$Hv6AZQHi3Ik9cqwrwXTwr%^w_Uy0gKC|ziwR&}bZPi;V*Ca=UtU3-Xn?Uy(WGv z5xr{?$9p2`zbgOvcgtD-47uyyBoF*+<(Yr0eDLp*+x{hT+CN)%`v=N4e}9?lZ!Q!3 z4Q0B&32_~nNSN)fN4ma@@mH2fe2?(gkYWBRGQ?j&TKh9g9e*lm@6RS3{kdg;zksy# z=agFh6w-#W8pLJ%5u~a=4s}yWO@9K)5=u#b7%Aj;TGFo?T3(O5V&v8E$CAQ+OPOkD z-RAxzw96_@{mG=QKfQDy-O-;(8dBEEpIYjZ-;#bC`QuY36(NCCrjOc$>i($Y$B`=Z zU(EkU%lo4-*BDaNF9cs8FTVe##`WLQ1capiCmPRxNu&BN@O?*P_%CW={}qknzp63) zmo=9EnEnjep<(^|G@}2c{tel$UdTRu5;8|GhD_1Bgli!)^=il%JrvSUH;4SAi$m(` z0>U!F{E*r@GNhQ!2q~-6L(1sfkV-l%q=-%kDWvm4D(F(`%nPYVd3EBFIwGX7&I~E7 z;|a?{>go274!V(ePe>=d9x_TVGtLvnd=s)nzl5yR@5Jvy=IY0g75YA8F>@NqoW|+V zkY2hkq_wUIX|4sy ztb0S65}NA{;xi%rNw;B+m1v(|N7HURVPr@S=3bod>^hkK$Ao0nsUZdEH!ox4CY_&< zk9E}3^&xe2CF8Fl-WXDg@7j7Qq@$h<>C2o)>*tW!g!%f3_1tG2_d|y0gOCxF4PcE^ z^;}2~y-og0*7rMPsapSPjpE;~LH=DDjySe|r-t!wQ1P!*4Oy#y*qgs0^GGkz@Z=}< zpVpL|VOGw%tpAN>@}JSP{!^U)5z04eH2-GKc@^LLH5d0FkN>U~^553n+>@fjdHpXm z8+Rj{|Ar>>A11y=-VH6lJ;_O(vi@gU(f?L!`9EuKe_ZM3kIY>R(N^5U3jUYc(Em+4 zaNj%oBO+sIrISC3jORX&@F$mf$kIxG5kf9m5lT+KTMwcdn3=I{Pu>M@7_q`b5l%gx6(v(3-}&q{_}d7 zuih~8*&9kc(7g5fn44ZOad-2<>u=t8JHR~$-KpF2e|-<&yQ{h8H8K|o_o(;OYe%~l=CW7I-1X|4kBsxoYtK9e zm~-Uq^NN`DUUswEOK0YIY0LsIk(uiSnc-eo)7E>>9^I0<>`yB~Yws!FH>EWDTfo~V zdA(hd+1tQg@08@;Ji;nT@2!*g-du^~jg`pWWckiHy#QM;g0Z*#6*<3Ja+Py@;%`h` z5e&}E8E2G5{zPDHBrw<0Uj9$o#s5zG_`ho>|10jze_GjpP7C_Cf}Q)cxc>n0A>v)! z&rQTfxMPR4uz$DaC$Ery5Ak;DZ_;x9qgsr-a@^~D{%zDduhpqr23cr~3^W4w8iRpt ziG#tzevCWZ|CKxZOzVS#RlvY%{v=$bR~hrZcK1Kmrljk$zADVW6mxCJ+{%!y zL@4Org8Z#!Z7Vd3e}Sg)&qa0@B73Vfy?+{VxI&Bg*Ms%{Qa+D$ub^zDX7*un}7kqtZRt>ePI4Q{r7Yrdp@4MUyH0R^GBDB=#a_C z@<#N_E_6<4{wjZvY@uu^y6X};>byUPoISueG@jtt)NG9%Xo!G$v9=QYp00;EeI|CPX$s>YAZ{-#X5nkKqyX`;CL z#BEJH*Ty7s-Az_Ez~pyBOmR2UlyNgmWjDtZa^p-6^7FZ|Cc7JCQd2Lp8)g!?U_u*{ zh&Y|=O!;_|%Z)Hus9S`%pc`pYx^C1RK>lD;)QvHD=%WbV$y`tR>SqeL(eyvjlmqXo zfOQSsQd8bdH>Jrd;D*y~kV(OKDP4P$&^0n~TyvAt4Ww-^#%;rQ==>X)_~a*N9VuLE z=0B1!$mDjtSx-Nc(RJi|gvm=@LFSR0_Mzzlt{O`V~Hf$m?^-mN!1-Co*n zF@4=m+8;3e3EkW_)5Wbay@-3eJ&duAdfQA7x0P{Dm&AwW-s|?Tv+>`OKv@v)5g}Sfsjb+*+kmJ=Fop^E^TiMXfIns z$J^$*z*Zuzuaj&&oo);309!>z*+P_OrhZ~=Xk%$f8$&DE^xD|Q)yg)W*0ia$F8$Rf z)U#Q&1MPa+(mIeZz?Ra%^f%sC(~-7_cC%%4gw3mMsndh&|fO8L)}`; zwG921w{f%-Z7SLfl%*%+*VZ<#CcmoBD#>LS{!oXtu1$*koL zY;jl3W_O?XmRau3nep7AiJbpPx7N((Ud(ofIo|_jrrT}CayKSZHi3Jx%pIftMKg-K zu$p!YxO2N0w?KgY}TfyvSHFQQI*pk;(FlAj`@>-ax;7$`Tuc2#a zf?a>p!u4T~JDNIRPg~c;bOb*~foDtIYR-I>*}yp;=6+lOD;^`eZ`^P5)p4y{bnDZY zHkzhJ-qPEQn%Ab|zGc*0Hm4Thz80`Sn!qNbEQMx6elpwSgjC$e41|)%N@e7faM&#GvyAkr#f;-;&p`5B0X)sRv0PuzU5mJ+Ig7Nxf>1={dVjH`%$m)GpH%cB;;@Bfx<% zV9O94ZO7>t;$e2SPN8g$U9B7JQ9VYx6SO}=zfbKwePW*zf7Ex@)4w*dMD;}^{MBE9 z?>G{lZ=WxWMDm3r{;t2N`@z1@hqQZcAL$kQQeW8jfqtIZFZ$B{(mytg{I#Fx^P_$V z)PF_(OMOOoVsGko#=T;1k$y-1H~mKZ(Tb?`Nd#Xc3G2%y8GKm?i5S0(6!sO9qQ2Tv z!Ph_<`C3X#UlVEQ`$wAl+DJ2BJ!!&sZOZHV>PTr{FnOV6{iTm@j12P)m;SyH($_ak zy80%|DBnowMi}lJB<+2JrKhjAbPj~>#9ibcUn8mEt09$qxgK6zo-#K%<~Dewiq0><{FIS+?7Nh4jq30Hujc&GC=O%-ZUBS>!rjx5}n!Ad~Sq(6)HkeiqIV+Fe zY=|zcZbrC&sN3H3A-^k_H3B(ohpp2K+n~0~L&yjAWie@81n|m-%>R;{E+M!UWJ-WT zWs&`G4weEyP_Xh*`K&G7}=fvgTaB$gf^}u^C@PUfkExTn15Uu z@Sq@gkrg{48+e=9R030qgJC7Woq~*2)Fr~!NCcJzF?I~{<1??6w95>x$)?PZ5I zST=gY<(#)j4tR^@ls7|;cyr{eHEWlr>&I@}|mW#$W3V z!iMe5T!xe1LzXb^dau76#J4+4on78|*+RXYl&|yp$O_6eVn-*!Pnf+_YvxZrcZmbnddmUN52mk=aILGvDeC5Z@c{RPReuk z{2||u80)WhkvZ+;Y_`i|>Rw~6SJ;npjD4DQTw@(inENf}@t%4gygd@$otNb9wFJ3) zlE%F!{huUp&n1QXAnEwd>r_g+aNL^^$?bkhap$Chi--<3=<6R+)kWt%h2ai4$>6?A zR_YXXe#uA3=DtaK`bb3I@d=sT7s*9l2KPo%xtDyulhmYhxvx@?cE#v3w4X-Y%?jMR zN-i39v5$Kgk-HjN9wJ4llaW62@SVmzmc;Ik#BvuUiaRMWSxanpj(d7kzHwGz-2Om1 ziaQ{Y-685;Wt|r#j=R8|E|Pa%61W?ZjC-BYy^sR*o7=sT^30(c<5XkpnuNwK9=(tBD$aLtvj}`xf~?L4FD4TYLjH#%m)(%F0m$Vb z+6{Ht%_L-a0obvKumsH5fxK@)ub)JBAHi?9k1z8H{>1~6-h)Z^v7_#hcZjm<_z$=E zK8x-@g&%PUUuFk5F~?QHCd&(6WWYu%2zF!vd%~Ey>}d@`O=rP_A5w{ZFG?uI8DwV< zQoCz}3zC$5iRKQ9zla!o;aa*Xfnf!g6(LT=RxI`i^ox8+Y9Fr{WK70O*v2U~g_sI`Em$TGaC(77g zxVO)d`R9yz+Z)0?AA;QX;x2b)9>e60H-h|L@*lGDC{U-jT=0Trt5+SFuOXYfDl*o~ zD?_~u_`7g*UUV=hIzDy+eC;T3VXCdXFw)ZV;TI>7US1r^;z>U*7C4kahI=VwqL)rO zdeNk-7eVTHA==dYsa?Go(%Fj$pB7I3p?xjSXd8+omzRE#&XhoZc0}S7?Xr1iG#`DX^$uz*%94AB8FRCy z^7iw+RwH@KG>W%KjW=8W`ls@}ToZa5H6C+IN8NPZ9*yX2qR#`&?-b+T)?(f*E$2Pb z+TI(!?`uKkU4Z$e^mYWs$>W{YyoB`3BPsoa@#eDLRm^oQb6lkf=r1~BM)y{#=O3@X z{bSVcpH6y`hWKabH~(<`;h&(-{k`C25ADYJ_2KOLaBd^K zAap@y?ny2%w<5UMQof=KqPe-;*@fKU?cmoQFz_(xE!@rRlHZ*sZwGgGkCbt@z`KLs z*KzVsfoJ=;!@I$dLsE#lT#@p6U{rheS?ancQU}ay2S)w_4t7KSx_~nyU3l;!E*Ovo ztVo8RmBnmBm(Rto*ywVB0p+mStC$nm5of@ZJIMDZ4w%_rqeKNs@l_@r{EV0oE;bg3}GTj;( zYX9JaTN#OsIL*e9h1j*Dt>U9cqdW{DicF>6*&3MKgg00z`vOa?VVJ`&zCU44KF7a!28VD4JMx^C zwJ@jH{iR5kvzs+H>n}xKZhMOFJNPbF@YO@(x9r1nxQJWq$vu4e%lI!aqxcF<*oTJL z`wg*S8`wi|11I4sPGOs_#U@>&DeN?jitl0V0*!_55!MdYzh<0zrl)>4|LA|FhrTmy z^tEZHw@nkh!`VDAt@W`9)@P=(zB0`MXLo}0+iCLYW|NsSO|OehQk`y6>l~9oXP8{N z)fCV*CXengd3CKxp$n+9$i&p4Cb|wYQFXZag&z42U->@1>NQuHds7Ckpq{zQJ$iv1 z`I_`6c(=!tf5M;sgm3u+U(#r&KwEV^%r|^aqrK5R-MDM*C~r=^X84UY%?H<%bQ9vn z^k0m=>!6p)n@8N&hm7$69rTiVzp-KeU}I?5!X?O(?EzGrwi)|tKAv1y-BH)=zl!@k6{lNn3C*q3FIc1&SO89n$qkyFJ`8!ZZr+` zh-sv!Ihzy6&blF+WX?6nZ%AC1`V9ze^}Ok#XShGtOef~tM<1Fo`o?qv13D81a>x4UT`=I0=})Ln z+s1mxw9;eT$E$(yDzmmcfqhuO9?xK%6HO-8lZib`&RSA%ZfSIcNyZ(^Kz=msXu@eT z?nh(d=A1)U>JB%>bUy1^!}?a6O1h4@hOT=(^WDuFw(`A(HLYXpeeCZZ?)x6nM@>uC zRA09O;PKc7-~#Qh%E*8W}(SFKH$1vvZKG#nhWmJXb%N zW%`Hj?`DKPWgqS$YsXAqyEJoqqMzcCh8M2S>Kyk?CU(nSfgR^VPkQ_^kp^r? z3~v#}uF@X{O-!8(;8#Vku@M+m5?-LF{S5y6A@8Xc277XVGkNVb@Z>JxI(hw-dw3VSeu z`!a&`G`-LM+%`k`?#BB2>ILq}ank!ZkM-QIWyI?^yVab_2GgDV*4)Q_$V02ZSuQjk zxRasv`;#|{z3jza_eXYSu!pm_hfDN@S}g0Qc7FXR``S zT&f?;dd_Vrb!O3S7Il`9zd^sy$0zDPHrv&2&S*5tYcUv{7+t33G`{~0()TqPdzT!1 zj|G;eU=I`6iyF`V#~!}YEMRyl@H+{*EuDP~mvx)^_o;uAdQYkUoU=Jbzc*-qhk6Gz z#PF(Yw=@5h%xOLGMU6e!rVT)j+tP1)#_LJjCXAUK{LQZafR_!?QRTqPvS4)~c*yTRTVSJSJ_y>crt@^vQ(#p-oo*Em7tGl7tVFRS3>xC`W6#J_Y zzGY2(kaGAOrSLxr$xkn%e8#?i<0Zmx4#JO5g3k~GpCJ~0MizXS^!O4{<*Fy}2VwE? z|L8^UyPm^d+<*JD-qD0%*tJu=L*(z#!QL7j#CKb7 zmIh;+HufgqGfcrZ7^vmFzW4`yuu;46-4naCt5)-RV^{x!o!U-wd5!TA%Ht~(*2rD~ zb^g@))1L~PII+I-$JN*V7}&#!)p{AoPltV;U4y*h_yD=E+e=~Fm%|n>qiMX#_zyMk zDeBQ*8SL&-*yF{p%c}-LeA2PKyx9Kfv9Y6KhbJZ^(y)R43Tk-bn2aBfzEgW8=(jR< zeNFnTMxPCsZ$o^M=31CI8{b8|CR*AH#@_Bj+?KLtTHWiS^}Qil!|SdMyg^!%bvE>d zX+8F!wKobMVkqg})NiFFyr%3;JNBXvaVz|mdf4yv7%y}!rL{Qy*QbxlUI+HCJ8>84 zcElHHt5u1Ey%E}k@>*VJ>h)*u`)OxylD1*IF5YDA@6EveAFBO`hkHvnt4*BUTAhW@ zFvr`ibMcRM;@e!tN4VmB(%arIeTGl?ANS!EKHfKcM#X3R;Kjs0h%Mgy(vIzfrF@DENw^x?JZSW=|oA6CG<1-(^ zci-VI%W-#?^j*0Ok8>5-xr3i^2cG33zQ;@VOU}T<>~+`W2IcoDyGz*<_eb92hup(2 z3C(|q+`WPG``{vDLwRx!d3*pL^br2#I%RjLcZcuYa41{x^S9y?ZDJ0q@P8J&t$bgQ zWytD6{Gg@yOq1Li!YUc%=E-EYTxJm_xn(kw{JHQ0gIMDz);G^>lc{bAeQc4n@CZBL zbT;B&t#kj%QMj9J_*lCcCv@B+?k2c!6^!`*yu*ASU|qYYzluFt3@6dsO_$MbCVtjP z;VncuxPHRBiPUhd+3OzgOr52*>n6P@8|bFWP&YwFxLM3+qKtP7WPqDQ9}_u`!L%PJ zysyx%746$e30F~axRUS~Il-H>lF(%$of-Zk9sEU7&OR%CWCHR-^U~uxW`g(1#l1)h zPnDFsG;kg{B(6&YCzTM43NP=x_Zq^T^LvrGTVM4Re8M$+*<0Q#{R}2O^*-ox@2$ST z|GrIp3r^s=_ki>hy+FNP-U&T~KfVut{RTeo8Tf*0`0ZD?+iT$s_P_;f0JoNN@8@|7 zbg{QWSK#x{^yYwPbHK0h$jVq`WH2(+ozMa73Py%&AgATPtSZP}b!4w9dG(RETKL+P zwGr~!87%9L47Nu$`y-D7>0==BHy)W=$v8{Br8PN^bYB#glFD!@*fdDVXtmc_c7tBKJcDW{zU(J-}EcI&3Er3=@5NK{kznA$=<$X z?|-5TzEJ+adlu;D1?@xoz0028fd_fbSWnpRTktKny?fNV#(7*|ekb%QdB@Qg2M8zl zenY*F;LH!s_BG?Yq0blIf6V6|-?syGuT$?|_@7Ix?-HEQEza?-cL$wxhPgfCY;Wj8 z=J%XATxac}V_%~F3HD`=w^dIw)+z6Z9%he1^Da_$lK3TaeZ`pX*o#kr_=(=6|LgSo zh<>i1+s>oM&Y;Kk)6Y?K+fMGv9`=7LxVedQ+{yWEgLC?q{GHx<&UGedG=ei5%e|Vw zxlPt}-a_3>y)DGsy>+ZEu4G>Y{ zQ755`0%wy98B7F!6IaqwR@h~L>n#8V7X**<5vocR(sf-K@V7Fdy3}^XrKPI{CbT7V z0FN6=XYjm(YXCMhq^zn80T0H44Kv*+SptSkz#duYrh$X=WewQ2l=M#UZUbSPTOp^w z#FOCDQLu8Qn*zqo04vAHa`1i)n6;kzo7`fsd@DG&k@QkIM|=o;JPo#81^b_XgSWxH zD`4YmIBe0k5<$O9E=`QToC3S996n@aY`z-UXKk>_+hU)0#$IoZ9bX$8tu=WqvDw;V zqcz5EYmIL(#4IAr*Rj}_Gt6TA+x7T=bMgO1X$Ne)9%d5pSbV)%+K>93@FRk$KaBjo z*q>eSpSqClP5qw49q|>KnW^~SlW04KzUSd9cV?^++QJOecK9Fz&1~}K(Z@!ehHX0> z-)fqf&RjQQHy**>+<{+u7CRE_%Ybs`AwJ4a{EPqa&n{tee$k8O3i;RZrB7ggp22p# zfQ@>TcrWYNh0lJLvPaZ?gpYF<|M#VyrJs}L5Vr3TY}L#7(RcAXU*H#>!R8HZe*wQ| zJO1oWc!$mG#d7>!tOfd7Z#FUC#jIx)^PS0hH^alt)n3efEPL3C{TV>sXy!hPdCX^? z8yRC;U~UJQ&tCF&2j(`NdSlFfz7Ny)HrBiV-e4~KH-|k~$X+jHoYmOtYZ+@j>1B)^ z+Ge#5#xD)VZ>x!YUkP8jFz49_f2=xrmEoI8VaJ!^yi37hJY`c%T{O?iht{>v#{NHz9dRC6dV+jh#g@2^ZE-D7wh?<~yF8`+ z7k7ewkI65__=`Q_(;LKBS+lz!gP<~6)-==W#l@d~+mOFgT% znd@ayY>v>jFX;a!W1d2;_qfs69}`JWlKq5jV8C2#x=HvTv#~QK;g3uNCq}#4GTc>@ zK43~e?7>FZ2DMxgsgE7dgmi0{9iBK1Wksn|2^**`xX_dSM`5dt1V?&;9lfw^Mquj= zWt>6m!8ADwUK}Gn1?C)NE~mkc`(VX2w;Ee-qkLoiFIdla*7Keans)GFyH|$TgEGt>f)BqAk9~~zG@STR8DUS!aJygn*?rQ7dj0H1>0#GM7rR5c*%kD& z7EWiqbS7QbE|QjZf>g58qz37xcCOU3OXz=Xpnh+Aoc=D!M0-Q#+QamFg715zpAjC( z3j1Al+Mja3{*`SuL{8anvfaLyb@m6m`eW++Co74U**CJ*ejwbEdG?jer~OL%L}u96 zvYx!z_99&S894hRGMF{=x0|Ic>utguo7$;TojoaKd$C8OB##Z2%37biuH&NtY6H`vn{*Z}A98va~7^H*bn2Ap8p$8m#>L)x&L^(+PLxDE2h+|Vf zhJ@bhW8Bg6CJuc^=G(|Nbk;0%L|1bQ8My-ge2s7$8M%!NokcI4NAH|O&t66spGJQi zN4`Sq??T`Hi{3v^{(0o+8f_k-n@&>yEZ-Nk8?x3LebgL%)fl}}3w>OXxSrXAKG~zK z(0w(K-BRegs_4EZ)Njie0|@PD6HMF#x$T9Vwxhp+<}qV@g;Rfre*KH?j>#T`m5nBx z>@eZkt5~v+^fvPYUHA}A{x!Pr3UdkV=Ky`~X6y}&GeZlZ|H@%QHR(Q*D^qB{KG!tVKXAWiH0LwG4CSX^Cz?`OQespyyc-35tk(%+6 z5hnus62PM+3XIW&u^OQhYk`|}^s7`yzn0Z!Qd2)jU36|m^ky0TC`Hh*`Sq5h(6f?U z4-!sF9NhyJZ$NLY0>fvcx5k3)z0q-l3B3pd(0%>TgCoK6k(5tEPc5bXT5$Vc3DVu@ z7- zw_yG^sX|;08?y@QD2slms-LAWaRqFI8iBGOlz)~6`cvwwN^5j~PmO^+5)<1oDK=mZ z&L%o%mkygTKYLh!y)B46QUuzWA?a?{$gKWNnX7w8PVUR^q!Q|7tHmg6xBykkhK*;w}-ZA zLH!o!w!WNOe@(*OFNkedl)2=>j?0G*OK*m7mj-jD!!;#$F+<>9&gTw}CoIssd}n1{ zsYvI?b}kL)UJCtIhPzw`9aa&!Xk;EBldsTgA9*v1C`-VK?O?)26CXK<#CI53O;`dp z%tpV>LS_~rC)-RySx)&n;tgQYVf5GjKu8XDB#~VvF?p$wr!<7PvK7o&hm0*n-sYL4 zj1imfAmZedr36oM%4M+gyeR{om6jVOzg#z!}*Sxlo_B!eE{J5;__OEjH>ois^4 z!Vi5!9{t#C&#>1X;uD_7e>{uLcNE+2I5yo0(nkWJ6h2)U`4@X{6JfSX0WTIureF__ z!e8y+d~kl>wXyr5{BA%Sxn~;eJ}bW;&=T&77I7!FlH+gN+-|}?&ES>7IgcyG~Z?11-jb6YjYte^J%k9)3|knjr4I?qmdt-`ib0nO-0D!_G(tf%IA)1ZbB7zS8KYPTGicR{L|XZ zz0&&bDQkGHEeJK;9oBJ+yt`V@-PQ{39Ca_!?w(c*q;G3k)>4SRiV_ODLz>xbXI)3M zy1Pc*+w^f;YtT;v_fXro7ut@!;WrlA);*x#TiTqw?(APr_fz{h1E&`rJ3KsZ2O&Cy zy&dX=;KM!-Co|vz##1&4eqa_H?i3eKrW3}wn6!x@)8G>(6XwFvErCDS;&Q_?W}{sY zd~AG~51%lD`fK=}!r9HIzZLKbmw11;!I_`qyw73NUvTBY?gn6X1ALZh_z-ol!Gp2A zI}-l`cDKep?*PwQ5AL=mc6?oYod%>U;HU66X7CIL;3js!A#Q~?+(UXN{PGFzPiX&- z@inf{XJ|hk>GL)GvLAla=umXPZ18^)VIsIcUy|x#@)k*4Mf$kYnYqWAbd4m`8I&)O zSky~S9D_FRU03u*AN;8v-0OPGr9S>rLqa{~8al_Stg}8oQ!wf3_#7SZxq3+?9U@_M zB)X`lgeQ)ygCq+5@f!v7(Rkvq_!YAx8{_iZ70IVt!RW(M0$r6ywM1312&cAyKovNSrg6f)UBFQJ!CN-aHwo;nKVAH+}DN!^{S zaTR?mVr`3qclI^{ z`w>n@pr41~%g$l{mY}!iGN;+-!)@r!t*mV(^PB}f4C4$2G0r&FKbSq~PyQ70hcZrY zuwx*8bYsSA%Xp3CxvRpyR)=#h4|bHn?=2treofgs<{;XQe){2;_LIb%S6t?uP^U^- z#!JR|rl9YbtSK663f(JyBSHPX*aaQA7cKC)J29W0jMbX_L9C})VC)aPNxb0RUB*8> z&HXyz3Uk*JaNknEwkB(0oL>$EZMspzx%Uo>keQfylSjgCSosUdSgm;^dTnz`i zdbs=A8hPxAoc;qQwCB4kp#_-K%$-GE?+{-G19ocxa4Ef;r8(U~@Mr|sGXxwOtV!K8 zxV+(-*3HrUZV4E%3mHE~e^<1>d#_!om=p!|l7~XY)him}<*qFGv zzJ>pO1~2}Q_o;hcNkS=j)70t1%C+T4JsRQLbIEyaqUkCQS6PVe~T?JQn zYfH|h0XW-;z8iq`t=QA%;BRFxz8-Ny^iDf>g8WO`nYtYVSbLCu&NBWr`oBsar?n~P z(}nT@j1xMSNe(XnjNQgvIR?*h3H*G*-TnX$egivxz^{7T&1l@qsM-a58iK5jBTNMY z7GT58MUGb^<14YBcVOG?l6J^=BRx*u8ua!_X^u{=1Af#&{)$tt0{D;>naIKY&yAcE zqL0wN!)Ob*rn<;oZE&g?xLE|;Ed_?A2ipn|=L^KI$h!@`UUm6-3oF6=nj*W6!Kx9+ z+DK%-FPPB-S?-4n_CofCftx*$vu1=QU_@KS>j3Z6n!4S=)iKE85au)se49p?E(MrZ zKIT=HHBJAECw%dLOg!m`jm_`5WB@klH0;qq z@Bzc&AEpPsD`T5B!M?5{3r$0KfEL*3EwRTNV7qpbqo#wL!LHrN_h!>gj$mJJr``@z zhjtCfZ%TekeB{n@!1SVSZ+L)ya@Gu%TV@8l#8%w8W5ZvQq}b`OSTH~cQ;2e!DM-xNhP z_27w0Smie;(M+&SZra+UW`GScZ76GO#nd9dnN48Y+t{Y5jbfVE=zK>rEp0ec+lDoD ztf7x^riP748$4l~+4Qjm&3K#D3@09E)0x3G3n97b#+X&9UyZrswO8O`Zc9q$mcpK7 z{`=tU4ziXLa6X6OXpZ0?Y{V~FfnTr&A7vT)u@cU11L=iuF$?fhHj|!>-x6A92|mO! zd243L6EjtAn?d+IZDc)ryoqyJXBx{2Q=8CO)^KKPIg90-#YXcFXHc8-Yf0S__!d=U zDtyvJQ-pKQEdAh^`g7(TNOy)~?1JCL-yBO@e5t;qTfsSYg-`4cchmvis5^J14RQOx zH^14!Z>fb3*AyS7iur@DB2obks3d$)Dasn*>ovr8Ylz=706wY%efPj;YeN0%_;viv zF>S3>#-D47|5F40q%7P@ar!D~e&IKN!(V;FJ^sx*z-RKlkp4+JgtA|Z5ml-Y7w5Ye zW0s|T8GOO-;YT@WOtHYkrl|*R($@I z_|D_>j!Xmtroyjpgg;mgpD+o(dInrZX#Fwp2xG9xdcbAyyD;iqlJV3Zh+Q@q*&T=s zcfmFrOnDFRyF0SpkNo+(H>`$J*Z|kCQNIYk%i?V(46+&vnT-msW#O-)!52kBRujW{ zMKw?H>7HpCa|a&g4gTK~c%aa9X#P!jYCn9=C-x&e`yN~B!L!vyt{cPARmKNz#vZp| zzZEfYl;bfLoU5daT$xhJRP2StZB}jsw=a& zOS4GNGaY3ovcClxpT}LA5U5u{#?!Vp{N{A--(u56R#A5)_j9r7fiFK^PMewd=aZ5B z$#TgoMYfm0Rj(!-gyTL1KYCqa!r5lFpClW+ZeCsYxqM>t`(oJgz6`dmFP&}aOKj`; zQrQ;1l(vp9vu*23WUKpP6B61Qz69hYrd|fyJWw|^>72H`FO6;NOK<=2<+eS1h3x=e zWjoea#11C(_2r>FyY1vFKuBTh)4sM(O;KMYTf!I7mSp_gzHqisU@l=v|1|l0p2_6< zW3m(H^?fth$V=_JZ({kbn()5R`JXq&cf~~XT_F8{wOlljNdL9h$a`uM`5u{=zW+=L z-$%k<+J7@?=qtM~$d(~gVLhc8w}3B}El0X$V6AaU`%MntSCf{uiGBOcAN#NQXm_x0 z8_j)t+<11Ec|~|*SD2f2xw&B%a0VmHPCLPzuv0mcv1Y#=ZVuXUoLM)s#`fh5>YK5) zx*25)gTZM`1Dh7?P6%elH~gKsDFfCQjNhb31 z+dsUET<1PMkf`8zZ0=)f?qxLlg7jBOX5Ui&1%2}cy>lNde*m9<6W;#{Z!DK-cN>g9 z2*0!+-L(@gY8hC)4<2fz+y%$4n)!0Y%#jOV_;E7i@Y>P##+i0!~bXn zAKw_rjX4>U~@fix_Tg;9Gpx>h$E$u<&t1&E_kt=@adU(KMpb<;3a=!11Q|% zSGe96@Ig1>0xy6s_puwEz*oKlM|{+eP5U4z7Kl?yO>BVLygRi-N3=CLz>j?3M@n!b z2XiQfekg#RC<^`;BQ61+ht^NZJQ7MdlNue6n!2HLPs#jZO7XxP9JYtU9(jWu@)KP9 zOFF9X8%emVZyFySFb{K%M~DmVMKtHY;n!f_OE}hd)c?qu9CpkHY#R@*@*QmyEc}Z- z6H#)2VU?I)9p1_6@P1JX?ynsf);2KrLaZSP*q8*pl9u&lAf#h%X_;r1|IIfa>&=g@ ziz}tTzvir`47w;Ax+_2Z=R`kcV4m@qNBqFNqe(v6R^lugvKMXHucnO0->l1E!a!3E z9aog^qRgYJbPJ?Qvu6dQHF~#+sX$%{(zU5yPI{U~=-MV=Z+jVsjkMhKM~8Ql`G#Ly zfSsGMpJtk_=Dqtrca>>8A-;-<0y!VEHgO+y1i5jKzLf$h*%hY`x83_*&kM zR>HAfz&<%iILrIuW!|Tb%NuhP`{aPUFw3#?mQsHzcHau(mDqes2-~>-`+0ww$veqx z?8q6|ki)P^hQbHel_Bu9ZP8OD(PLTAUxm0&CD30*0_Wgymqqyh3?v2iM@jBge(rxE z&Z#)(k)3nOL&%5j%TCBbUS`faKWAK#J5iGJu1=`Poh(E9B7rlEBI!&7NsIgx;qK>R ztlZqg^o*N_K5`M~Wj^_^bxH=#I`rIf(N9^}wC~ta0r5P|k7) z`fogYKbd_UfGqbQ^yD4{V^cSvT^stT#@(sRU97-53NUs-WH5By1sSU{_oP03hxXr* z^Xt#K^u(U)&)E!Sj6vw&v7|?vfiedj8ye3w-8t*Ngnp!laDPU~0y75Na31@;j{Oe} z`>?xjVoyE;t8VkA_!@5dE!g%MtP7F&U|K@^j=X0QVlIMVx3Racan2Vw+b!tr>FE7o z+_5gKtu=S1K5MTRnBy?^p&xrN3|n*%7}g)|J(zh9rcPJ(tWjX^g4pvQV!K#L zhV2ptn=TyRagp)3*m0rQ6cL-p@_y#zDeo9>u-D#G=QTdgcYK`>*hk;NvESg@7utt_ zcQHB3sFDf$FBy5^&2!q{#BbtPU~-CgsD1cRTkxqC;!95_%*5wgfPFR#e{2dI+9KYP zhQM`o!WXWOZ`le?tDsip%_$!~ZxVdR6!@WW@K=-Jr)GeUN`+6K2md%ResKzX%JlfK z3Gt~D;cu$m*Xy4SlqRpAUwUZUaAE2%aV- zd`&X=^SE$DN#NB}!Qmu_PfEe>)DmiB7et-Zq~oi&IMj_F7&pDk1(%+ld4$d(iCPzr zx^ekEaeR26jC@DoH;G}GTP*6Qq>tp(O{8HMD?0s#wuwrA&WjhAzfdoJwNbY0-3YG^Ij6mG5+>00pf^?9o(qs7QC5*Vu@`85N5 zH=s^K_OFI(2$$as4r&-Y(MatJ2R8-oZ36EO{pfcb{M%r-uhH;wA~G@rXArNx^v%qkp2g*q9*)DTjECW z6*Z87mZa;!N41kCa4#Kc(-%Hy0{qP=IINkv5SdxcH@|a6W`^rz(sS^IX5!~8Ma~vb zz82nMKIIen-iU9tTGkk2? z!sAV0-Lv4j22-yud(%S)u$P?(z1h#E@bxv}=Zi`fFrt9whxe&MC{OGrWEL2cBF4BX;R3w?-FmU*>VArVysV z@6Le(o(iwqgS*|)bs%(wM{SEdw}QKD3Qlx@w+@C&?ZiE83>VsryWNodPVk%^;jf#+ zhc<)1ZUNufgfti5b_*HlOdbQTvFcCl7K<^ z2sx0!6v${S^?QOmN8*hw3TaE5X#D>5tqbEk7vg+BIS_+^&`1( zU`F^rh!z+#D%^Pt%3?DQ&qZ=!NQZ;#jtxc_a48bl-~%&40%h;KckY&V!(H*txtrca z_t1OjK6=mHZSTH&}Ib}iwQgLN`^J(du9PQ$=} z!LE;va7ZIqGZuW91aChUT%Y1b>k79R%nv=w8RYRl{!l)bbDar>>;xzF^LBX#J#o&R z(nE0Ar`*4KmAA&zaM`!utMBvva0~r$oxHPf)|b#Lm#BN0a1dRxiTBOJ;LIlaIuG}K ziaxHwao>k;e+{qw3BLUe`11zc^_TY!KYA-N_tC1Su{9QY@UQ-HAM_g`iiYKme%J5R zx#rHG*EVrC=Yn;^!L_mItkI;0D!&6}U8{A6TSQ{t=x1Ti#qgA_xERK4SHUMn_h@x!3xE_t;3v|3<8T0(sGp zoiM5zN24QS36QIJaK=%1I|>J19X}9?YF6IDbHD>9hyPBFJf`H`E)~3TJl?e;Aden> z{$Yz^CTme`C1g=3rAW=@!yPYlH2Lq@^|hpBkVi z>Vsv?!K@av;dkHYjc#!4eZl#G#68giL*dvbfN3*%7nq{M2}1~d!1tl>W4&c0y!||I zaT59C$)Ap1nL&Cg?{v%11uM}7{MK6560ZO!H-fYC(L>7!I9PIOX_zz17 zv+z|09)HGHTF_)`_I$3sIke5p$KW(8PZ8GNkJP@25f@CI%0h5F*_jKW{& zj_=(S|GhKo=*FBH;jabbBNbtflW=ZHIH#!cRgAolFP!%ee$VoY-*tt?AJFs9(d!rB zUC;Aoa~QjRANqU`w#Yto`X1hSmUEX^V2do`zAfN>&ESrY=FMm%a>D=5#og_MjnsnZqcIB`I zicwaO7%zyp6mnb{`>mc9z`n|ZOcvp-s32|1@t#x?TdOGYS1Zs@JKA)_j_8CR(u(kp zR-mu4w9m@#077vf4cHI``ynDWMigv`DB#j(bzqAJUj7A7Lcq!}`Wno>19slh_q=QV z21h-x{sZ{>7MtRgJFf4DUxMMEcuW03JJAOkFMvZZHsDrj#)zxmGznN2U;P@9vIzQE z`M<~XGyQ(Tr@04q-*tQR4)F)D?JfN|@XhL7^)ZJa{Yd&P{a>Tr5qu*4=R5qG{pi27 z=(}C$$iwJ}GvqzS&awJLjXq`muQVn28cAWdNT&mTbAz*am{V~uJHIByKIU&`^rvE7 zXe@n?9{581o$pVi|IqFebAAO*zajq!j5=gHqVnr2^;Fi}UHDb->q>(pxKopS7{K8*sn+$!4%_FSg}n z?B2`R!q0gt_=qk052u>j- zd`m%76FIE|=THZZqal1p3n|Q-T|RIC78RLm0)Ns4PA?cvY#`Xs6W**3eA!TBejxnb zK;*VN@;($f9R;T`0WPpV?+HD@pU%j25BQe0@QT4;N-%vkg|7*w{{djfM6hEaIJ6DU zYb$S22jMCX@Sd<$CJ`pU5AnAmaAbqPm0@7fQ1D>{V-AOdSq6r!r|nvJty zFamCA5`D~ot5{|p@aFIg-s(BOH~tJC^%u_d1Mvqq*T-;Dq2;dvWv7h7i9|LpOl2^&IVC_1_oW|Kr279NY2R7699yp}k=!OGugF8qcVV+yz zwoafwuEIY)U|;X^-gpDfdN1=o1`oL(9%>iq3(WIh>ix@E>?5pXU8A@Y{h42X?n@`` zMJxE98r;vK=)6qgNoMYGL43AUf%H#FE`No;V~}|AUc$*eaQ!55a*p^YT+lvzze9u_ z$jMT;oyBqzTi^uoR%{ghmPj^XLoC6k-GuME1WccfEwg}l2AIuXPm>o6{&ymDMjv#S zx%j?|`3=Qt+HAnC*@wS(l-~xdru`z?P6qGiQ+F!A85jZHj|S^k({3{UaW^n~GJ0YN zn9xqM<9nyT7R$)*I$*vvF28|@jSm31@LGx;S_eC>I(A`2LV4`e zVj4vIWaxI< z9{t1s!@`4M;jvpkfSq477xruc#?On*o(&AIi~ZP~HWk6nCiL5gzB-|gI%7*V0rT2} zcTLe#ZSYg;(|-l#SAlhvp^sA7yhVvK(q~$*l)vcL4B&2BZ1Pl$m!9#`la~yvkBp8G zJ&iwc0Nrx{J+lM5cQZO-HMZ_fbi{V_&USRu9?A}Z`4_2w9vyXxwwLrLzD@+>jpcytSI|+X8S4tV;udob)h}O_zXj4PtRoHSQrP;1(K-AN2CO|idmNTMj==fYz;{vf zV-D6^n0?KrA?(3>;$P?&M?b=Sh{fK;m4=*cW9|umSHw9MKwlKVekjLVa}8|WCfL0l z`CUgpc#-k)ir)$Thb{9IJ@AzG<0sfNU(p|bcu)SzJ98Mhh#hzbeehm#h?8vSh5Yhc z3I{Mh0(hGi+0SLhf!(8!zmZ^iQ{FL}pcks62l69t`LU;R$u~(TuW0{WvS8n)l-H7v z{&Hh~rp4w>CvSwmJ(3`KESZG=Kau)rh>N0gnqk|uf@|*$=1fGsr-RkA!0bi*wqye~ z=o)OtUAzl#CGZ*!rVIfyx)TP2D()m;3MzJi(j;KW?HA-txN>kN4up z<~BI{%)~c$O$PJR;f_QhCi zlVNadgX9YS+W)b1AMig_{~y3VpOnZdq!bxtBqEALMx}uc9k~|UNP2l;cA)9p$v(m_||cAZlyW7FmclU$Di=K zL-da$j^EO#zN9tH6Q zC^emza;!~fC`nT(MprGB=tlGH9TXRe&|aRUg*|64b)TSkEClwuW1+L zqj6=Wb*G`*Jdh|uPkJS|6TCx5sz6sNK~s8%{!@ykR9bv7A$M>-3Z;X8f->&ENh5kC zxE&M;uDSj%cuU?Ybgr^NBB&9haz9Q#yBuTHYr(%k zP1;r)?P#H$J+y5s?Pwm|XpMc6YiTEo>36Gxu8D=T<>f)U#P`lu(vD8(%U|prJxfRV zftGwuAD;*p!3cAB^m^q&E)eV>L7vmduUMpxQNlinkKi*SHObBrEyT)xA>Q159J zUtm1_<>SPU&MzqcqVn$0jN*yaxbr491P2*3CN=Iy{S4#T-$g)6bbvEO3F_}V4u0cGd|6=Hs?mCybzKHZI< z{Q<12%YSdkhyB2HM_Bq1eC@%{?e4s@(173G0q*uSKS$ub6Jg>4oNF__x&_9~bv_;z zPr;if;zi5gsGkub<7kys4w@^ME0{Cr)eq& zV^78Q(OWjf(n*udJNTR5eU(-di>*|~0`Fx7JNA|J{VC$7@_WX3cYR{p*}d=WKV6T9 zeG7*+u!T!t(TYTCe5@PYzB6vt)H*>6-0nT`4d_9&V{37=<$Up}e6NXo<5BEsAAF`) zA|LLOieHw251p0WebD)1{OCuF!+XZ&U&dup94H;TeicVL#{S!Zl~}`PUVy87i7(7H zA4bz5`Go#dhNRcBSQhWDa(5>}j61b?n72?&f|6Wi%Js zCN>A1#do$IP*2%@H6xzM`6 z_nu>uHXO4z{REx%oVM+j<`Vq4jXxzxEWx>!(^5B@+o$lF(~0NJ@iKT{Q9gYUeOG~> zT@vScEAfwTCs7!ODgp~~N?!#3E5YZkgFDs5ncCpA9r4tD=K7~N?WeSl(e#xOiQ@Fi z7pxWKfde_|EYJ9Rhx>#WP8`FNWANfmq7p9FfWKZ(c*pyz16$sLM}@R2zdExdHYc(r zcCbMk*`RL|3H&fQ>`IYX>H0hA&f=y&B#O}%3gV1!(o4#~s5;83p?%e1StVFjL*4C+ zo7QxJK5SkW=bu=A`wT}OiMM?Pi{{X2=F)w}S$`X(z5NmeX#(lBIVp~p9DjZguTI8Z zrZ&zV(B6BAD(ZSi`L*=N+sS?~Nv~guORuD-EOc+O zbbhxsu|nL}c<^Fr*5Jio!TJT#EVlmW*WuWbtNw=Jrm>LHIJiF%^6e=!pGoG&RQZ-f z`TT9BvQ{UuCblG=!_o6bZTTK2IDi`;_fEFs(ed2ixDb;J`hl{FgQpGh+s zsa@mPtO3Sp2bzB$_x$>yaNBbvP0TYMXVITFsAsb>!ZP=pyL$=F){?uuB3*m798ICd94-hq>M{T+izRfB3pLXyU(U!ymBoq1aNq{2=~*D)t&%)`SLKmrvbuhLU1)5%|pY`{gf??$X5TTsriG=HF`^mU}Kieol&Y{PeN zrk)1;_Im1i$NlQKXmxh8i1aVVPOzn?`SyGH7eBf_={b*yyWrW6#9ng!GW$|QD6ak- zIO~(_Oq$p?o^J}9&?k1>dTZFXyZPr!X==;(-kaF^?_mErwqYgg-{9U+egCud=PNku z5AqzdX0efVv84};XsD#XbUaJ`nj z+AN%w?l4<(#_^~+PO(Qv;r=1}9@eU31&+N@x)nhk_M&-W7H&ETbw7X zA^Y{*Am;CBlHk<$+gI_B{Sy!4u^I949MTtMGfUz2FIcNChR0`;<`FzRwS5r}Cwkcr z(iHcu8(a0aX?2-;d_eqU&hOLqy<@d2{p`016O)-i3AuB|%zYfD() zIQ2}%>&Myiu|!+H;gyw4Y|J@htSmqA zRW`6Vj-H$C%PKrY2TyOGdpdEM?3;KLhkwF6%0YVzZR3-;eOCEij`Gc?GtH!9&Boal zm~-=RfVI|57P6!Kdud9v&Gg4t7Ca!-IK8;^p&;h@tuL#+^9_t#=fv-IYyhuTHhM3J(IO@ zskVQmU9)2;we4|jFM?a=5emcCDlog0c9n`9h2JM)<+Y_AKc)r!rZG*Z8BNDuSJK|^ z#9CQDYy+#CvjJ7Lt+@1+94lyBCGGuR{^PMXo%<_In!-_7y$`qF4!>8bb0_~`W2~t9 zs(Aj_)m4kX&`2G%_z?BgS4$sM6W@%zd6!LT$@P zR}=N+BKRq%gUbmnxXz))e2#}xK zm9s#5zO-&R%6R*XPc%f@fpoo&u|xR9IeE{sO$Yh7d-*l%jQ?pk$WmIbFGI3n)%iq~ z*uF}!E#|;}X}+_@^eqm$N}36Lot4pieAS#Ozz2HT-1A+Ch%;YIRKtDBCJy7_dmT^U zzpL1uh4|c;*1=}sbf42ehr_iI_InJ+`TF7iUGTi#_Fi;ihdRKt8i_^lXF5z83dj4v z@qxi(LONQ^;|N&2F85hgbPBd-xm|mHSiBguhcCezjzO z-j^=)r{08<)%ZBoVOwQBiNA=1fs1H5^Wa}t-cqewKx;VUf+N~*604Wt}d&c`c>^KpkE5Y@Eo2!m$v26t^)cZ#Pw`&-mkRV zLo|w=8tZy|;8*YURhV`{@Fe_y61MwmaQWu4nX~oZBIT}N!@jk5(QhqiS4H-|7W-9E zd^v3@Blw$KeRYhDIl~T|WnX?_Uw-w@{=^He@f$C*K_@-qWpQ`$i6n_pc={;E@$A;< z=pCPv?*uJw6Hc+3Jq(}kkn2Ni*a`3FDn4;YnrWEswhw755enRhaKqosU;pcsucva*y-Teo1u2%Rb}}eZ=P)MN{(I7<9`_ zY)WzVNdl z@FB#yn)vM7u)aO4YfmHUPHP!SYx$C{GLH^4)7s`re`B!Dy63mTW^1MEtjTSs;qI|U zy4AYd9&4Z9TBF-d7dl8c{>k5A`g&iBg$dwjn5&#fU& zi>HrIkKdy$|7R^ON&HjmkJD-5Gu@vN<;xvk;W#mVm!5o{mi)8w|8V{ft^Kln8@C<* z@plHNtkYhnwV$RnAF|J4hjwn!-mk6q&Y~5K7yPAtdlv(XM1|EI|E~>?CtnF_MY{G zHtgejLPxf;Ia^hq{R!=3sn|BQU@yD6lWp>M(ENh%eLTxAcpQG_cYGACJ{{TbaS?`| z!Q+p^(BpiA!`9-q;Ry#}34 zDqZ>5O=%Uaty`5&?8M{!9lv9B{%R#0{*{E^8!>i^;Of3fVvMig=g#Mg&g6^EalTrf z?Z)mdWBGgO*73`Ch+l4uFNe9a5>MbX&4Tc2C?J*Amzd;goayGB_HLm&KpG<(z6u?Jngm%X1KYG=>j?_e;$;4?NTZ2x@EAL9B} z`?A-2?w#=QsJ=Q0$A0Ah?1z&F>35r8c-VJaqdt7z^E~6ZGHG*8$IR+Y2cw^Go`roW zpso4!U0!*@KF)>LSMjbY;qvuhZUb1@5JHfYgS~|pX|=!E zOZB_8^3!EFv?)^clRet{*v!SM$=`#F0_eul~C>G79n@3-uy_zf=ldXc>yL+$ApZcoSPxUU#F zj)*6ZkBr~3m*F9}d&l4O+=HvPY4?A@)jM$Y4qQzc?+2$x$1@07U~`7}xOm3+Wb2p1 z;B`OgKZyS=-9OeseXSn;*0qjU$KTnMk8fl9Rpbo=?%Q#Zic71j{^Dw8#VOrGa4 zc$zUjM!!srJ;Cmz5*~uD4_SlwH`a-U-wnV2#Pt`}1*gZJfx#K=^Ltcz&q|v@|D}xb z_13=~Xi;rqzu@Wzh5h>d7kGRaH{TC?H;FsUkJ*otufww!>Xp}O`I0HXOFkS;LY~Ie`8E7WvkcI zQ@8Q|_841R_?4US-fhxsG|txA+q%YaC4Re*F8n#WJ`S&)!p|Je-VdY)f5cC1OUG+s zoY%w~-h{t?I|n!L8%z1R6`={HhFdL@KO=R*6&w8%1Y#fV2r#_UZtzXN-md1FO-ymY&zQw;bCaTa~>$(57 z`?cUy73oXicO~G{t1zsHhNrrq7K zz0f6bL4Btal#N6~I$9go9cgNPVOodi{*AWu!!;ZVss4SH@jW~ z6Ipz^!_(2&olDPP&2i0#=p!j`&j-{Ur+?glu{XT!KaHq=yzTpFAUu2Nroq|~IL1(8 z_6uuKvy9`o$2IgloI&~}hNK<=AKH{r(r#G~Y z{Rg|VnAZhpBCo_2n)jFu!)|$k#;f_)_;Y|EymquMP)q0H_oZ7m)L=d_*{AEU*n5c#JPF6INxeo^IrB~ z2Y>u~$M2)|)KS-)eDRX%sw_=q9I=Xayhk@^89NTs&daxt#&9Oq8dv|29?_Sr=u97Z z2hKI6AJpU9hh@~3w+>CCee6dX$w^wt33ladtS1~D8`+x}oAWQUdjN)}hMS4l2sUS+ zFx>u^k6rhbwwLm|(YxEIvqPl4@1unr<0ov_woRUKq32oU`M#!SeChc%=!+%#Y=ORA zrjMsNF4m?co^hT&Sg&6<%DcmJ>=M6J8MEj-GjZNA-p^;W48Na4hsf+axA&2kzEHq* zPQ18)I$oBxd@R&A=6fGYl;u|nX(r(_SJ$qJ`VIqD&szPm1I}-u^RHx&7i-H``g{ew zViGOI?|bQy+tFBQW&BiN@5|8J%bPE`&9VE9&xg#Z)aF2HT-w+0tfBP9o!_^{*2UW2 zd&X!h=MByCy5@Tw+~i$2@*X~2)v+p!pk~7CmGEF6uChbg!_xWnL;hh^cv26p)PU#p zjq|2(ppkSf_5$2~z z^oElStdTY3E5C!g)!`r4rkU2^Lsz%=xV&R!Jg+2ewJ2TqRhU{)+8S`Rj=Xi~B=5u2 zws5)?&esNRj)R|L5>G;*{4l>LEG@vt_T4e{4!8dGnfNcn&v!nIA2E_Gnj&tBHce(P z{355el3zWgz^UAp~JOrqB=%- zhROPJ9{*=0ySmvL+A6s1cMH8Y-;(pb_C)V0e7}|8d)4UvrbILEsyoaM@x5<^hyN)l zsqylN{Tp`@gJI)nI5^Q-${g4{4Q7vm;geydUu=YVf9laB;=}uC`Dv-?!dYN&PMq@v zKHY1^qSY(w{%7gK|M2mW$9kJ1ALA#3?N8`O>l^2KF#htXy$rn^N6-m-;VK>Ja4qS0 zo#}a9`Ty+pszX^vvf!`d(c@E+=$8qOV+IBZMn_Y{axGsirV*Xbp5k_ zIw4%~Y{zh{GtNWoPwTl8xO(bX7rgvaxbzin&TBXK^TkHPt1ocAPWq>vdRkf2tl$~_ zHlpY7bypZbQDv`|@EewSH|wpJEpoov`&rF~EoUFsvttM8FMh3)FP>l9OVBw>Yr|X7 z^PR%?P6$6o`b-i1RXp~SzM%y1Axc>LE2NK#ahL25gR16uADK^KH2NqR58Sh4Z!Ds|4rkv9lfM+s)+p zh@RDt9UH=i^|5|2kR~&pFV&xo>q49Fz^-;=GyAfkz1i13jxE{SM(ju%_B4#|qKuC0 zXLr}#+0(wt8fFb(tT2^7GLFrpgwu+W^FjWJeQv#YCI4)kwSf72l8J2T1isr;f73MD zdci=p)Gy6y|3=!-9=3Ux=bgir&Cn0CXmS%h#}YPf8QZ^}{oWh3sWQ7&Lf;g%4p2mz zWLkBd26V=JJ7%8kHa_RjdF*JRjfeiiHn_Ex-B|)#KZmPh`Ou@-iHW%F*L>(D?yZ4& z3vkwu>yM=AOu}!c!@#+YR;T#jRyL#i)z})p?IwPj>jiAuV&z!f!g~urx=K+w)A%5t zOE((_o-FSdc=Bu*KN0?a#5V{z`iC&PG0t9-PF|F@TY`R_o&N9y?e{5~?_)IDEJ9{l zN+#M%GFtaTw3KADiZs#vc|mg8P9m6OE%i&f&ZOYC;FI8H&@(t0)D1S#bdJ$>b_#oG zDCdHYf>S}?;6gAwxEu@(t^`wpe}m71JHcdo8mG~J<_3?_qn@$0=~sib;Rs#in6@2q z{E1F=h@P{bKJp_w`5TSuR~pG5!tZpFU+Br#*wc$boE1-+XwK%eNhD(-l3@qFG0Nt2 zrjxW!WM+#puow@sQx7|)WuL;n2;;3GAod^8N?_t><{QfkI39Sco3YhwIbl%PU?c-iIylenG9SK{< z;A;JFv=KPk6#OgXW{dxS3~z=g+aSTd*!$*cOI)p+`4qtY^u7i!?V74gC< zcw8;>xw5(OuDRV*d_!Ehyg5?E%&LZ4zXu~)$lu#M4tZX4bE*U1zJ+wHapD2yd~fr& zKkhrlyj*TS;TC@%wAJ4wZir=$AEKS?q92^1^y`TG3z#(nUQM&-WtK1*es#tBd%?d}xOPK4 zzdjs(!@H;ftE+ooujA!~^>QYu(i$tty@k}?_V4Wc6*m4H8@P>K+Q=`R$se@i3G4Gk zX=CSKGHhVp^i^SPVhTGU}2EcCUf8v9?uoY}%x!hG}N3-eY<7#=>x!)O$=HX+wVshwt zosRl9KKxfu)jD2&y6Q95qO#L-)4`y0j!)1_AD~U!bw;Nh8{7!Kghx~C@fa6e52nMF z&x1eU&4r*#a4={S>G058e;H51I#?gNngC zdr#I0-@!4vT->V}tO{BNTZ1~{YX^%Q?P3Ww2Caf`?Iqc+3_E1h^S}DGOZ#2WIXL1N zrr9XXjwo~u_5}Tc)4>>cI0im0iJs#T+V3+o?R3^L^U&e5dEPv5un3K&6pme$Pf!c) zRTe71t?Kr))zDYX9V^p_i&>Ma0RP_8w{;zx=+mZrh!$|a1D~LmYhPH<&&d;=aQRkn zu>&689tZ!#8bhchcW}S6_t4$?T{CzXVrD0H>k~f1Wbb)&gpE~<0pBY2_Ck*SZNd&u zDfhy|1K9nKu+T5|8&f0cD06Y`DfE{~w1b(nifMTFY;j*3gDd!V-_a?y;CaV!v1@GU zO?*2Xuh((zB)sUfu@CHDcpnz_hIzfM)eMa=FN640SVDG~mw`pkg&Cy7QvCKPe$`L9 z-Z1e~JaYuQJ<0r-!UvrU&t~zNXW$qik68}Kmhz_;n@=0ft?z{pD|fK_TjARpTwsAT zi}}l&;pZ;39>l&b;56s3|JztoGV60G%+t7edspf-*5w|DH$`DUG3#*oVbseoBd_pc zgbRCM-Ve_AMcDLG{D^xy#chRKhvCLL7-lC2tjq=@(&9E@`Tz0E|G;Jb!uYOuvJ1G( zxd_KUg5|wwtUX~w$kjT)^(Hu4OV3aSU#UeOsOx#F;cKB?c+2xw*7Dc&OF8#TE44CA zFQ;!x>ZdobrF;=bY#-T}$8eo=N_#>-XHY{XJ&{)rl$Ev$PGlz>{}`)g`fh>341qpY|xp1Fp6_1Uck>Zk{4 zTj6&NwV|f-+R85{&1=djsl3-|CnfN_3i7`dtqp!+JPlQ+-$$m>yr6$xVBzv$qj~lG zWBTS%EzioLW_O*(czh10f6lQeRvPwcF(an9{N-sXZz}(J8cRWMGlz5ql~-8%^C~m6 z{m+Zf;~2h&Osr`NBl1D5P3GuZ*nE|;e971NOeOgd)!}?u_w3R)XYTM76a2o^Sa=#E zA-N~M!w&q!mpNmeUBXv?<^TLFoZ;^s=c^p%b6r#3@8)25|9a%dRbw}bMJWl*0js?5UG3l_?^Q(4Dx?8Cv-;1(W!Onv9Xos|1b z5Vm6~&OM49obUV_HgY-czDgVSu%T;%w2qInH@=`_eRXrh@$bj?Zv_*r&wd=7Rp#lS zH~ThF*&|&KQSOxBQm`gSYCZKImhxXb{(7)DxR3oxh0CY1?it|q_kz!&yn8(R4$peP z^B)WPv$X^D#W44l$iFtS;ejW}o;bi? zclg&m&EsAX9@Swx{HC>;xSVy`4kh*oTUe+gkmgL}*M91JC_L{0A4lNIgK5SC@aPc7 zKY-hP@b=;GeKenWH0+;Ad!EYwU18tDdbZ&Qe{-|PUXlIQd&G@xSa(xsE&k ziAUe^_lDQm$kS}$S$5JFl-S2Su5&0iDOh=xhnW%9doiIZMRqyE(L|{ z=YD~Pojc;4w}LW>zoR)@%5^FHv=mNS)|z8g=f$lZzU+Q!{(3QMi3OGWx^>Y~$|{WK z7gE-WwkB*Fu{hZwc@KFY?;;8o=o;p6)<6>Z|Qde7j>duatHgQt~Qluq!p z_+0k96pQjd0+a7Xn!p>d++WwQ2=~K}^ofSrQp)rA3qy5Z@fp{2m7H7JkD2&jY4# zUq-)Ffr;xt`ZU7&+wPsfgq9oV`y#zZgUWvKE8@&7{p-&Y-@Y4cyGXM*@4)^I=ecD^ub zzSN?5#+t9s30>$Tt?XTDN8jp7FBxFnq%*AT$!1Tnt}+wO&VsSC;p%)g-(Q*XB!5%- zDYo2S=LsLO-L3haz3Kjg+4ZsV4Yzg^j_s+=zs3(%!r~>4+whH}wBh}@##wv8PtoH3 zq`BUq?pO?N7q@S=y1$L7;8=-IQ4?o+7k?^`OT7)# zOPYrTqd9pQf4YVXZ8rb*(w@V)xZRrTN%QlBd3uJ{b57m=sW%7Ye#)GF79%bQNlN00 z??9q*5q1oZFycL&yAiukC*sfx)VdHyou)P6I_O~7(G8z$h7Y!*>-5AA`_X?|i))OV z4#O*lTI2JTNF4Z0SX2+MY=$G(hK3FB*=BfiJNBa;`||+~{sFGtR(D+WcN=Z%h?9Q?i@&r_X9=Gwv@!Gf zIa?yzmMwmbZMaDzN*%96r>SY*Om&)2x%eZ_?~m7rLe_XAdQGc%Zoyxt#&gkgUbnaA zb$cHx*|%6FJ|SK|J}3Tud~Lk9@KJnMysywdevHzyKHfI|ZM<`QL;O8^9^aC$Nqm8l zC&Wv~KZ}>JKc`Oo3yRcq@k`?!~INJ@;rfekL2Vh#gzb-mYQO zR=T&$d3e2#{R?gN{MesZ#Xmfwt47Wr!k?_t8Sio{YqFAGHPgtQYW(!(`waG8dho?Wxmqz$VBaE@0^zDzV;dYm1AUoDmKHp6;mLF#cANI!Y z;yQ69$9ZVsSpUl$xx#-q$Uf~hAC8(2r|3)vT<_=G9~QQoqucQOAIzQY*5fu>JJ}&D z!S%o43oK*LKDX~+4$ifRUN9LZ%*MTD;9L`N`o8A=7(BnX_1QseTVLz39qH?BVMQyL z(?G5^LJOh2(%!?`y73#jE4>pf>l1mp@Cmxvr_;sthr;{NqceMg&J`PdgkB;Mj+7;vJ5mKD9h(jj4CTu)DD~y@D+` z^A;R`hrN>D^9ar><0?LXJy@o^`E1ROAO&0TWa5&2lPA?1+Kemg$g%7PE41?vL!TZoJ>LISPXYY(Bbc}36$P-@YI~8MZUWMir@QAmB zH`tr{klbG!>9eV@emQ2oLJ3<~pWRB_hnpN@k&a;>KYL48yqSA)T*Ao1aKStO>HqD) zS-t*?w|3TBxWsCtq?c#r=Vs-T<~Pnt7;hz=zi3ZMNufNft}4{Bue!GJ*~I#5Gd!vx z4C+bu2{E@1O<@9-wGfi7g-$!**-?mf$P74y!JVLN`$mR+7zbd@X?*uIE`5$>`?o!E zv3Mz*wGh4dmG})D^%vaqg8M(?qQ5%-foA)&IUJ_g&TS(>d1Xjt~gllA8Fc9nL4S%}hDTAE%f+xM5cQgLGz?AUlTP@Q0i%=N!0k$4u zO$T$Oi5c*YQtIGQP2kP@u0zb~DP3ROX*~VCSClp^V?9o^4%0e_ac#zt_M1;X%DLZ+ zyMP1ThL1mCVCOKfOHu~n{uFL0@di9SFZ_ykoiX3e$`^8}gP7f3b*)zRMlIP30SUdSu1e1*^#d67hlx5jSu3RGWzpzYwuzI>XqBdyvbAf)4czKT60y40xe8tzg!AjD~Z1U zPfy=dS3;{Yi1+1a`NMd>*u>@}!@Dwjf4SMBf^@Pn_}rV;+Y8})CHPu3@V(Gxz0Q7> zw=b<6P3(2!f+y$6p;|EN6$X=wvF<Yo_#NkZ4Dc_gg*97)MvkW*MAx*x3ncJ`!{{# z7fF=;o%+H$Hygj(_2GVbzja(4wfi6H$HT^WXp@t&$4T|=J)_vGV4}K+@BUhYX)8D?61b^R#zfJMHc6f#_yhDSQ7)n3$cL0PP0(S@EjDzu$o>)z9 z@q@$-haF>JaEQl~X&?*i6_^V%miU{Enf&uP@L(SQeW<+sBJMg*o*l~BgpsU<3)5l1 zd|WcrtOw#Uy`u1X#5X_1OG!XU?{O?JKoX2`*@pzAT1?!Eq1ElUqLD4?V(s*ucq*}vXiG}=Bf#AQW&f!k0)Ok{m%yxv zq^IX3)mM+ubpFGCe~0*gK>JvHF8yn&KAjfnGl$vfecr`3$CJ^xSgfySvm4{tnNMjn z-7)08e27k1a(iQC&c2xX82G(b#KQ{*_q+;eI(6CW`9>G`T=Xu4yXM@o-xu-5WZqJrm+=M z*?_q;qY=<_CJdhxt=nytcMTh}4Z~Z7xbqmCZ~*^`(WA?^12Z--MPv}I8YK2dB!6kb+ zhWh(Ebf?@O-W%W)aJ@}q}D4~t7Yj3ScGZ~`9+4}Tr zPdtO4F^S#(Lf=hS!!&-&O#aJ+=zVqe?)pITzEIn*{}=~bJ?U09dz<%nMhU;d_#0AP zVMqQnZvLaH+-9}!MOc20-8#c=-GI%v*q$r)q#cLvf5PvW@tG1jr#6n0Qw?%Kw&%@~ z>^SptLOAYU#k>kZ#Bff8cv^{9Spjao1r=*R#Sjadz|C4_UUl(xpm$B)dsFevt)DeB zw_2NTe)ZB^n+H#4@$bK;H7qng7piTx(&xbMl@Txh$lUEs2l&{`5ADQ6^LZHT9YK>A zZ{9DU*DmAVZowTl(xUgkj1zR_Z~4U=g_Z8D!Xwwgk1er((aow%y2CYe2T?if=LB&?(8(`j4(R`O*&`%5`6p%Ui}U`|AC`-Vb(qR zbpj7g7GZF47*__izQ9+=3DZIjp9{w2mnRo|eUv61qklgX-w$^W#*#TE!JltyXQ0hD zwD}LK0r$r@^X0>5SVN;;g%^MCIX8Nq`OddU?-#8g+eQ6w+4El0gFox()8ej5^NVmz zkDg|e_OngLVB`V)x0{{(HnP8;vXve5eIq?uTdEqYSVcWv%3FEa+sG<5i;&Kne#RTk zOi6mdTh1Wve$4tdTc4Do<6D5ZO=fmKhvQSUvn)7G$V={nXK9SP40ueqmY+Q0O55?G z)i}v&yzv`cWpczVcR-4*{KIwl<_egz+VuvgusOO8dFW^2MjP*+MBMiq^I(DbGr>$7 zPsi?OzI8H-yP3^xtzBYjY)t$db->e2l8uMKn^J81leXE+oRbWFYdT^*czrc5Y znqU5iePmD2pR>_;3s^%cY=1=|zH?eyZ%Y1idcJ&m*AGkkFyH!q`tMzSc3i$k?B{;M z-m}a!@XUPfthD53_}V#X#(CY#p^OyLJt<#u`4Yi?`8Lsu*IVz}9Xz1yH1elc|I?14 z-=39jkWO7`>Df=wveSxtithU)y*PEGDV%UTuDngw6}R&F7g#@AK?_)FEpCSMnZdDO zmhib&4xk-OvG?RN=fj-$uwSKz6}q<84OCE}9$Zdx<^RS6fY^&L>pIa>FR!5n*M7SJ-jbv;h}4EtY(&?u%`-I;KkC{)+N;G(DgS<3TL-myxW#y*;?)+S!0 zrRTS<_!cdsoHc=p^pSGLNJ;N8pFM_;(cZKD|GR(8^;5=1UiV*QBVL!jvh|X;?S*g1 zHoV1V)Mg9nMRw!`Rxp=w_zXLe!~LShV0qTMvT^$o8&=eq%_BaW^rhLj!mbMlFY^vx zH`2>Sw(UcuOlDoiu@U3Vk!42vLe_IJd*IiD&87)#c3<{kxOp^Xun_kDd5~vXIX?84Bb2n2t@bzz8Kg=SbAtFMTOHD(KHu?Y^2`|}O$#jnqrQB_!^3K&`m z{PK17B^&0J)vV1R1ok9cgTpth#hqgd4#V^FG~Sc++_P-KN&4bAqHTk#zxw}1@D)vXG2EP?v=BFkNF=dIAZ{&v`fFGmws~2E&p$%z)6Oq=s*CEmuI<fI-tVgJH#Ie46oozZ%6j>Jva9cS$y$FkGo;kmq{JUo%?j?co|ke@$?U1!DVvkQ+& zmxW*OFkH`$uNM?vgse3%?ALio)f^ie3B8R9Ukjq6ZFAg;l^?=5&f(dYu#y{?_%)u? zNq7Hr_fOu_eKeC4u<{9dNKW{f2d?JFlb*qmo)_}*_g;dRZyC$48Q0bDs_NDQE92O{ zioh>?f%;I?%y`2L$p=F-xtGrhU>3(*R@Oq_vyl1mVuZCJPk+aC3p1jJ5<8kZ4e+=2 z&bz|jHW0d+w6Lq`>W|72jx8H zZnzKQ9Nv0fjth9|8Rw_v+06q#L*qGy*Pq5)kGp?O&TDpwCx=N-#BT@*9QsKd{ZT7# znW1iOnuD)K#0$a6yfo=ot*80A3uJ8qp*zFh9+0*xByA7HTSM>Gj&0*F$X5g|zZiwa z@si>T!|puviNeaLK-b7i*Laz>QpmA%gy$bh7wR*uS&A+^hAztOq>QF8znOa-#di}v zWE<+s+r+&V%4-~dp5~HX>+a`MBv(R8chhTsS}ncF1|_ToT&BHTrVX8prLp(?InVnf zzac|B^fykjRTua+$KkSX1k*?E>HpA1?)ED zv5zupdQc;}dAHr7u@jEB2b|rEe8MdFc@p~JzcjwPSV9tb8ft?#VB|SE@jlpj*0{fn zVO+r>F2c~?v5r$%#dS);RVu)F^Wi+^af#A!&iNU6Z%F^Ed*QnLDfz-OE=Y3~?w*M7 zc)5F@L+xSa*(5w=62u-P$7JkeqFN>>WvG@ck$xeKVg?0xk+SE|D8jl|N9A9X`iT2^drg53G#$HOKc_L!ws7=&POq7-uJOAt!83%V_CdJG`-~aw^DE6TVet ze@aK#_P7>hk?U!#c?ssdpzb&1eG`X#l{!;Wd&@d6!54Z>Id6HIN_wCnZ>ly0p^oP$ z=_w2Fmoh0Mv)aS5pK~3y|0#98K#h1Y@|)h1za2E~Cshx3dtk6--t9^~yHuaAQNkMU;RhId6xMzxn+`j~T-u~xgLYV9}97t6oI(=Cj~KzAwIX;B}~_MwsdsrwVOWfgus$87oD z*xO5Q`5vb@L%%y^jpGkC;4i*#$W46bn&wvvw%3OK_3`8S6qnY{>)N+b6NcA=yG>j- zz;DXp-^JO4*YKZ;QCxAex(E#SjcC^7U3g#LJZ>ZOkh&f0?*hd`ynYXcH z+7D94$5DHHSw@+yd^p8SBd>ZFcPTN)+i6IWA| zF;nd`9KVu#k+lI|iGhiIwLA1PMzbhGS)0+Gb2L6Wo_(6YKFwmQX0v4rg^ldZPTJ9) z*ncch;OGlJH07iATU^AQZ$|k1Aa0zVj+CAckqVE#ACFFoN2d^%3g1p{f8kSb$!y){4W1_7WtAo?stwZ$k#4kp9jm;l+PGYs=JMlwqgyFwT>qjy$K|@JB^Twqtfu2y7HU{Wr2Rqt8;vtx z9MYD4+Vq*S#_Nqia*y!*qn(eIGklN3qquLp^&_q~YVUT>vRa@rOs1e3wxEElOc{0*?|KEIRA$2oP*3|f_r=+?{3CCqQ^Y(co zumIkb%X|*!YdVZAgV~qNEDeuo9RIS0?7Qu>uM{+~G}e(*+q00``Qz4=AEb}{i#Ohc zKIbF5x{0k^3Qs~?y3T%wZ`rlEY*T2Lmg1c=*~JwRHXeacXQ1JAmhKPiGiELE!Kmz? zt;t=1H&?7%{zvakiS>op^Ptvdw7QiUrsTu|(mT&CWP=*{J%jIx!=}10={=mWmZxdt zDH>SGXzuCSLZb%wX$?FyeD3$8Yb-R7uP&S_p%2R7kY#bpm(=-^ehAlkpMZf+dhUm; zRr;b5ZhQ`}@RhWvFV5?iKm7K{MeC7QtTp<=8_gjzZd$^+$%`=XWqR~W`nI_9=k;l^ zsGhw4-}m{o(^u@g179JG+SAgzX%qR8b&Q}|cxyBG@R3pFd-BHmoai{rI2mEgcQ$H5 z92#hh^>x3m>wZRk7&ichgt*t&I0-ScAIzF$%zYkl+V7yvM!2!pI6kAKlSXK$N9;4A zzB3Lt7?K7HW2?E_Snk?FJ>}L?YgJF$QY*Sk(@*&!M<1eYjN&Jwx=I?Cm5ljP zj^S8;lOI(o@}-_udR{%2SzK|w7>=Dnt_!Otw|P)hcv=}xu{e)Q>kE9YD@kA8`RnZX zJJCFN+Wm*MKAc~XteJ>`OVcbdliBQ0Vz4!qes_NAyuQ|M!rrUQP_39J18%WZ&v zRlvo{V;!$YYgf&&x}JDrvxuivz&PHLqk>Rbu8O!}SuCmuo|qp8ENVyFtIo?PqdGoU z1p^9sXiLncDHarRv+DS0Ir-m`?{%w5A+IbaPgN|Yh7i_U9V4zPe`zgx85<1w>SL}R z!zNP;Nv&kvp+)}_WTADWr%5NbclkCgAt`+$PM5x4d>Xpwqco4CG==0*`akL5f6zv* z^3`t$zY71k4uahFVrQWGItEN;E1>g-bpjL>g=3+|Ijpkq(_{Q|2S>pl;bIS_m9fGApRHS z9ig{u7Z=_;L#NrL+%V5+8p%oe$#rVXpTa${0ag4jsc+IwZmJ1lg*a|0&)XP!H^HTw z;WMqFdDxb=aQs7|JzLX72(f)E``3#d?4+Gdw7sdee!vQ}Wl4Hqb?qXq#U^Ve2?4h#bLwacD#CzwaPWP;2QUj;;DOGZ?I41duyA!-QSKQ zZlqt&ru8q!zK1J!nD&JAbyRmF?XBbeg|@w+eyiabOK4$ZeN^5PggoV4Eo`K(tIAbY zs@L?_OLDyCDJmlaxUE`xx*p8Vj!=6);Un*)r!bI>?8}b##T`C# zf2`I|Wq-%um|sVl)j_=wa`QR*!B<7~$2e^s!A=jsXeZ*K!}Q9Rc*6qEHB+8xdSZih z+Vwd4YCRF^UQ6}Hay)o;ROWHDoKW*F{dm;9{c1j-or6n(R(jvL>Bjy#f{ub`s8)JRm^n(SH7;OU+S~1FPTGSqxq9tPo^|` zQnGN_-F=X44eT}h&$`bIyz&$0JZdFpkb(5ozAY3pg(AlZvli=32?LR61D z@GS>Lw0xKnodUV@fI z;qz-|UU{>ww$b{QFeuE>p`E+W=(hVuZx-XNyL5UbEz&WWW6JmezITWzC#+W;jE;BBo zLwA~U$1(B45%z2_@3z9J9rA64PZyMZ%^KppSY~_bp0kFRgLYdeo{zfx5-quaozu@* zD}3H=y=Upud94bTvOf4MHTW^QZw^XvDyfrO4NNJ={ZSdY?XAlpuBi3JSLnen+IRAj z{oJ{%^<}3C=a%jXX$tzku$&dGNfvdtY?S`#C{GdP<*}~#yfw=b)(l^kFI)UiTJrAL zO&>344=80eGdB;b3$O;I*oF$th%%-$V@is<7kFnlOTP(Oa z?C9XFR%cII`M(qN2>px!Y~P6ge_xY$I}0QKVX(K<3od=6)=_#j)D^lab09rvw0Ag8 zdqzRGPrQNdTF_q3Mo_d?WS@H)Bh4aT?s+^sTQueh@&7_@|03I8#8}H=+=a*IVO2ps zZ3XyMU7p5Lz3sdT-=Q3aP{H^squo{HD`iX;#T)X#wS2HFlXaQL`J*Z91;1;|hkJnEX-#szkQ>ccHltU&9hyOyM^^|ZU^SG9~aufZ`dk)!@FN2X12LG zOi7*1*1mYwAf*mao(oUazKY?4+ZH__K#? z*rKnNum_v;^$ypYU9Z=7i`a@e${4SQ#wqhN3P~R=eILGcP}3($93j;h7HF)t3{va= z^xP2ZmT1RXclN1!D|LUP79Q}X4oG=SI2g5K9eWeLlSB3??Z;CO+f)66@v)mfx5)_E zrj5sFFGq!)-rpW=+^vmA*|oFwdMAZ~{~AAk(S0u1XK@3cy-N$Z?K;l$&1CF8$L9LF zvHzR#d+QpVjg8>3^BEw zt55^Z&-&*T^8}UVA+_9Zo?K?HLoWR<`yICN8jQbeT;CL~iNEOnZQT22q#-4B{UA*y zqr5rkFnP_tyb<3HwXJX-=7tJ|&9!H+(L#_rw|N%M)eL4}YEPF!z3HXM>Iw5fk%H2e z@pM(R^BwcNIo=j>@Vd|=qD{V?KlChO@?DDS%kH)dl5Wef-g-H6KWAvQKf>V@pU zw=BbVc~@2Oo%MbIcz9jh(ql_dO=ZIX37z zJMo7#yMN^m*ow4P0w00<;W4RWh~-JFT0Uq;NNP49J}*6%D~|>2MM-OQ zGO3l$82gagomBEa1KYD(Gc3TqWV5=N#VY1Q>_uAECY^itiA^PaD%WY%ai7$GXj^Ez z&T88ce0!I69uhXQm}^Br4%;vRNn2RpJJXIJNQ`&Ma0q?%K0hp`X-S7$`-5<0teN!?rhOzdG<*+TkwEjIj37H#SP!xYy1Tbuk)0 zGtxr;VhXk~k@cOb_m=A6@OmVz?PLAW)74OQhHEyh)X>&dGb6o<(O+Gub(9q9RmIGw zQf5z1Gv-mV>k+g5QFHH694uko=}$H+2?V%qo`)LiO}6WjxqaJwPi}q&=Jo^Tbi!WE zDzd^>E5l(J^iyOtR!|_+*jGNezLlEvDo$H|E0a&<+ zDt!L`2CuzYVcJ(aXS)48`Pcu*10uyK$J#YSDjAnv1& zWQEnw3HgnQ9C&)Dqd$W$<)w!{Ptz<&+bm-IybHM-!Pmw{QY#ukBV4?LV?8MTUUaV{ z%TrPOE0|eHAty^y0PcrAVPv{ps zW5i}rcNX~y8_hM%oDy)ph|yieh_7Zgl~vXo>U`IXdxwpyjceA2)ZNUGF6LG*v$Css z)+IWQi00}#vwV%&yuop^;|{*TF&K0{c3=EoKEXe+$KdoM@muy*B(-}aCk%ZOmrjB; zKWHaOI=GzMUW`2AbHdhS&^8r(z8iZKmrfhMZSTn)ocfw|yo=WGuEmnthjCR+zpLj@ zb^gLjzvTLFmM6ra3()EsG`RuweuXIK{QF1R{~+o=y!XE%d-H|=Cu{josMD7f3;A*< z&l&1TA*OWnj4ky+Q`qt$p4^Vz>mvgdJ|LoW^jfoianMtD4fwP_T+QD=Ma>TEce~ zj)xb#-zT6@5^wSzya?BHuHp}WIzM4u>$-9H8=mo-xZiM#kSCqOp?Ci{P;F&+B(8|B#S-mXYT{|O)4BgL;`{ty@Pu#*_z57HfXelEhH z)$nLN7PbaEJ0k72XwTpFi0g*wcZ)j$qju6Qj=;3_IOQ6pEi-$Th+QdcP{V2{wphy+ zND=bYnPNv`$bFz!7iS-s-?byn{yn(!YJ?fZU`GxLdNvsKC`BO$Ovwtto}wmXqY;Gh z8DY--?mP`)($EUBK(sW_E35QRIHrL@IU!PRh>%6Try$gmP$-9TvWPFB%vaTu4^}-P z{z>)afgCRg`5?zr;vVpSYB>@xFbJM-9qx@wPxnn?f8Pze?f#}WT(t{2)SvJ8|AEAH z_ihCb!N_#>G~Y*yPeD_^WyjxL3U(O&2oBLL_RF`M=6%HZ5&HKQC2fxOG47{>Z?_Bk zYro|>-R|*q&ga?3_>Fz$v+YZqWl#4E`?;svmp$G2H2b_~*{A)5xJAKeyZ>ejYt*oT z4!>AgYboX%mA#ujvM2aiO`%S5L5lOP!+nBB9e)+~t9^mz<=ZdUX~#ptkJR{^G?ugU zm5Z*fNqtF)x9veZLw~tUrTX??*$xC;6)G@s`627Ob`ZjzY z*}a4EcDz)gwv>zZ)KvBUD@N}i#P8Rj_mk2+>RmqKou(2BSh>l{X5}#s(knUUDt&Z6 zw;C!$I#LcfUt|&9g!qjpG$H;s=V=Ww?nd#V#_$0{y(aYkW*PNMjPB4^o9jHZ!J)sh z-nidkZtOJHFR}T*3kiNm5{&&3HvT~fo}9Ax6vWO6v9rP996W|N)^N_eJ;i@HX&>XS zZ2mQR{6+KiXM0+%xV~vlUvV9i_ILC8Uw-l}{>=R>O2S%ijFm~D#8m2el#l%^^mrP2 zcFJtFt53%sU|M;rRvBNhQ`k8%Tp14HQ=Q*RZ>Sa4T6+?{0skP zsf*UtXKUV%+E5p=HiWt1bz`{LN(eExx3oR6(r#?f`$9`+p+40UCbx*})2GS~_4Pr} zbFjQUA>{|!)kayZ|Bt5ofZuBT{|DZEXNxi-qJaj<3Kbzl6p4nBNYN0bZI)1pimZki z${xwg$jn~(63I$dk*sKa`afT%-~VwQ=f2Ol&ULPHo$GqNU+-~U$K8j?H#YrYINil^ zOKiiv5cf`KexLPTSH&kil@yMz_(HO;pE~JI&NiY`?yPQifwxt>&mGi%^tO#)?plbr zjs2=HcuT&=KWNE+YYj^w+$MCq58;m4LS{;uPN>?qNRJfwp=T+h@A6yLGrLlK(Jw9;@`@jlQ|uda=7* zR&kR&ry374UMcQ!{%&Jsu9f~4=dQ8drW{kG9p`_VyWOTFcfi~SrF>q^yzBQVj+&k8L4w($A>D)nOTV+EvL=FBp0foa)_PORAp3GTuJ9gzaQ`p;fA4Nf3t1BEjx70x-1(WEJo#0E z#LFwTB+IsvQxN$yZRH49+ghtSPHQ^JdZPAqh}P9XTWdicx6+nkhu@Z;bo+Jxy0%s! zZ{sW7Rea;_5cw!=^H9Fd17Z7~?y|deJ8iF__PI}yzJZ(%=L_Et(jVkrC(%d3&Erov zwvfTC<<VKnI+HUrLH=ElX*vfT!?quxcam?r) z=>4uf{XbkNy4QQS%}3VvaiDidgCAl0H|$v}@v)_FV>SF<3LAcd?W=4DD}FC{+oobG zsutXBbC?s{*%^v&guQPC->bl&k|XQrmuujDbx6Mr`~P07w)Q};wstP|p-u3|o#`Wcl0m`quJF7UPI@-P4gQAXo&}4~!urp!-xF^3 z#ge&xl;r+3#{ZTJY*qO@{wy6JGS8eu3f=$`wl++DtR&k@BRpXc@4LIs%X!* zkUNjsj}7`JZTueXdK&A&WYX#h$M1K?C-Crlu>MK*#^6EYNUO0@O|(v;1z)U3#MUrD z{-a5*(d-Ks=u_wFLFbZJr_;SpuoZ4~tp0ea{qC+hnGEZ~BGFU2zV^@3D^Hd#KDN{J z%udb?p@R>T^QH9v*eiyTgXxch^wHk>Y7csP51K*pIYADmz>AK&amTU!2*>`@O(2QW-|DIb2t) z{UFHs*2v}otOI9|)Tdk9K$CXzZ!7PUl=3J!oa706dW!5Hrj+f;?v!zNxOt58hbYnU z$`gF*r7kXVk3Mql;pxw`KTOS!R^LPX4|DD;&wDnT&2ZZT)I;igG`?^pq#EV@T!*br zhEPvnsllw9y!Gkc{@sq;$BLA9{yb)Km;G>!S+ML;E$ans`y*}cQ|m%4cE0nkN%gSS_o(Y|+Ly!90_Fc_w=rqu80XqpOjQJ>+z<|6WG2B%jO7@^p0Lu95SjrPt*?uk>nZ zzT6j;-j?g{rM0Eyr47mzr7g?au?);0rKT4m=`Mc6TrN2t0@|Vj0N$H2u z*Z$|1ewFI?(p#m4rCF|g+1|UZU8oe_Nxi(ZVfh!&Gq3b>=@nOeQ5^rn{~u-$TrBNk z+kd&oayeEiuPtp|-m+Y`yj{6Lxtjm7l2AkuX5~8NHKiTPDaRM;=zV!U zr<6B(4>zd)3)D;xzrBn(9brzc{?^Xk_DPPmvekl*xQRDfpZB>jfAD_x8+o%EX)DVr zZOA`S;@9|t<$bvpwNdF8_V8c%23Cq$|CK%bXEEcy7ys*6wKilg+e(@hg}wbfma931 zeg9YXt6#Oy-=zQ7k)PPr|6}+1N?Tpb2AA<+FY&s(M6-EDuXtRqxy^rK&L7s+Zzah` z(1QU)i<7r0Y^_g*`|2R6*m4%&s6wRoE)Lrzr=tD=@YGvzCM~|cvw$f9Z zlB0XrYeF(M(T9&DPmh-F2*;!Ib;BCYa+f}2eSFQA$a9eOD)zSMpu_!l*TW-Uo4Rr* zNU#f;UYk7MiIlBoZ*y{d9r<60L@$%{OZd?K#h1Q@C7-~cx1i7~r0cu**6hNb^(qv6 z1fIPM_h!Ms_+lR;&E}ABv73Kh;M%jK<9u>@DO>w|Jne0GH5+QZT-c=Ef?S_Fveda{ za$Y9qALaO&V}C%iRnGn<<=3R@H`2ZjMc*R{Uo7kb4?)p8-TN*_rbF3RAmGcceOa0} zAnH5N@Fn|C%5jz>FDgr(CHA#9oqs_IU&SUrw!hT9{`9R}_43Q~rh$J+?C6Llw4#weGbJ zL)})YgRD)9yz1au+ZFcf#&}vIR_=Y}wWD=Yu`01cY>S8PYB>xa+Esfvq|lwu@TR+2 zPxGeF_5M3*1--PizTSI32!5`%I01fNqV3(l?{uq{JCjBCY2K!XwfL8KmS$_^pYRX8 zspq5*+~e#F>r-0%G(Mv7ey54%nhN_T^DoZeRhtFDpVv!XV2ysS($}t=Z|`e4BnD-P zTz+$Aq2BVH{q>LwIhG?bS<%;zPgGfd2>F+s-;ialmEw>{H?zCqyGAz z<7@dL|6-e7sV}eK+o~jzq>?C+)kVLqaKGP*{JxV{`u-v@F-u*WYgWs1t^du9fZvHk z*}l|(ZM-pG=cfFe+m(|4GQPI|iu|`I%~z5|*7x-HHDQ;eouoEXS-WF)|K%67V>Sb$L|j%TEc?kNbe@@)P#0%7`!;j`PN!P zQ(GxdU1zq#preb_(QdcZrncAOs@bmYY)x%6ww9738))I1LW6Z;qJGzom$6|lg8>Wm zzvx@vS(nqJHj_&=ZFM8pZK5UqglBw+r-Vzts`tLCzsd zs58SnYF@ue00(Dpj4#m8~^_#n*0o!nO7KF`M!IoESu&$z_+PZ((O@3lq z$-0!?F#i?wB~y(;Yc8}lt} zFD9`%|HBqy#CK%p-j0oA8#eE%d=q;W_P0IR{ho-O}TlCH}kL_4F8Y*cOR+bt{ zUzL@mnsU~0O%;}pl@*(@&uk&r8e+;fF&2NFvX$0-qg2b3@mFPAuFQ#n|5tf7k!yW* za+o`{QSg7Wz>aZRcu|{%Yqh)zWX(UT@cKU)0m1 z`#!I)y-KQnNMg??0l$F*KNPx0iBG^KbVW=K1p7IHm#c%ulA=rLE>ifmlZ(?&~uqwB6Nc-+Lomv{%g z=5AMn=S_py)9gJfs^(=D?iX0L=g2oPHPWWACLS%_ad~71j9;d74;{=gFR@?LAGR&LoxIbp92R^%D{@ zvgtiC>TSBz6H>lFW1^vg*1~l*~vHg!73C9YLdjkGHga1A4Zcn&AG9o^JC$-Y(=i{~Ik?uRe zUO&heTR|UNU7&0W{Uiv{Oz%ky#=d%Ob2f^4?4bL=u-NM?wT=w$}eTT9>nrJg@t+| zJNP93)7a7nOWB*ndk}m0#k`8=OLGBhc^4M;^BnJL2CJidk*$yY*y1mf_a)Mfm(z6K z#%b0`@_CFE{AHfRd2G^iSfpPx2Iv_!@fUdko?``nhEHI&W6!aMzszg6luvXSJo^cv z{bG9&AH`4BrEu;`D7*qLf9u;ipE&a#?AmNy#G_Z$#WrPC;vIt5qh1?7SvyhItRLSg?Swu@2Zt`b^WQ%5A^QOC;xi6 zuCF#YSlY|U!kb{=G;L+NRuljEquS4_5bkOAm)LY3#A|Z=MQu9&&*2$qUw7dwQ`qM2 zVv&iC6r0WS{@-QMiM{emZF#YF`z!2=e{ngT;6E7o7k%I>=a*{N*{*7(Hudj5wS(_+zLq{6ZAM-BjX&1HKXu#QTQzh6?PXB7tf75FJbNxT`-USf(4{bSE{jrw3nobcPZnl;?zM4N~ z3F+_!zVHroe?~8Phu-ia9sU*DkLpiz^`Dmq-rH6#i-znuNI>cc1;67MN^ocQ8 z_+|L_)!0dR$_;wp_3-^p5?~4maIO8zSkr_3qx@bj#Q?u&LWf=j1`HyHt|>H<$e{4? zA?)gX3obsvoid)}_9DM=e(#p&I0!M$J;u0SWZ4+FF~PCuD3hd^Ov;V(f2HFW`yDQa zOZ^WbeFj?3cl2C1v>v}sfowfsR=D;F(61FFYymsNPj}+Gs|7>r+HTC}w}Ww`n_3go zvcbBgyoldI>3P;~;P3ai^S5~NB53`K^MBc1gTrnNi#LF|t6aMrUtU`HAFDYMOx}^t zx~4QW$+Sv%`G)e}5`W&5Y}*P~-CAO%l8YxwDh&>e_E&sm*_RFsac; zz4xZ;^wtWlpm#-AyG$jrkO|Az^FhXHzzt_h_2p4Y(Ka z_FuEKoXzyp!k!b(`6X`pqdU&UIzKM%7jF6ptbP#Vd=MLbhz03McYRsPS<*d?xxTFo zvt2zKbA86SS*{6BeGV^v*7*n7LGE$gBlhot@eg3UQ{^?j!1UYwPsOS4W6Qd`;Nru@ z4PIgIa#A5OWT3rsZ1uF&O+BUV203;)ZhN7c8m8v@vb_#+WR#p@8;y+UDNQf3pdHS8 zmc961<0EMc?@uHHIyxE~%kj2a`aQJJOO7b8y$MWi1g{fwv=fA_XKxE_U;}L;Hovm} zZJgZ_R&GH~$L>x-H+#`0t zy`XY^=)9x<$l}_rt;v3~nY^m|-@;umRLe}o=Cuc*_GvMjDkoiPC zt1Vw!Hz*g|%4t&ev>m%ccmGFVY>7G83#Y4%0XBwsd*RkKu)cNl^}p!R|H7>e^!E** zUUj{CC;Tw6gZ1>~=GxY=nl&8_7HlSMIC*90P(^OrL8vX9-&~5_;KM;|5#gDM zfoR}fHRQM&*8KvJ{=i(by%_$?6MOTAGAxFn!K=UIn0Udj9sQupv!{6}cUZ4}rVLPAc|1tW|9FlepMjD&`|H#!Z=~fG&+|P8W?@8t*eCc0%_urG@ zKe3B1#9O0b{f2X{B;!}$n5*!E-wGXmL+x!-?XZScvm@rvz*a47br)@LE5~=T9sRJn zw$?yvOKjdjddXp0VRQe7=uM{08YcZ&cq{wcW3FB{b{Y;?H`Ys zo$UPSdTe{W^cYv2BE^|f4AjF%!iX#2ci)&O@%7;eQ6?dIVZcm1creu?cC5o}FTukS=eVrkWXrwn#L;Ce z+CuH%GhFMvLf3d+iiewfygn8XX@cOL26 zO-&wE)LnBpc&N8?h*oz9gg;tKJI;ET*0d+S+*mE|Ypr9iE-774iKF#2Qm-dzdk0F{ z#JkKp+*v)xkJd&DJlfj9Q}k7e=uT(5Z(=70DOq<`rNmbD@dRfoaZl1HwyC5&LCO<6 zdF)t6TBDsEi@UecirPvY-(Y+JXR4(Eu8(iAqias~Ob1DOv};%+r#92m^y6k&TXYgA3W8Qbyc1-i~1f^ytBdH#c&dPtll!3oE_o)_4EGD@@6}F zmUc=O-$rYBG}Ll)@BQr^;EwTKRM88zR>Rw>;SK$-=8vcZV>WesBkg)Q4)#4nT4Mbh z7JZ`SE#Rq$y=S&|{91v1Z`prAJAGE$ebDb~&U~yrKMgq_g_cih*YPC&&$;)t`1mj~ ze)QcUZQ|`8qm@k3>Z4~|j_uz-kC;$wkAufoV3Co5nTO^i@g^Nfnf9!&XV}l2g{=$u zw2o}4sMrFxuBa$s+Z&QTOG&vkj;~@x`klP{75`mt3tv^(UBZ2rkxGBLCVYPrQZ77w zClYgSOG9jSNBnmelBPbHb^tp`6C5}(sYz9z^l8S<-BxLiWNVJSxC{Hoxyn0;WEtZ* zp7x~E_4KFo_q+7(siesxj;$XXklLEhy8D@QZ_>ivvH!UxQ7OL{i1ChdAL;2ItNTyY z{kM*<#*er42E%bGdy6$;Sp)BOcP*>7H=RBfopu+fx1PW3S=fhHd!K*ukFF^EPpjp# zk@p=N$4>HSR9th6wtH5A7gy*(S87oYLf=Oq^BYh-SpEuj_#}j0@0Xm1?S78oe$St{ z2>1OP?_J4g zGC;&g7ttf#MWpmJM`|B)uwE(-rMI|~A-*5hU*y8z(ipR2kG5Rr|60*16GWU`BjqTw zs9r9@VWe49M>{e}49XfG0&Jw7hI z;oQR_Fz%P{OlcmL@)i*=*SgnKM`wy&c}N`0bFRD3w;pGdo>jtoJi%BczCrmWNt-yF zYt+G2o^70OOkQTa)|V&mFJe0Ss)0d{Tqw;5*Clpmq;Kb3D{g0k$d5}LztEX;U3Z!~ zJwvUYXzO@mMh_9evxm{6HR&Ha@!MAOmTJ&PHldNMtF&2ZiCB&${K?Tv{-V3A;mcia zOw;pj8!Zb4!w~61KgmR=*biE;+i2%>7$?Ud7I_ zJ|E%cP_UAIQQ zi6uy^#3GiI#E&ILzLI+`#oxY?%cpwbht4d-;1;qr{zeKX=lNLO2c-VTdhi0A{(IN| zQpo5syUhm5y@j$xtJssg+gG1$29q;aXXB#(rtem!%PfVrKkKF8gFh8{|0|#W3Osm2 z-XFMAa3k?+iP245{yZgl-(6pXBZ;>kOcyxY+8=J704t-DU&POKCo~Gb7>~`)fXz1) zHk6xj-^sL%8w(rH70~xO{5Y|~H@o6K4CE=6qVSN{{*Uwi&;2s?Dp($lG7(=M2eV_Z znIg|Ah41z*cX`ZxGQUCi^K>PMEom%NAFpg9mFZ#{%Y`s=p#Px-S0ADziC^xIwYTSy z?QQQI=lXk|aQ3n8k?V)MV|=Wmm3E>#UgH_YlO*x`hA+)j<_}rGK2?)n!_dF9m*sv- z+EO)$w-uSUJ;V%7?(JQ~&wngAd@AKKkwnrK#oDlR^So%eQ}`%3xU0rMZm)t_fac!sC!Sr&$1{#`5} ziEep^?c;wCKY0J0@z0;|qx~+%<6Zx=3;)??xc^&t{*&@~QqGTCp0Yh#NoLCZSx4tU zh<8~dJ`lh14ea=c`*E}uFqZZrjoqq{*Nkk;%8=yzPZC)Z&revY$X>48rtM^oU zx2e~g9M2qA7fKhLn(TK93>!-anI`Qu-s2#3mwLX;nRpgw7JkL(Gk3cC6!LOvQLmZb zbh5NJd#fW{dp&k`z5MQQe5yNK=J+V@c7)}|;@aEg6`TA6>gxe-;Bj>|(^JLnezmd> zQ>sh7rR%+s@btv72RBcKn_Y@FbOyfIt!Qzr@W115$5zla9^BZ%;#)WXD?hN{>PHkD zb9b#aw(i}v-5m=}>R`W#UE7VGQwKw+1(j-BtHZ`MP%t`21tk0pF8vMRRu-IM4NP4} zZ_WR|`b1*37GW|A^t~mv!zY8+!R_e6YxTs8M~YA72mNxf$yTBzpB+z4tl4 z;hXO}_cdf(?5@8`|D8O)wzty#zOntO62+bv41CPh4`EdIL7&7HUjl1-LXST1Ffkt; zG0Jo7^($gi&d1(*!=8&F5AZD#v0Tl-6Kp!0`HyT2ocNgvzp0gmtgfA*FZ@;=DX16|X_ zea{p_kXVybEXiv(+_Q=EvAZ_1-CX)T+~FYq;lE*~@tkGp`xL^M>CS{XWNg`2-K;Tt3R#e&67Ie3w<`V^*2Z z_#eN7+JeEsDo7awGENO10g) zXdl|DbVbUhg|jtsmNW530E9l4URyEoWNoa6s+@{E^Rs0A+8{@0T2(V4a&+oMxu zJWH%Gb@aC8`dY@g$5Oo?eJ(zv<1y@vR>^qlV0Umn(GIvOWSUo`DrZ`;KD!>SBu-=YFKP;6pLU!_8N9 zlwTpCmwH8p~n~tB?U((>coIz~{#AODF>`~h3dvn{cH>H39o`_ek!)@LNxXXMX^>g&Zq-o3(R^@^H$Lv4Nn z%_FOxDzJGX&N^AmM@HOGyoCoK`T97NV0-+j*E=)CJG|O|aC(gQHOlr7_V7!+$%`F1 zjkFGCp9q`xg~q#J7Q1LAJHXhCk=jn{*%}&ej#I3GryJoLU&FWGq3llu&V7m3e61zM z_q`C_yz?;%-?tRpDkCihz{Kn9r(Iv5CtQqK zoev}X!^A=UyF$v-SWqvp9ldR@>cwiUl%qUho=oG8EG>IKpd za4t62jQi+cu+l4SU&Qw}#JvVU;7k0*S2tLy(e_5up!+*}mHe(M%GF=*yGD=f;pi}F zbML8^OI^`VS$fE+4McAWXB+A3!Rz4Vq1L?$bUvu)^<4^VJ=+zjh1)R38T$CWo@}Zo zzsWOP=Z+aoHwEvVqV68TY-g*#$FSMQqPqSEz;SO=)05>e-fzlswx{T==3F5E+!%nM8xsle#V`5c_={5~Bq9wj}`)x0msh!Yl3Xa;Uc+kgJ1fY>a@&OT*qUWO;~f)4lDNXEaw$2o0o(spB+SPCH*qw+ zfK8-Lbm6}h8?O5T2G4=!|AWMzLhMice#O%Nm%I~exUQlK+ssDtuPLvp?zWYsw*RW; zEKHeese_h07Kd0`laYCg#}|4)GyAR7Lt8c2(i=U5ylmmOIeD5Gm!vz=nz+E&V={i~ zByS__r=R!IQ+w>ItwjDG&sK9JS=`ckGO6D|x^B|P*0jD|4$=;X@}hKKRQ z%q;0&r;okPqLg^p#K1lR5fZEQyc`~K)xB(Vk80JAK#dn1f1e&XpB#%my8wTRFCuYf zYv{-;$+ex}Q8eS~bfqe+=j&qw>yc*}r`dufu@h_M37BK7wVlbg=rUc{FOM(Q=s#_s zUF2XZcF1E{COhJ%vEO&5?VKoW#w2!yhlyKRub;#QJ&HsePs%-l$Im6N7SRwAueX4N z%6yH{6aKOO3*$FnZ>kLY*IFu*SG7w2N>hd8sYE7JA#JLWMAgZ$t*w9a$Ce#0u~4ls z*H(!HS;UjmBCbG!!`Q*oE&i!D2 zh5N4|a~9EAK6mAhq)y~ZV(#B}1Kr@Pg9i)X!(K6Z&(if(u&F5ewfXpb=* zLF+tNE$9C*YN6~NE zL$h}JNhdw1nI6!-=u5rzf;05icJ@!vL)%$8Sx?qeI~4tf*17eQv@ZnRwn#5VT{#kk6Ly8AbnX?EL=v<1X;88e9qAt;F-VrF{N@ zb@4lH!ejX}1j>Hw!vDd&-(lX*xaE9N9q&M@cMCrJKRo!A!teO9n7vo9(plmR-^6(5 z7Uw^JZ?EIVpYTh*?fAz9)&=vP#b&3Q4PdtOkJz3m#Vb2hAw(kUP#0lbHC$i5PNX46iUE{}R6wFy|YLt-nW<;T4$laO+6%jW=V^6XkfT zGfyb(O|H9H?lbV~yW}|D*=t-qQeOSd4bWc%;}B7fmx^<|1TP=xtDPf^+8=F3fm}CP zX(q^TxclDh+Pma>zu3y$X@I!Mfkg!680oKbuUpLla6d*r!_z!qd6p*diRF9s@(y3^ z68gnYxcN$6-uQo)IQA!g{-xtTTEBKA@!Ws2x^L+n@8a$6=PkC>g8IVUey}moJ`a+( zGg;4Hgj{bxu=u~ehEd?Hk z-pl{45UMUqMkDNbXBOCcSa)6NYT(GtrP`}V+1x0-hW2-tuAyT)$Z>ab_%(IBfjiW+ zzopV_VEq#&u7z^d<+A~c?$&aw;e5t`mdsieuDqSH?CH9F@$|MV@$D@gSl&-Irn4_g zd|%e&ZY=O;8!_0y$j^-XZ0*^O!2OS*12i*Ea6i2LAWI8cK?_Hl+1^_Xg#YiVPGX~3 zhm$YI$Nz(k|LB^3!0e5UG1|xsgqthzay>m%YOWj7Z znMM{|Ll?S~-6QcQ-Tj_Qk|Z*+3IEpSq*HBLS9SK(hSvCLchwFOr@1TpMq?6dH(J$J@WJ;{~xiv{vbtc4)0kKZ~X?z z`8+8cd3~?nTkysaTJQNVvKLN!wyki!b}+9M1ZxHfGlHNlY}?N7x{7?mV1@qiz1Xb9 zeizYl|I~Mu<4wO8ed$NXzbo|L%yqF4qPziH=0KM>^t*Zb(jSFyYjxr6ss`z*(f3R1 zmg!$h(f zk?#@=#m*w}u9xEkZ}n=wcd-FH?Tx>ojm*_P-dD;*P{uC*Il20wmJxs68`{GxTG8Ds z4cC#K(Sa^?*GrYR4>{IddCx754VUjAxnFL*!o9|mx(}&~7v%dcS^cJ*ztZl$fO?;k zVtyceOVyqa+>5C>HaNj zdGUVy>$(cQl}+@6_zY|68;x+hmJl@DEk5~!F}T4ee6fi))%%hz3@b(cQ!9}bHqafjBuO8l_F|`=L1Im0rMS{wY!FG8 z$cV_co@)Pe7Nr4>#D{Q&b2s9jSGsz-ry;-DfC_o<2S;z{~{Gv8BbaTu5U;}ZEYsR?a7^8Nyx~hs-#<8`#++~%mzZ_Z3I`gLTy`>DV(F`(@^p}Ez z&mpA~<2Xl(>7>WKWX4qXnfv_T!J2+8>&%dXZBHQW#}->d)XgRQA*0mCbvF4>t*3?7)0AXtP)Mh$T2A`zkNWPn+C}U;uRxXAFlH_kcpL+|Q|#`pGWdgI|8AnmEp|0u`0 zTYE_vguSTH6}m&z-f|h{4*ebJQ!u;>{r4+0<8Z#yEYXoqk$3d%=-tt+lV3;qA48kS zh|y;5(vYpA0T~pD(*$d5Ocph=7X}$QRGqYH>`d%IO{}|;W{Gl4jP@^NVCE%UN;dvW zE^g>pWbmdWT{Mp!*&h=3c#x$D+e1rI`*0;W)bBB#^cdPpQ7 z9APW^e=FCV0pHIhTL!Z!#HMwT+8hG^6PMV@GaezA<1mAGXJZdLSA7g6d(O2NJ4Ebe zmwF4gk|9@lE4R?ZZuagU@lMk&B86s>mG5gU@o~Qc;Xm>2GgdIM{!co86Djm4N&2=n z^nsRG1=!`kS2C5MSDg zU9yV*j5o|oC>h~Ur{GjAAXZbTocOP{d{2=Rr(*LT4a5iXrU=i=tO^zAgc%D071|zyF#Q^>JO32Vl z|2`cKAMSZFZt`f)m-!x=ku%Nw2c;v&61$aX#AZ^)4iYWoUEL5P1To`!EaiLy#;14_u_}CxFMkU4 zKEjY6gHKOErQ4xW#$-<8Q9E$;4>fhWyQAsQ4MH`VXDM2OL z$#PbkU*+_x(tR(FSaz0@H<3w8?QbRj-RU{?=v?tvBx^@PpK* zg{>sJewE)>?zq%5EmDrxmFNjptU2sPZ+L5ooO+HGDgOEUyzK}7&*wYM))+EptalRc z^U2!NN#sYO1rOJz;z4SlT^*@Shl8}VbtLX{xZm~hgNao<$??|O<@)sDFqN^B#Ja4~ zbK{qdpQ|^+y3ExU-&p4OXed=USR*~7o-_3vNnA#> zxGKuKg>+TmOeJ3B-|?kHyT%6a4y<|>H;%RHWm?$7w%#P`-h*2i)$}mQ8N|w%!cVc| zZ^^><{UBH^EMsiCL&&xZVfSE&J(8|+HAx*m!kzM-PHx{Qr|YD?#oexwcgE)444dze zf94;Ef1rCICHj%G8MoAl+&jCFo0rlGqJLcKcepG2xXUPay$}N!=iZU=Gckb23jHY~ z#^)FHlG#}1LGnE7EJxmTX1%`hlKOg2{k-NsDHf=gk8OuPMBA9B-WK@(h&KE$UK*U= z-n+_p$Ha1m z!LQE3kVinVu;R#%$cd49RWNq_{!o~HHi;3Ic6H&adkCj{6y^q97nqgk=c4ylf;QD) z&t^F6Hnz4eRX~IiW}EoN9WmNiRpOgm$nv`eN6maGYxMu03q&cEmcXdkkw1md^B}?x zaODel@GD&S-aVFMwwthk)Pfta4aFXlSjpIR>a(n@x9uFk-qMzhrWFfIdqr{<@2$yj1+()s|tR3NIHAILx^L&JGeC z*jF@VCsBdt`5z(Wne08?&EnO~nT{+$r`YaiyA@m3-tJo0bFa5uZK&$TwuPx1se^nQA>VQPm|XZD&Ej3s;vHD{A?$mNgnF4IS}%*nkudRV$5(opx02ah zE+A`%l0O5zr^}qZp3gNgH&aNjiSY3Xk|}Y8;qK9HhPdV|R~@NEL%jO|Zv(SbO%uN%yvwxkEE{J8ZLp;XY$$8e0-` zydn0vDgL>gV;k81v50y2K@7yF?9!jJE`P|%I){Zgmg0w5c<*DIolw|rC-@(4?`nIa zSbY1j^qyLAc16dEbNqL#IL^|t;xKFLiUTT|RMe|zP_cK#p%uIOUw{0Vil!AUDw^5b z%husWy|=DtS8=SheX$&Gt8>K}6=%wOuo3ZHExqjbG-iGvYw}p*$u9%@AW@k-HN84jvwEC2l}c5(Lx6?<0fVXv<3-74z4V+Z+mF_Z1N?lIJQ5nJ_@ zEavxn(wQvL8D~G+*5jUjw%T}~1^r!-D{r#P&$s`U$dy>|-w`#k(7CzJeZZdfGfVWZ z>|QHa)_!DpUd$$)Snn-Zwd&&UyQ}}qZBmUdBL0T@Sbg5vKKAytW=zy>QfD662K0~2 z`&zr;?gz4d?cvSFzi|v5AbL&sdu&xVvTen;JcYgJ0m%F$S@TkXzQN>-3HnA%Y32is zN3AjZIzWq!ZKabod^{9xo%r4)%p&7$?uBqSN_7|Jc87DfJNtm;LH+MV+~WZ}FtcOKfqsd{d(ZklM)#Cs zvteT9yLd%TPq{jCZrmxaJFxf5lxvVu3{}FRN;m{diJqHivvVMAe|us3L##uKemq_e zzFwcbT%R4I?AI#M1qGA8Sbll75tu?Ox|u(_H=dm_8K>#XClnk$oc<_1`UF;_PR<^n zAMbBHPCXq<$4K3El>Q{!8AG0Ux8uo%%psL=BB$8OIF8^$uFrUo%=I0c%JHu0AnPHHg<*04=7!ca--KUFvLSyLztFPvTCZbF81!t{0X$R=Odc zw7(}DsAT69&)QR}u4s@Ue3K1%iS@$!3r7U`)5>zXZENew~1m9NjgZ{y` zzt;Ib_&qDF%k~!1W#oBP>&BKXSw^-kVlm1^9LKsMUSk1o#Ag*>*j@r%Kjv$gD^Bkv zp31pAp&yI=`_Mcr&+~N7g}0wqe8aalpO@kjzu!RJuU-EpG@kGHk1+U8p4Sz856k&r zzjh>Xgc<*tv7rm4Uts-Cyhx5OaL?ad@wYNBk^XOf#Kig~{hyvEX#O#|_<}pU<^Gvr z{C8&(7g$mcOWiBhoz?0g9>^;2y$1BIq9&_}b;+zLn}|W#nExZ5kj#~`#}UL3 zBt~;MbAavU`eA%v8TVXY|J+xqHU-XQY})zy_ZX7kb`m0yYZLuWhv$#M^;c=5Z(wMz z`hAKfKa;=ne)1vlf}h~6(d6giaIrvqVDDRe_G=7z0d4*tF&4k$vQ=p1d*Fflhz;Bq z7p-UOAguLJzR;t^2*yu%viU^^Vwh*~C7w}?Mj44|4&YPl;@oMtW@o7~qii?c#m>_7 zFjA$rF+amCqp{biM(bQ%jC*;?h?yHpci^()G1-gd(;sK;i5++4gKR-ZZ-J}sQ*i2? zFx12!*1%jh$Kz|^uko+$!{^!FUE3;OyF!0&>z)}O)2le%j>of;^T*QckM27bb<|VN+3qCAmab^8##;H!_K9o}8PC(se`}VB zeZ7NSif3omok8ixQFKi_?^h4h4+1~Q=-utuO{+nd~qpTEnJC=%=|fth1pFHh&QA%zP^%# z*bwq>4DC0jnZ|Fml5OC7(&7(Gd|}^_CtnnL?E?St=YC|3J^e)*{A_(K;}_qC^G_F1 z2ASD%?m8))v=IC42H{swycP&)W9>-ss31{M9f8;KALZ~TlW(AX2>oiT!~ zu)`xQhmrxukOZedh{T#DGUiyG%P!KdpA%^ibjaM|gNyvSlM%s=%oW&EigO$t;*P?(7?>yFTV$05;c{C+&+K@Q0L3ief?do__Qsz*ysR0Rs13c?-?%u)k zcd?#sNjdr`NjIg=%#IVF+;#jF;|u$B=A=w~N1i`^;LItNr%X(C3%O;ky~NljwsA|C zohbVnB;GF0H1L}_1!7@OJYH}4oUb;{vyLs$bPB&k=A0T<@bK8r6XSUY&24?0XJ*lT zhHv>v?>w_w26NvntR3<5$3~v9T?^TZzQ+}!4Sq)({0)!z#*vIVSj|6^xph|48Q1fZ zjQv_6?awsFY-gOqD)^mm(WfP@!CW%Ve~Boq#HYkJSqtmW2;AtSu|MyP>m7_UwTI+k z(Z@jV&VDl*sT)q3S(Gwf`(m6dU-P@BVAVtL=!_u=AJ4pdPwTC(>D>$U@;})lHlT-Z z1@X3|mnZ%$_JwWW;m!~uV`esG+sIhIZP--nvOzS28_l80f!00!Hi4tt(ebyGb_Zvx zLDAS?H?=0tWM^2q9qs=oGC+px9m`0Dzt}c@fiDZ;!-qQJa4b@@E*Y2%N(73xfO^(}Os z%t3e)G#O{P*V*Vp8OxLy!h75!5-;PLGS}5KYy6}as=bS8CFf(!8MAc?)QL^$q=NUJ z>K%m1cC**Txo*-8flrrs9~sXx+V2(CagJV4@Z}D!40mti-8HAL?By-*OdiLN+!D(_ zvhWjhbpCiuI9~0HbUzwb&Y1o93ERja5t3&Yx%-YdGS$f0Ao=S77 zwsE2s(g{+g>=|p;-t}iG+|Jm->UP)T1!SIyko+|oJJmEBaeOU67Vo_8d4333jDR3}22b`b6?=yeDTI|#xY z3?GBoZJkTJ_5P4=KP+)K+j|x`m^oB-v%M<}+6xYDQ}FAJ)+J)+9X$6nJamrrSl0hlUQn6`+`tQcA91t`&aBxu~WrX728(q%u-a_ zcKwQmjyJboyP`%#P5(PoG-AU!yrK=e(t#{XO>7^{F4RT5;Q-$w>cCEPj_(RxrpyLj zRVNuYG*5lbxBP&ke@*MCOtbl;i0X`mExdj^8dFU&qptVX$a~BPtd`oq8T`&&3t82X z+)8}Vwc6Woto$l+Dt_9`@RD(7&(RT|CMjNFEq~Vb$Awq)XKdmNR`&1geL`k@jd|n? zFNr3p2jTbB+V|C;Gf#Eqc{oeoKSnRu7xLGE2kG|*(wq->{5ZWNbByeb!yhJndhgkO z)0^}4uq*WY!Fq9c(FL^hAqDPS1@U|7)A5-`Moh%(AApMY!MP{k+}jZJJ)AOCZycHwln^yCePo-)~dD}dfu8OSYo#~ zCl$BAng6R)CLQAgsKQSfe`Us;)>e`RMl98o-;TC2PH7FlzzY7!WtP9C3%8y_R=sIW ztjIm=W|>1FBWfSTgkLQf^Cy1acQk(38Qy6|31zJ5WN&JF(%fk2U!uWkl3G-af>$E<>A`iaavsD zROXJTsb$83Rh?CGJKNjZ%A6pXb@orZCmiN~n9yf9QbrBLpSarZ&+IFeSkiySkJ2CB zg6MDQDfvRdFP85uVN~%fO~c0$kunWpk1ZJYJ+L~ce=Bx>GkD zpRCumfz+9+^ngO|ZjYsgc?Pvlz#jWT_>8wXUEdDUXa0?g^yu3y8BviKz8uL|rhWy7 z9)fjWBHb`ocP9QqP6u&=y&dBXSs!+ukmE2Qe~%qS_Rs@Xhq>{qko_%;vRpRg%0 z(|=;4@!#zXrQ%0R{9Ik{yTqgSSAk$Fi}$#JD8S4s65I4@{^HH;XCA(d{BFoAyG%}h zc;{7FCw3}Y&OR)2LA7{?_bc+N&@T3oe>iS^OKfs`!M|O#vsf|WYd#D|I2u<-T-0H< zPQgyPLcPRt#11@Ii=BwyJpk37)1#iCHO6+4$n1ypoLTs8V%F}^yC͛@2QG2jRN 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