Skip to content

Commit 137ec3e

Browse files
committed
First release on GitHub
- 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
1 parent 928af36 commit 137ec3e

File tree

13 files changed

+401
-111
lines changed

13 files changed

+401
-111
lines changed

.travis.yml

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,46 @@ language: c
22
sudo: required
33
dist: trusty
44

5-
env:
6-
- TARGET=TOMU
7-
- TARGET=MAPLE_MINI
8-
- TARGET=BLUE_PILL
9-
- TARGET=BLACK_PILL
10-
- TARGET=ST_DONGLE
11-
- ENFORCE_DEBUG_LOCK=1 TARGET=TOMU
12-
- ENFORCE_DEBUG_LOCK=1 TARGET=MAPLE_MINI
13-
- ENFORCE_DEBUG_LOCK=1 TARGET=BLUE_PILL
14-
- ENFORCE_DEBUG_LOCK=1 TARGET=BLACK_PILL
15-
- ENFORCE_DEBUG_LOCK=1 TARGET=ST_DONGLE
16-
175
addons:
186
apt:
197
packages:
208
- python-pip
219
- openssl
2210

2311
install:
24-
- pip install asn1crypto --user
12+
- pip install -r requirements.txt --user
2513
- sudo apt-add-repository -y ppa:team-gcc-arm-embedded/ppa
2614
- sudo apt-get update
2715
- sudo apt-get install -yy gcc-arm-embedded
2816

2917
script:
30-
- 'cd src'
31-
- 'make TARGET=${TARGET} ENFORCE_DEBUG_LOCK=${ENFORCE_DEBUG_LOCK}'
32-
- 'openssl ecparam -name prime256v1 -genkey -noout -outform der -out key.der'
33-
- './inject_key.py --key key.der --ctr 1001'
34-
- 'make clean distclean certclean'
35-
- 'cd ..'
18+
- |
19+
set -e
20+
mkdir artefacts
21+
cd src
22+
for ENFORCE_DEBUG_LOCK in 1 0 ; do
23+
for EMPTY_ATTESTATION_CERT in 1 0 ; do
24+
for TARGET in TOMU MAPLE_MINI BLUE_PILL BLACK_PILL ST_DONGLE ; do
25+
make TARGET=${TARGET} EMPTY_ATTESTATION_CERT=${EMPTY_ATTESTATION_CERT} ENFORCE_DEBUG_LOCK=1 -j4
26+
if [ "${EMPTY_ATTESTATION_CERT}" = 1 ] && [ "${ENFORCE_DEBUG_LOCK}" = 1 ] ; then
27+
cp build/u2f.bin ../artefacts/u2f-${TARGET}.bin
28+
fi
29+
openssl ecparam -name prime256v1 -genkey -noout -outform der -out key.der
30+
./inject_key.py --key key.der --ctr 1001
31+
make clean distclean certclean
32+
done
33+
done
34+
done
35+
cd ..
36+
37+
deploy:
38+
provider: releases
39+
api_key:
40+
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=
41+
file_glob: true
42+
file: artefacts/*
43+
skip_cleanup: true
44+
draft: true
45+
on:
46+
repo: gl-sergei/u2f-token
47+
tags: true

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
asn1crypto
2+
easyhid
3+
pyu2f

src/Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ ifeq ($(ENFORCE_DEBUG_LOCK),1)
5050
DEFS += -DENFORCE_DEBUG_LOCK
5151
endif
5252

53+
ifeq ($(EMPTY_ATTESTATION_CERT),1)
54+
GENCERT_CMD = cp empty-attestation-cert.c cert/certificates.c
55+
else
56+
GENCERT_CMD = cd cert && ./gen.sh && cd ..
57+
endif
58+
5359
CSRC = u2f.c usb-hid.c dbug.c u2f-hid.c u2f-apdu.c \
5460
sha256.c neug.c random.c ec_p256r1.c bn.c jpc_p256r1.c \
5561
call-ec_p256r1.c modp256r1.c mod.c hmac.c platform.c $(DRIVERS)
@@ -76,7 +82,7 @@ board.h:
7682
ln -s $(BOARD) board.h
7783

7884
cert/certificates.c:
79-
cd cert && ./gen.sh && cd ..
85+
$(GENCERT_CMD)
8086

8187
sys.c: board.h
8288
u2f.c: board.h

src/cert/certtool

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# certtool - Initialize U2F-token with attestation certificate
5+
#
6+
# Copyright (C) 2019 Sergei Glushchenko
7+
# Author: Sergei Glushchenko <[email protected]>
8+
#
9+
# This file is a part of U2F firmware for STM32 and EFM32HG
10+
#
11+
# This program is free software: you can redistribute it and/or modify it
12+
# under the terms of the GNU General Public License as published by
13+
# the Free Software Foundation, either version 3 of the License, or
14+
# (at your option) any later version.
15+
#
16+
# This program is distributed in the hope that it will be useful, but
17+
# WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19+
# General Public License for more details.
20+
#
21+
# You should have received a copy of the GNU General Public License
22+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
23+
#
24+
# As additional permission under GNU GPL version 3 section 7, you may
25+
# distribute non-source form of the Program without the copy of the
26+
# GNU GPL normally required by section 4, provided you inform the
27+
# recipients of GNU GPL by a written offer.
28+
29+
import easyhid
30+
import struct
31+
import secrets
32+
import argparse
33+
34+
from asn1crypto.keys import ECPrivateKey
35+
36+
FIDO_USAGE_PAGE = 0xF1D0
37+
U2F_USAGE = 1
38+
HID_RPT_SIZE = 64
39+
40+
CMD_INIT = 0x06
41+
CMD_MSG = 0x03
42+
CMD_ERROR = 0x3f
43+
44+
BROADCAST_CID = 0xffffffff
45+
46+
def sendCommand(dev, channel, cmd, data):
47+
msg = struct.pack('>IBH', channel, cmd | 0x80, len(data))
48+
msg += data[:HID_RPT_SIZE - 7]
49+
data = data[HID_RPT_SIZE - 7:]
50+
dev.write(msg)
51+
seq = 0
52+
while data:
53+
msg = struct.pack('>IB', channel, seq)
54+
msg += data[:HID_RPT_SIZE - 5]
55+
data = data[HID_RPT_SIZE - 5:]
56+
dev.write(msg)
57+
seq += 1
58+
59+
def recvResponse(dev, channel):
60+
data = dev.read()
61+
ret_channel, cmd, len = struct.unpack(">IBH", data[:7])
62+
if ret_channel != channel:
63+
raise Exception("Wrong channel")
64+
if cmd == CMD_ERROR | 0x80:
65+
raise Exception("HID Error: {:d}".format(data[6]))
66+
return data[7:7+len]
67+
68+
def init(dev):
69+
dev.open()
70+
nonce = secrets.token_bytes(8)
71+
sendCommand(dev, BROADCAST_CID, CMD_INIT, nonce)
72+
data = recvResponse(dev, BROADCAST_CID)
73+
if data[:8] != nonce:
74+
raise Exception("Invalid nonce")
75+
channel, = struct.unpack(">I", data[8:8+4])
76+
return channel
77+
78+
def put_cert(dev, cert, key):
79+
channel = init(dev)
80+
cla, ins, p1, p2 = 0, 0x40, 0, 0
81+
Lc = len(cert) + len(key)
82+
data = struct.pack('>BBBBBH', cla, ins, p1, p2, 0, Lc)
83+
data += key + cert
84+
sendCommand(dev, channel, CMD_MSG, data)
85+
data = recvResponse(dev, channel)
86+
if data != b'\x90\x00':
87+
raise Exception("APDU Error: " + data.hex())
88+
89+
def load_key(pk_der):
90+
pk = ECPrivateKey.load(pk_der)
91+
pk_hex = format(pk['private_key'].native, '064x')
92+
return bytes.fromhex(pk_hex)
93+
94+
def command_list(args):
95+
e = easyhid.Enumeration()
96+
devices = [ dev for dev in e.find() if dev.usage_page == FIDO_USAGE_PAGE ]
97+
98+
for dev in devices:
99+
print(dev.description())
100+
101+
def command_init(args):
102+
e = easyhid.Enumeration()
103+
devices = [ dev for dev in e.find() if dev.usage_page == FIDO_USAGE_PAGE ]
104+
105+
if len(devices) < 1:
106+
raise Exception("No U2F devices found")
107+
108+
with open(args.certificate, "rb") as f:
109+
cert = f.read()
110+
111+
with open(args.key, "rb") as f:
112+
key = load_key(f.read())
113+
114+
for dev in devices:
115+
print("Trying to initialize device {}".format(dev.description()))
116+
try:
117+
put_cert(dev, cert, key)
118+
print('Success')
119+
except Exception as e:
120+
print(e)
121+
122+
def main():
123+
parser = argparse.ArgumentParser(
124+
description="Initialize U2F-token with attestation certificate")
125+
subparsers = parser.add_subparsers(help='available commands')
126+
parser_list = subparsers.add_parser('list', help='list U2F devices')
127+
parser_list.set_defaults(func=command_list)
128+
parser_init = subparsers.add_parser('init', help='init U2F devices')
129+
parser_init.add_argument("--certificate", default="attestation.der",
130+
help="attestation certificate in DER format")
131+
parser_init.add_argument("--key", default="attestation_key.der",
132+
help="attestation certificate key in DER format")
133+
parser_init.set_defaults(func=command_init)
134+
args = parser.parse_args()
135+
136+
args.func(args)
137+
138+
if __name__ == "__main__":
139+
main()

src/cert/dump-der.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,40 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# dump-der.py - convert DER encoded certificate and EC key into C header
5+
#
6+
# Copyright (C) 2017-2019 Sergei Glushchenko
7+
# Author: Sergei Glushchenko <[email protected]>
8+
#
9+
# This file is a part of U2F firmware for STM32 and EFM32HG
10+
#
11+
# This program is free software: you can redistribute it and/or modify it
12+
# under the terms of the GNU General Public License as published by
13+
# the Free Software Foundation, either version 3 of the License, or
14+
# (at your option) any later version.
15+
#
16+
# This program is distributed in the hope that it will be useful, but
17+
# WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19+
# General Public License for more details.
20+
#
21+
# You should have received a copy of the GNU General Public License
22+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
23+
#
24+
# As additional permission under GNU GPL version 3 section 7, you may
25+
# distribute non-source form of the Program without the copy of the
26+
# GNU GPL normally required by section 4, provided you inform the
27+
# recipients of GNU GPL by a written offer.
28+
129
from __future__ import print_function
230
from asn1crypto.keys import ECPrivateKey
331

32+
attestation_cert_def = '''const struct attestation_cert __attribute__ ((section(".attestation.cert"))) attestation_cert = {
33+
.der_len = ATTESTATION_DER_LEN,
34+
.der = attestation_der,
35+
.key = attestation_key
36+
};'''
37+
438
def pk_to_c_array(name, pk_der):
539
# parse der format
640
pk = ECPrivateKey.load(pk_der)
@@ -34,3 +68,5 @@ def cert_to_c_array(name, der):
3468

3569
with open("attestation_key.der", "rb") as f:
3670
print(pk_to_c_array("attestation_key", f.read()))
71+
72+
print(attestation_cert_def)

src/efm32hg.ld

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ __process2_stack_size__ = 0x0100; /* blk */
88
__process3_stack_size__ = 0x0200; /* usb-hid */
99
__process4_stack_size__ = 0x0800; /* u2f-hid */
1010
__process5_stack_size__ = 0x0200; /* rng */
11-
__process6_stack_size__ = 0x0200; /* pbt / csn */
11+
__process6_stack_size__ = 0x0100; /* pbt / csn */
1212

1313
MEMORY
1414
{
1515
flash : org = 0x00004000, len = 46k
1616
ram : org = 0x20000000, len = 8k
17-
flash1 : org = 0x00004000+0xb800, len = 2k
17+
flash1 : org = 0x00004000+0xb400, len = 3k
1818
}
1919

2020
__ram_start__ = ORIGIN(ram);
@@ -157,6 +157,9 @@ SECTIONS
157157

158158
.flash_storage :
159159
{
160+
. = ALIGN (1024);
161+
_attestation_cert_base = .;
162+
*(.attestation.cert);
160163
. = ALIGN (1024);
161164
_device_key_base = .;
162165
*(.device.key);

src/empty-attestation-cert.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
struct attestation_cert __attribute__ ((section(".attestation.cert")))
2+
attestation_cert = {
3+
.der_len = (uint32_t) -1,
4+
.der = NULL,
5+
.key = NULL
6+
};

src/platform.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ toboot_config = {
4848
.config = TOBOOT_CONFIG_FLAG_AUTORUN,
4949
.lock_entry = 0,
5050
.erase_mask_lo = 0x00000000,
51-
.erase_mask_hi = 0xc0000000,
51+
.erase_mask_hi = 0xe0000000,
5252
.reserved_hash = 0
5353
};
5454

src/stm32f103.ld

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ MEMORY
1414
{
1515
flash0 : org = 0x08000000, len = 4k
1616
flash : org = 0x08000000+0x1000, len = 58k
17-
flash1 : org = 0x08000000+0xf800, len = 2k
17+
flash1 : org = 0x08000000+0xf400, len = 3k
1818
ram : org = 0x20000000, len = 20k
1919
}
2020

@@ -159,6 +159,9 @@ SECTIONS
159159

160160
.flash_storage :
161161
{
162+
. = ALIGN (1024);
163+
_attestation_cert_base = .;
164+
*(.attestation.cert);
162165
. = ALIGN (1024);
163166
_device_key_base = .;
164167
*(.device.key);

0 commit comments

Comments
 (0)