-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Script to inject key and/or auth counter directly into binary image #26
base: master
Are you sure you want to change the base?
Changes from 7 commits
c470d93
71621e3
33edbb3
cf01fa1
d0e5af4
895f7b0
d7709b5
43c88fa
6a4b390
53047f4
abb859d
f1c9c4e
d64c74d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ | |
*/.dep | ||
doc/chopstx.info | ||
.vs/ | ||
*.json | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
#!/usr/bin/env python | ||
|
||
# | ||
# Use this script to inject your own private key and authentication counter | ||
# into U2F binary. Might be useful if you want keys to survive firmware updates. | ||
# | ||
# Example: | ||
# | ||
# Generate EC private key with openssl: | ||
# > openssl ecparam -name prime256v1 -genkey -noout -outform der > key.der | ||
# | ||
# Inject generated key into u2f.bin and set auth counter to 100: | ||
# > python3 inject_key_bin.py --key key.der --ctr 100 --bin build/u2f.bin | ||
# | ||
# key will not be modified if --key parameter is not present | ||
# counter will not be modified if --ctr parameter is not present | ||
|
||
from __future__ import print_function | ||
from asn1crypto.keys import ECPrivateKey | ||
import hashlib | ||
import argparse | ||
import sys | ||
import struct | ||
import os | ||
|
||
# TODO: detect correct offset from .bin file. | ||
stm32f103_offset = 0xB800 # See stm32f103.ld | ||
#efm32hg_offset = ???? | ||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument("--bin", default="build/u2f.bin", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we combine inject_key and inject_key_bin into one? Or even replace inject_key with the inject_key_bin... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think once inject_key_bin is proven to work, it can replace inject_key. |
||
help='.bin file to inject keys into. Or "stdin"') | ||
parser.add_argument("--key", help="EC private key in DER format") | ||
parser.add_argument("--ctr", default=0, type=lambda x: int(x,0), help="Value of auth counter") | ||
parser.add_argument("--offset", default=stm32f103_offset, type=lambda x: int(x,0), help="Offset within file to patch") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, for stm32f103 offset can be 0xf400 or 0xf800 depending on build flags (CUSTOM_ATTESTATION_CERT), for efm32hg it can be 0xb400 or 0xb800. But it is more reliable to count backwards. It should always be 2048 bytes from the end of .bin file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pushed new code that uses this calculation by default |
||
args = parser.parse_args() | ||
|
||
fname, fext = os.path.splitext(args.bin) | ||
assert fext == ".bin" | ||
|
||
print("Target binary file:", args.bin) | ||
|
||
# load and parse private key | ||
if not args.key: | ||
print("Key not modified") | ||
else: | ||
key_offset = args.offset | ||
print("Injecting key from %s at 0x%0X", (args.key, key_offset) | ||
if args.key == "stdin": | ||
stdin = sys.stdin.buffer if hasattr(sys.stdin, "buffer") else sys.stdin | ||
der = stdin.read() | ||
else: | ||
with open(args.key, "rb") as f: | ||
der = f.read() | ||
key = ECPrivateKey.load(der) | ||
|
||
# convert key into raw bytes and calculate it's sha256 | ||
key_bytes = bytearray.fromhex(format(key["private_key"].native, '064x')) | ||
key_hash = hashlib.sha256(key_bytes).digest() | ||
# pad key to 1KiB | ||
key_blob = (key_bytes + key_hash).ljust(1024, b"\x00") | ||
assert len(key_blob) == 1024 | ||
|
||
with open(args.bin, 'r+b') as f: | ||
f.seek(key_offset) | ||
f.write(key_blob) | ||
|
||
if not args.ctr: | ||
print("Counter not modified") | ||
else: | ||
ctr_offset = args.offset + 0x400 | ||
print("Injecting counter %d at 0x%0X", (args.ctr, ctr_offset) | ||
# fill authentication counter | ||
ctr_blob = struct.pack("<I", args.ctr) * 256 | ||
|
||
with open(args.bin, 'r+b') as f: | ||
f.seek(ctr_offset) | ||
f.write(ctr_blob) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Basic test | ||
# Requires dfu-utils to be installed. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. libu2f-host instead of dfu-utils? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Package "u2f-host" on debian. Pushed this change |
||
# This runs through the steps described in https://github.com/Yubico/libu2f-host documentation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually I run this test suite for every change: https://github.com/google/u2f-ref-code/tree/master/u2f-tests, it is very complete. But having a simple one is a good thing as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ok I didn't know about that. I just wanted a simple 'is the key working' test that didn't involve interacting with a website... |
||
# If run with -k argument, the json files are retainsd. | ||
eliotb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
set -x | ||
|
||
user="tomu-$(date +%s)" | ||
pass=$(uuidgen) | ||
|
||
curl "https://demo.yubico.com/wsapi/u2f/enroll?username=$user&password=$pass" > regchallenge.json | ||
cat regchallenge.json | ||
echo "Touch key to confirm registration" | ||
u2f-host -a register -o 'https://demo.yubico.com' < regchallenge.json > regdata.json | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shall we add .json files to .gitignore and clean them up at the end of this script? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now by default they are cleaned up at end, unless you add "-k" as first argument. Rather simplistic, but I think it is ok for a basic test script. |
||
#cat regdata.json | ||
curl https://demo.yubico.com/wsapi/u2f/bind -d "username=$user&password=$pass&data=$(cat regdata.json)" | ||
curl "https://demo.yubico.com/wsapi/u2f/sign?username=$user&password=$pass" > challenge.json | ||
cat challenge.json | ||
echo "Touch key to confirm authentication" | ||
u2f-host -a authenticate -o 'https://demo.yubico.com' < challenge.json > signature.json | ||
#cat signature.json | ||
curl https://demo.yubico.com/wsapi/u2f/verify -d "username=$user&password=$pass&data=$(cat signature.json)" > response.json | ||
cat response.json | ||
echo "" | ||
|
||
if [ "$1" != "-k" ] | ||
then | ||
rm *.json | ||
fi | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is better to be more specific about which files to ignore. What about
test/*.json
?