-
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 4 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 |
---|---|---|
@@ -0,0 +1,73 @@ | ||
#!/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 | ||
# | ||
# 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 | ||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument("--bin", default="build/u2f.bin", | ||
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=0xB800, 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. Maybe we could guess by looking at the .bin file whether it is Tomu binary of stm32f1x? Or at least we could have two preset values one for each of them. Also I don't remember if playing with make flags can change the offset. 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. All builds share same .ld file, so I think offsets are really constant. Maybe this argument can be removed in favour of a constant. In a pinch, a user can edit the .py to change the value? 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'm wrong about all builds sharing same .ld. There is also efm32hg.ld |
||
args = parser.parse_args() | ||
|
||
fname, fext = os.path.splitext(args.bin) | ||
assert fext == ".bin" | ||
|
||
print("Target binary file:", args.bin) | ||
print("Key injection offset 0x%0X" % args.offset) | ||
|
||
# load and parse private key | ||
if not args.key: | ||
print("Key not modified") | ||
else: | ||
print("Injecting key from", args.key) | ||
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(args.offset) | ||
f.write(key_blob) | ||
|
||
if not args.ctr: | ||
print("Counter not modified") | ||
else: | ||
print("Injecting counter", args.ctr) | ||
# fill authentication counter | ||
ctr_blob = struct.pack("<I", args.ctr) * 256 | ||
|
||
with open(args.bin, 'r+b') as f: | ||
f.seek(args.offset + 1024) | ||
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. if offset is "Offset within file to patch", why do we need to add 1024 here? 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. args.offset is offset to start of patching when both key and ctr are patched. Yet I want to be able to patch either independently. |
||
f.write(ctr_blob) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# 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... |
||
set -x | ||
curl 'https://demo.yubico.com/wsapi/u2f/enroll?username=tomu&password=test' > 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=tomu&password=test&data=$(cat 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. can we generate random user/password here to avoid possible conflicts? 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. Reasonable request. 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've done this |
||
curl 'https://demo.yubico.com/wsapi/u2f/sign?username=tomu&password=test' > 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=tomu&password=test&data=$(cat signature.json)" > response.json | ||
cat response.json | ||
echo "" | ||
|
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.
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 comment
The 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.