Skip to content

Commit

Permalink
First release on GitHub
Browse files Browse the repository at this point in the history
- Add build option EMPTY_ATTESTATION_CERT to produce firmware without
  attestation certificate (needs to be initialized later)
- Add vendor command U2F_ATTESTATION_CERT (0x40) to initialize device
  with given attestation certificate
- Assign unique VID:PID
  • Loading branch information
gl-sergei committed Jan 20, 2019
1 parent 928af36 commit 137ec3e
Show file tree
Hide file tree
Showing 13 changed files with 401 additions and 111 deletions.
50 changes: 31 additions & 19 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,46 @@ language: c
sudo: required
dist: trusty

env:
- TARGET=TOMU
- TARGET=MAPLE_MINI
- TARGET=BLUE_PILL
- TARGET=BLACK_PILL
- TARGET=ST_DONGLE
- ENFORCE_DEBUG_LOCK=1 TARGET=TOMU
- ENFORCE_DEBUG_LOCK=1 TARGET=MAPLE_MINI
- ENFORCE_DEBUG_LOCK=1 TARGET=BLUE_PILL
- ENFORCE_DEBUG_LOCK=1 TARGET=BLACK_PILL
- ENFORCE_DEBUG_LOCK=1 TARGET=ST_DONGLE

addons:
apt:
packages:
- python-pip
- openssl

install:
- pip install asn1crypto --user
- pip install -r requirements.txt --user
- sudo apt-add-repository -y ppa:team-gcc-arm-embedded/ppa
- sudo apt-get update
- sudo apt-get install -yy gcc-arm-embedded

script:
- 'cd src'
- 'make TARGET=${TARGET} ENFORCE_DEBUG_LOCK=${ENFORCE_DEBUG_LOCK}'
- 'openssl ecparam -name prime256v1 -genkey -noout -outform der -out key.der'
- './inject_key.py --key key.der --ctr 1001'
- 'make clean distclean certclean'
- 'cd ..'
- |
set -e
mkdir artefacts
cd src
for ENFORCE_DEBUG_LOCK in 1 0 ; do
for EMPTY_ATTESTATION_CERT in 1 0 ; do
for TARGET in TOMU MAPLE_MINI BLUE_PILL BLACK_PILL ST_DONGLE ; do
make TARGET=${TARGET} EMPTY_ATTESTATION_CERT=${EMPTY_ATTESTATION_CERT} ENFORCE_DEBUG_LOCK=1 -j4
if [ "${EMPTY_ATTESTATION_CERT}" = 1 ] && [ "${ENFORCE_DEBUG_LOCK}" = 1 ] ; then
cp build/u2f.bin ../artefacts/u2f-${TARGET}.bin
fi
openssl ecparam -name prime256v1 -genkey -noout -outform der -out key.der
./inject_key.py --key key.der --ctr 1001
make clean distclean certclean
done
done
done
cd ..
deploy:
provider: releases
api_key:
secure: L44PBDcHxpeG/HN2rIkAycRfRV0uiuyHIAxk/VWGyg7K/COMDl4Nhwjg2a+G/vMtH3CIgih+dxlG6auVI1kFJNgpQOsuhn8obl+QrF1+Vw+8zHIHVPCVook30Ar5ovYjV9EXHpA6lHucFOrNFrJ4BeT6NdDYjWVq+c7+9jgjtk3Hdoz1OfYbVLhbNRVwv/SqC8tyo0EjOqG0Fe13XXYDjUzNsFYZOP9Xu0Y94O64i8akrfNzwC4xZHFLpH5MHoF6nd/LJXxixsJ2C2OYJAcjI6ju8qwjGPWvIbIV52xyrXyM/nswGo/B7s7w/B4o2cqciJEll8fRqtJKeE3AWPc5L3hWz1ii6fb+5dAbgW+triwpNjLnX5PzJSsH1no6+3eXW5GmQnvS3cf/ht22JUvy5XnSnn3AHgJTuvmKXJfvE48ZWDaM4US+4z+dLLNRtxEEsTDHRNa96inkLVI60ZHIwACw90z0avnr0rvUdEYsx56oksCN1HcRr0tK3x2iBpqzBPGOYcTBENMnhMevRie88hjidj/ePRem4yxHHswZKmEwu5kL65atjTWN6ZrQQOqO9BH0NiFXfk9gOoWwRLhF090qoMI8n8u5R5iXz8wAMbQzH7/rntoCoNUy9bxLcclmzrk9lsxlhDp46xYfOHH/R9BRcOFKAEymt6B1k41chkE=
file_glob: true
file: artefacts/*
skip_cleanup: true
draft: true
on:
repo: gl-sergei/u2f-token
tags: true
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
asn1crypto
easyhid
pyu2f
8 changes: 7 additions & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ ifeq ($(ENFORCE_DEBUG_LOCK),1)
DEFS += -DENFORCE_DEBUG_LOCK
endif

ifeq ($(EMPTY_ATTESTATION_CERT),1)
GENCERT_CMD = cp empty-attestation-cert.c cert/certificates.c
else
GENCERT_CMD = cd cert && ./gen.sh && cd ..
endif

CSRC = u2f.c usb-hid.c dbug.c u2f-hid.c u2f-apdu.c \
sha256.c neug.c random.c ec_p256r1.c bn.c jpc_p256r1.c \
call-ec_p256r1.c modp256r1.c mod.c hmac.c platform.c $(DRIVERS)
Expand All @@ -76,7 +82,7 @@ board.h:
ln -s $(BOARD) board.h

cert/certificates.c:
cd cert && ./gen.sh && cd ..
$(GENCERT_CMD)

sys.c: board.h
u2f.c: board.h
Expand Down
139 changes: 139 additions & 0 deletions src/cert/certtool
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# certtool - Initialize U2F-token with attestation certificate
#
# Copyright (C) 2019 Sergei Glushchenko
# Author: Sergei Glushchenko <[email protected]>
#
# This file is a part of U2F firmware for STM32 and EFM32HG
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# As additional permission under GNU GPL version 3 section 7, you may
# distribute non-source form of the Program without the copy of the
# GNU GPL normally required by section 4, provided you inform the
# recipients of GNU GPL by a written offer.

import easyhid
import struct
import secrets
import argparse

from asn1crypto.keys import ECPrivateKey

FIDO_USAGE_PAGE = 0xF1D0
U2F_USAGE = 1
HID_RPT_SIZE = 64

CMD_INIT = 0x06
CMD_MSG = 0x03
CMD_ERROR = 0x3f

BROADCAST_CID = 0xffffffff

def sendCommand(dev, channel, cmd, data):
msg = struct.pack('>IBH', channel, cmd | 0x80, len(data))
msg += data[:HID_RPT_SIZE - 7]
data = data[HID_RPT_SIZE - 7:]
dev.write(msg)
seq = 0
while data:
msg = struct.pack('>IB', channel, seq)
msg += data[:HID_RPT_SIZE - 5]
data = data[HID_RPT_SIZE - 5:]
dev.write(msg)
seq += 1

def recvResponse(dev, channel):
data = dev.read()
ret_channel, cmd, len = struct.unpack(">IBH", data[:7])
if ret_channel != channel:
raise Exception("Wrong channel")
if cmd == CMD_ERROR | 0x80:
raise Exception("HID Error: {:d}".format(data[6]))
return data[7:7+len]

def init(dev):
dev.open()
nonce = secrets.token_bytes(8)
sendCommand(dev, BROADCAST_CID, CMD_INIT, nonce)
data = recvResponse(dev, BROADCAST_CID)
if data[:8] != nonce:
raise Exception("Invalid nonce")
channel, = struct.unpack(">I", data[8:8+4])
return channel

def put_cert(dev, cert, key):
channel = init(dev)
cla, ins, p1, p2 = 0, 0x40, 0, 0
Lc = len(cert) + len(key)
data = struct.pack('>BBBBBH', cla, ins, p1, p2, 0, Lc)
data += key + cert
sendCommand(dev, channel, CMD_MSG, data)
data = recvResponse(dev, channel)
if data != b'\x90\x00':
raise Exception("APDU Error: " + data.hex())

def load_key(pk_der):
pk = ECPrivateKey.load(pk_der)
pk_hex = format(pk['private_key'].native, '064x')
return bytes.fromhex(pk_hex)

def command_list(args):
e = easyhid.Enumeration()
devices = [ dev for dev in e.find() if dev.usage_page == FIDO_USAGE_PAGE ]

for dev in devices:
print(dev.description())

def command_init(args):
e = easyhid.Enumeration()
devices = [ dev for dev in e.find() if dev.usage_page == FIDO_USAGE_PAGE ]

if len(devices) < 1:
raise Exception("No U2F devices found")

with open(args.certificate, "rb") as f:
cert = f.read()

with open(args.key, "rb") as f:
key = load_key(f.read())

for dev in devices:
print("Trying to initialize device {}".format(dev.description()))
try:
put_cert(dev, cert, key)
print('Success')
except Exception as e:
print(e)

def main():
parser = argparse.ArgumentParser(
description="Initialize U2F-token with attestation certificate")
subparsers = parser.add_subparsers(help='available commands')
parser_list = subparsers.add_parser('list', help='list U2F devices')
parser_list.set_defaults(func=command_list)
parser_init = subparsers.add_parser('init', help='init U2F devices')
parser_init.add_argument("--certificate", default="attestation.der",
help="attestation certificate in DER format")
parser_init.add_argument("--key", default="attestation_key.der",
help="attestation certificate key in DER format")
parser_init.set_defaults(func=command_init)
args = parser.parse_args()

args.func(args)

if __name__ == "__main__":
main()
36 changes: 36 additions & 0 deletions src/cert/dump-der.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,40 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# dump-der.py - convert DER encoded certificate and EC key into C header
#
# Copyright (C) 2017-2019 Sergei Glushchenko
# Author: Sergei Glushchenko <[email protected]>
#
# This file is a part of U2F firmware for STM32 and EFM32HG
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# As additional permission under GNU GPL version 3 section 7, you may
# distribute non-source form of the Program without the copy of the
# GNU GPL normally required by section 4, provided you inform the
# recipients of GNU GPL by a written offer.

from __future__ import print_function
from asn1crypto.keys import ECPrivateKey

attestation_cert_def = '''const struct attestation_cert __attribute__ ((section(".attestation.cert"))) attestation_cert = {
.der_len = ATTESTATION_DER_LEN,
.der = attestation_der,
.key = attestation_key
};'''

def pk_to_c_array(name, pk_der):
# parse der format
pk = ECPrivateKey.load(pk_der)
Expand Down Expand Up @@ -34,3 +68,5 @@ def cert_to_c_array(name, der):

with open("attestation_key.der", "rb") as f:
print(pk_to_c_array("attestation_key", f.read()))

print(attestation_cert_def)
7 changes: 5 additions & 2 deletions src/efm32hg.ld
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ __process2_stack_size__ = 0x0100; /* blk */
__process3_stack_size__ = 0x0200; /* usb-hid */
__process4_stack_size__ = 0x0800; /* u2f-hid */
__process5_stack_size__ = 0x0200; /* rng */
__process6_stack_size__ = 0x0200; /* pbt / csn */
__process6_stack_size__ = 0x0100; /* pbt / csn */

MEMORY
{
flash : org = 0x00004000, len = 46k
ram : org = 0x20000000, len = 8k
flash1 : org = 0x00004000+0xb800, len = 2k
flash1 : org = 0x00004000+0xb400, len = 3k
}

__ram_start__ = ORIGIN(ram);
Expand Down Expand Up @@ -157,6 +157,9 @@ SECTIONS

.flash_storage :
{
. = ALIGN (1024);
_attestation_cert_base = .;
*(.attestation.cert);
. = ALIGN (1024);
_device_key_base = .;
*(.device.key);
Expand Down
6 changes: 6 additions & 0 deletions src/empty-attestation-cert.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
struct attestation_cert __attribute__ ((section(".attestation.cert")))
attestation_cert = {
.der_len = (uint32_t) -1,
.der = NULL,
.key = NULL
};
2 changes: 1 addition & 1 deletion src/platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ toboot_config = {
.config = TOBOOT_CONFIG_FLAG_AUTORUN,
.lock_entry = 0,
.erase_mask_lo = 0x00000000,
.erase_mask_hi = 0xc0000000,
.erase_mask_hi = 0xe0000000,
.reserved_hash = 0
};

Expand Down
5 changes: 4 additions & 1 deletion src/stm32f103.ld
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ MEMORY
{
flash0 : org = 0x08000000, len = 4k
flash : org = 0x08000000+0x1000, len = 58k
flash1 : org = 0x08000000+0xf800, len = 2k
flash1 : org = 0x08000000+0xf400, len = 3k
ram : org = 0x20000000, len = 20k
}

Expand Down Expand Up @@ -159,6 +159,9 @@ SECTIONS

.flash_storage :
{
. = ALIGN (1024);
_attestation_cert_base = .;
*(.attestation.cert);
. = ALIGN (1024);
_device_key_base = .;
*(.device.key);
Expand Down
Loading

0 comments on commit 137ec3e

Please sign in to comment.