WIP cryptographic design

This commit is contained in:
jaseg 2020-03-09 22:10:46 +01:00
parent b0a5232487
commit 6880468862
9 changed files with 292 additions and 6 deletions

View file

@ -60,6 +60,10 @@ DSSS_FILTER_ORDER ?= 12
PAYLOAD_DATA_BIT ?= 64
TRANSMISSION_SYMBOLS ?= 32
PRESIG_STORE_SIZE ?= 3
PRESIG_KEYFILE ?= presig_test_key.private
PRESIG_DBFILE ?= presig_test_db.sqlite3
CC := $(PREFIX)gcc
CXX := $(PREFIX)g++
@ -90,7 +94,7 @@ MUSL_DIR_ABS := $(abspath $(MUSL_DIR))
COMMON_CFLAGS += -I$(OPENCM3_DIR_ABS)/include -Imspdebug/util -Imspdebug/drivers -Ilevmarq
COMMON_CFLAGS += -I$(CMSIS_DIR_ABS)/CMSIS/DSP/Include -I$(CMSIS_DIR_ABS)/CMSIS/Core/Include
CFLAGS += -I$(abspath musl_include_shims)
COMMON_CFLAGS += -I$(BUILDDIR) -Isrc
COMMON_CFLAGS += -I$(BUILDDIR) -Isrc -Itinyaes
COMMON_CFLAGS += -Os -std=gnu11 -g -DSTM32F4
CFLAGS += -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16
@ -107,6 +111,7 @@ COMMON_CFLAGS += -DDSSS_WAVELET_WIDTH=$(DSSS_WAVELET_WIDTH)
COMMON_CFLAGS += -DDSSS_WAVELET_LUT_SIZE=$(DSSS_WAVELET_LUT_SIZE)
COMMON_CFLAGS += -DPAYLOAD_DATA_BIT=$(PAYLOAD_DATA_BIT)
COMMON_CFLAGS += -DTRANSMISSION_SYMBOLS=$(TRANSMISSION_SYMBOLS)
COMMON_CFLAGS += -DPRESIG_STORE_SIZE=$(PRESIG_STORE_SIZE)
# for musl
CFLAGS += -Dhidden=
@ -154,6 +159,14 @@ $(BUILDDIR)/generated/dsss_cwt_wavelet.c: | $(BUILDDIR)/generated
$(BUILDDIR)/generated/dsss_butter_filter.h: | $(BUILDDIR)/generated
$(PYTHON3) tools/butter_filter_gen.py -m dsss_filter $(DSSS_FILTER_FC) $(FMEAS_SAMPLING_RATE) $(DSSS_FILTER_ORDER) > $@
.PRECIOUS: $(BUILDDIR)/generated/crypto_presig_data.c
$(BUILDDIR)/generated/crypto_presig_data.c: $(PRESIG_KEYFILE) tools/presig_gen.py | $(BUILDDIR)/generated
$(PYTHON3) tools/presig_gen.py $(PRESIG_KEYFILE) $(PRESIG_DBFILE) > $@
.PRECIOUS: $(PRESIG_KEYFILE)
$(PRESIG_KEYFILE):
$(PYTHON3) tools/presig_gen.py -g $@
$(BUILDDIR)/generated: ; mkdir -p $@
OBJS := $(addprefix $(BUILDDIR)/,$(C_SOURCES:.c=.o) $(CXX_SOURCES:.cpp=.o))
@ -180,6 +193,11 @@ $(BUILDDIR)/tools/dsss_demod_test: tools/dsss_demod_test.c src/dsss_demod.c $(BU
mkdir -p $(@D)
$(HOST_CC) $(COMMON_CFLAGS) $(SIM_CFLAGS) -o $@ $^
tools: $(BUILDDIR)/tools/crypto_test
$(BUILDDIR)/tools/crypto_test: tools/crypto_test.c src/crypto.c tinyaes/aes.c $(BUILDDIR)/generated/crypto_presig_data.c
mkdir -p $(@D)
$(HOST_CC) $(COMMON_CFLAGS) $(SIM_CFLAGS) -lsodium -o $@ $^
$(BUILDDIR)/src/%.o: src/%.c
mkdir -p $(@D)
$(CC) $(COMMON_CFLAGS) $(CFLAGS) $(INT_CFLAGS) -o $@ -c $<

View file

@ -0,0 +1,48 @@
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <aes.h>
#include "crypto.h"
#include "simulation.h"
void debug_hexdump(const char *name, uint8_t *buf, size_t len);
void debug_hexdump(const char *name, uint8_t *buf, size_t len) {
DEBUG_PRINTN("%20s: ", name);
for (size_t i=0; i<len;) {
for (size_t j=0; j<8 && i<len; i++, j++)
DEBUG_PRINTN("%02x ", buf[i]);
DEBUG_PRINTN(" ");
}
DEBUG_PRINTN("\n");
}
int oob_message_received(uint8_t msg[static OOB_TRIGGER_LEN]) {
struct AES_ctx ctx;
uint8_t buf[crypto_sign_BYTES];
for (size_t serial=0; serial<PRESIG_STORE_SIZE; serial++) {
for (size_t dom=0; dom<_TRIGGER_DOMAIN_COUNT; dom++) {
DEBUG_PRINT("Trying domain %zd serial %zd", dom, serial);
debug_hexdump("oob_presig_iv", oob_presig_iv, sizeof(oob_presig_iv));
memcpy(buf, presig_store[dom][serial], crypto_sign_BYTES);
debug_hexdump("presig", buf, sizeof(buf));
AES_init_ctx_iv(&ctx, msg, oob_presig_iv);
AES_CBC_decrypt_buffer(&ctx, buf, crypto_sign_BYTES);
debug_hexdump("decrypted", buf, sizeof(buf));
if (!crypto_sign_verify_detached(buf, presig_messages[dom][serial], PRESIG_MSG_LEN, oob_trigger_pubkey)) {
oob_trigger_activated(dom, presig_first_serial + serial);
return 1;
}
DEBUG_PRINTN("\n");
}
}
return 0;
}

View file

@ -0,0 +1,33 @@
#ifndef __CRYPTO_H__
#define __CRYPTO_H__
#include <stdint.h>
#include <sodium.h>
#define OOB_TRIGGER_LEN 16
#define PRESIG_MSG_LEN 16
enum trigger_domain {
TRIGGER_DOMAIN_ALL,
TRIGGER_DOMAIN_VENDOR,
TRIGGER_DOMAIN_SERIES,
TRIGGER_DOMAIN_COUNTRY,
TRIGGER_DOMAIN_REGION,
_TRIGGER_DOMAIN_COUNT
};
extern uint8_t presig_store[_TRIGGER_DOMAIN_COUNT][PRESIG_STORE_SIZE][crypto_sign_BYTES];
extern uint8_t oob_trigger_pubkey[crypto_sign_PUBLICKEYBYTES];
extern uint8_t presig_messages[_TRIGGER_DOMAIN_COUNT][PRESIG_STORE_SIZE][PRESIG_MSG_LEN];
extern uint8_t oob_presig_iv[16];
extern int presig_first_serial;
extern void oob_trigger_activated(enum trigger_domain domain, int serial);
int oob_message_received(uint8_t msg[static OOB_TRIGGER_LEN]);
#endif /* __CRYPTO_H__ */

View file

@ -248,7 +248,6 @@ void group_received(struct dsss_demod_state *st) {
/* If we found empty entries, replace one by a new decoding starting at this group */
if (empty_idx >= 0) {
DEBUG_PRINT("Writing to %zd", empty_idx);
assert(0 <= empty_idx && empty_idx < DSSS_MATCHER_CACHE_SIZE);
st->matcher_cache[empty_idx].last_phase = group_phase;
st->matcher_cache[empty_idx].candidate_score = base_score;
@ -261,7 +260,6 @@ void group_received(struct dsss_demod_state *st) {
/* If the weakest decoding in cache is weaker than a new decoding starting here, replace it */
} else if (min_score < base_score && min_idx >= 0) {
DEBUG_PRINT("Writing to %zd", min_idx);
assert(0 <= min_idx && min_idx < DSSS_MATCHER_CACHE_SIZE);
st->matcher_cache[min_idx].last_phase = group_phase;
st->matcher_cache[min_idx].candidate_score = base_score;

View file

@ -63,7 +63,7 @@ struct dsss_demod_state {
};
extern void handle_dsss_received(uint8_t data[TRANSMISSION_SYMBOLS]);
extern void handle_dsss_received(uint8_t data[static TRANSMISSION_SYMBOLS]);
void dsss_demod_init(struct dsss_demod_state *st);
void dsss_demod_step(struct dsss_demod_state *st, float new_value, uint64_t ts);

View file

@ -0,0 +1,46 @@
#include <stdint.h>
#include <math.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include "crypto.h"
void oob_trigger_activated(enum trigger_domain domain, int serial) {
printf("oob_trigger_activated(%d, %d)\n", domain, serial);
fflush(stdout);
}
void print_usage() {
fprintf(stderr, "Usage: crypto_test [auth_key_hex]\n");
}
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Error: Invalid arguments.\n");
print_usage();
return 1;
}
uint8_t auth_key[16];
for (size_t i=0; argv[1][i+0] != '\0' && argv[1][i+1] != '\0'; i+= 2) {
char buf[3] = { argv[1][i+0], argv[1][i+1], 0};
char *endptr;
auth_key[i/2] = strtoul(buf, &endptr, 16);
if (!endptr || *endptr != '\0') {
fprintf(stderr, "Invalid authkey\n");
return 1;
}
}
printf("rc=%d\n", oob_message_received(auth_key));
return 0;
}

View file

@ -12,7 +12,7 @@
#include "dsss_demod.h"
void handle_dsss_received(uint8_t data[TRANSMISSION_SYMBOLS]) {
void handle_dsss_received(uint8_t data[static TRANSMISSION_SYMBOLS]) {
printf("data sequence received: [ ");
for (size_t i=0; i<TRANSMISSION_SYMBOLS; i++) {
printf("%+3d", ((data[i]&1) ? 1 : -1) * (data[i]>>1));

View file

@ -0,0 +1,143 @@
#!/usr/bin/env python3
import os
import sys
import textwrap
import uuid
import hashlib
import binascii
import sqlite3
import time
import nacl.signing
import nacl.encoding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
PRESIG_VERSION = '000.001'
def format_hex(data, indent=4, wrap=True):
indent = ' '*indent
par = ', '.join(f'0x{b:02x}' for b in data)
par = textwrap.fill(par, width=120,
initial_indent=indent, subsequent_indent=indent,
replace_whitespace=False, drop_whitespace=False)
if wrap:
return f'{{\n{par}\n}}'
return par
def domain_string(domain_name, value, serial):
return f'smart reset domain string v{PRESIG_VERSION}: domain:{domain_name}={value}@{serial}'
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('keyfile', help='Key file to use')
parser.add_argument('presig_db', nargs='?', help='sqlite3 dbfile for generated presig authorization keys')
parser.add_argument('-g', '--generate', action='store_true', help='Generate signing keypair')
parser.add_argument('-v', '--vendor', type=str, default='Darthenschmidt Cyberei und Verschleierungstechnik GmbH', help='Vendor name for vendor domain')
parser.add_argument('-s', '--series', type=str, default='Frobnicator v0.23.7', help='Series identifier for series domain')
parser.add_argument('-r', '--region', type=str, default='Neuland', help='Region name for region domain')
parser.add_argument('-c', '--country', type=str, default='Germany', help='Country name for country domain')
parser.add_argument('-p', '--start-serial', type=int, default=0, help='First presig serial number to use')
parser.add_argument('-n', '--presig-count', type=int, default=3, help='Number of presigs to generate')
parser.add_argument('-i', '--iv', type=str, default='safety reset oob presig iv', help='IV for presig generation')
args = parser.parse_args()
if args.generate:
if os.path.exists(args.keyfile):
print("Error: keyfile already exists. We won't overwrite it. Instead please remove it manually.",
file=sys.stderr)
sys.exit(1)
signing_key = nacl.signing.SigningKey.generate()
with open(args.keyfile, 'wb') as f:
f.write(signing_key.encode(encoder=nacl.encoding.Base64Encoder))
f.write(b'\n')
sys.exit(0)
with open(args.keyfile, 'r') as f:
signing_key = nacl.signing.SigningKey(f.read().strip(), encoder=nacl.encoding.Base64Encoder)
pubkey_bytes = signing_key.verify_key.encode(encoder=nacl.encoding.RawEncoder)
pubkey_hash = hashlib.sha512(pubkey_bytes).digest()[:16]
if not args.presig_db:
print('The presig_db parameter is required.', file=sys.stderr)
sys.exit(1)
db = sqlite3.connect(args.presig_db)
db.execute('CREATE TABLE IF NOT EXISTS presig_authkey (timestamp, pubkey_hash, bundle_id, presig_ver, domain, value, serial, authkey)')
bundle_id = uuid.uuid4().bytes
print('#include <stdint.h>')
print('#include <assert.h>')
print()
print('#include "crypto.h"')
print()
print(f'/* bundle id {binascii.hexlify(bundle_id).decode()} */')
print(f'uint8_t presig_bundle_id[16] = {format_hex(bundle_id)};')
print(f'int presig_first_serial = {args.start_serial};')
print()
print(f'uint8_t oob_trigger_pubkey[crypto_sign_PUBLICKEYBYTES] = {format_hex(pubkey_bytes)};')
print()
print('uint8_t presig_messages[_TRIGGER_DOMAIN_COUNT][PRESIG_STORE_SIZE][PRESIG_MSG_LEN] = {')
device_domains = {
'all': 'all',
'country': args.country,
'region': args.region,
'vendor': args.vendor,
'series': args.series
}
presigs = { dom: [] for dom in device_domains }
for dom, val in device_domains.items():
print(' {')
for i in range(args.presig_count):
serial = args.start_serial + i
ds = domain_string(dom, val, serial)
ds_hash = hashlib.sha512(ds.encode()).digest()[:16]
presigs[dom].append((ds_hash, val, serial))
print(f' {{ /* "{ds}" */')
print(format_hex(ds_hash, indent=8, wrap=False))
print(f' }},')
print(' },')
print('};')
print()
presig_iv = hashlib.sha512(args.iv.encode()).digest()[:16]
print(f'uint8_t oob_presig_iv[16] = {{ /* sha512("{args.iv}")[:16] */')
print(format_hex(presig_iv, wrap=False))
print(f'}};')
print()
print('uint8_t presig_store[_TRIGGER_DOMAIN_COUNT][PRESIG_STORE_SIZE][crypto_sign_BYTES] = {')
for dom, hashes in presigs.items():
print(f' {{ /* domain {dom} */')
for ds_hash, val, serial in hashes:
authkey = os.urandom(16)
cipher = Cipher(algorithms.AES(authkey), modes.CTR(presig_iv), backend=default_backend())
enc = cipher.encryptor()
ciphertext = enc.update(ds_hash)
assert len(enc.finalize()) == 0
with db:
db.execute('INSERT INTO presig_authkey VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
(int(time.time()*1000), pubkey_hash, binascii.hexlify(bundle_id).decode(), PRESIG_VERSION, dom,
print(format_hex(ciphertext, indent=8, wrap=False))
print(f' }},')
print(f' }},')
print(f'}};')
print()
print('static inline void __hack_asserts_only(void) {')
print(f' static_assert(_TRIGGER_DOMAIN_COUNT == {len(presigs)});')
print(f' static_assert(PRESIG_STORE_SIZE == {args.presig_count});')
print('}')
print()

View file

@ -122,7 +122,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.1"
"version": "3.8.2"
}
},
"nbformat": 4,