From c470d937afb027bb47df174836be94b9664e3cd7 Mon Sep 17 00:00:00 2001 From: Eliot Blennerhassett Date: Fri, 22 Mar 2019 14:11:16 +1300 Subject: [PATCH 01/13] Script to inject key and/or auth counter directly into binary image Signed-off-by: Eliot Blennerhassett --- src/inject_key_bin.py | 73 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100755 src/inject_key_bin.py diff --git a/src/inject_key_bin.py b/src/inject_key_bin.py new file mode 100755 index 0000000..7ea46a8 --- /dev/null +++ b/src/inject_key_bin.py @@ -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=int, help="Value of auth counter") +parser.add_argument("--offset", default=0xB400, type=int, help="Offset within file to patch") +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(" Date: Fri, 22 Mar 2019 14:18:46 +1300 Subject: [PATCH 02/13] typo in param help string update Signed-off-by: Eliot Blennerhassett --- src/inject_key_bin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inject_key_bin.py b/src/inject_key_bin.py index 7ea46a8..08ef0c7 100755 --- a/src/inject_key_bin.py +++ b/src/inject_key_bin.py @@ -25,7 +25,7 @@ parser = argparse.ArgumentParser() parser.add_argument("--bin", default="build/u2f.bin", - help='.bin file to inject keys into. Or "stdin"' + 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=int, help="Value of auth counter") parser.add_argument("--offset", default=0xB400, type=int, help="Offset within file to patch") From 33edbb39c5653b08ab7d2325706ad7250da82730 Mon Sep 17 00:00:00 2001 From: Eliot Blennerhassett Date: Fri, 22 Mar 2019 22:06:15 +1300 Subject: [PATCH 03/13] Allow hex numeric arguments. Change default offset. --- src/inject_key_bin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inject_key_bin.py b/src/inject_key_bin.py index 08ef0c7..397c263 100755 --- a/src/inject_key_bin.py +++ b/src/inject_key_bin.py @@ -27,8 +27,8 @@ 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=int, help="Value of auth counter") -parser.add_argument("--offset", default=0xB400, type=int, help="Offset within file to patch") +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") args = parser.parse_args() fname, fext = os.path.splitext(args.bin) From cf01fa1aaaf9f5edc7020ae77debc41ff8b1bb90 Mon Sep 17 00:00:00 2001 From: Eliot Blennerhassett Date: Sat, 23 Mar 2019 09:55:57 +1300 Subject: [PATCH 04/13] Test script using u2f-host and demo.yubico.com to test key Signed-off-by: Eliot Blennerhassett --- test/yubico_test.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100755 test/yubico_test.sh diff --git a/test/yubico_test.sh b/test/yubico_test.sh new file mode 100755 index 0000000..33c5526 --- /dev/null +++ b/test/yubico_test.sh @@ -0,0 +1,18 @@ +# Requires dfu-utils to be installed. +# This runs through the steps described in https://github.com/Yubico/libu2f-host documentation +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 +#cat regdata.json +curl https://demo.yubico.com/wsapi/u2f/bind -d "username=tomu&password=test&data=$(cat regdata.json)" +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 "" + From d0e5af49405ca956b5147e68af4a0b02db2504f0 Mon Sep 17 00:00:00 2001 From: Eliot Blennerhassett Date: Sun, 24 Mar 2019 15:59:16 +1300 Subject: [PATCH 05/13] Randomish user and password also remove json output unless -k is given Note accounts are removed from demo.yubico.com after 24 hours --- test/yubico_test.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/test/yubico_test.sh b/test/yubico_test.sh index 33c5526..287b6c4 100755 --- a/test/yubico_test.sh +++ b/test/yubico_test.sh @@ -1,18 +1,29 @@ +# Basic test # Requires dfu-utils to be installed. # This runs through the steps described in https://github.com/Yubico/libu2f-host documentation +# If run with -k argument, the json files are retainsd. set -x -curl 'https://demo.yubico.com/wsapi/u2f/enroll?username=tomu&password=test' > regchallenge.json + +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 #cat regdata.json -curl https://demo.yubico.com/wsapi/u2f/bind -d "username=tomu&password=test&data=$(cat regdata.json)" -curl 'https://demo.yubico.com/wsapi/u2f/sign?username=tomu&password=test' > challenge.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=tomu&password=test&data=$(cat signature.json)" > response.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 + From 895f7b0d8486398126302cdd5f896b4ee9045313 Mon Sep 17 00:00:00 2001 From: Eliot Blennerhassett Date: Mon, 25 Mar 2019 11:45:11 +1300 Subject: [PATCH 06/13] Clarify offsets --- src/inject_key_bin.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/inject_key_bin.py b/src/inject_key_bin.py index 397c263..6f45fdf 100755 --- a/src/inject_key_bin.py +++ b/src/inject_key_bin.py @@ -10,7 +10,7 @@ # > 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 +# > 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 @@ -23,25 +23,29 @@ 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", 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") +parser.add_argument("--offset", default=stm32f103_offset, type=lambda x: int(x,0), help="Offset within file to patch") 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) + 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() @@ -58,16 +62,17 @@ assert len(key_blob) == 1024 with open(args.bin, 'r+b') as f: - f.seek(args.offset) + f.seek(key_offset) f.write(key_blob) if not args.ctr: print("Counter not modified") else: - print("Injecting counter", args.ctr) + ctr_offset = args.offset + 0x400 + print("Injecting counter %d at 0x%0X", (args.ctr, ctr_offset) # fill authentication counter ctr_blob = struct.pack(" Date: Mon, 25 Mar 2019 12:58:01 +1300 Subject: [PATCH 07/13] Add .json to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index af08cfa..891c854 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ */.dep doc/chopstx.info .vs/ +*.json From 43c88fa72c164f85122ce34b34e7124efb8aa52d Mon Sep 17 00:00:00 2001 From: Eliot Blennerhassett Date: Mon, 6 May 2019 10:58:58 +1200 Subject: [PATCH 08/13] fix missing parens and format in print Signed-off-by: Eliot Blennerhassett --- src/inject_key_bin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inject_key_bin.py b/src/inject_key_bin.py index 6f45fdf..3130e33 100755 --- a/src/inject_key_bin.py +++ b/src/inject_key_bin.py @@ -45,7 +45,7 @@ print("Key not modified") else: key_offset = args.offset - print("Injecting key from %s at 0x%0X", (args.key, key_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() @@ -69,7 +69,7 @@ print("Counter not modified") else: ctr_offset = args.offset + 0x400 - print("Injecting counter %d at 0x%0X", (args.ctr, ctr_offset) + print("Injecting counter %d at 0x%0X" % (args.ctr, ctr_offset)) # fill authentication counter ctr_blob = struct.pack(" Date: Mon, 6 May 2019 10:59:51 +1200 Subject: [PATCH 09/13] Calculate default offset relative to end of file Default is 0x800 (2048) bytes from the end of file Signed-off-by: Eliot Blennerhassett --- src/inject_key_bin.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/inject_key_bin.py b/src/inject_key_bin.py index 3130e33..368e0af 100755 --- a/src/inject_key_bin.py +++ b/src/inject_key_bin.py @@ -23,28 +23,31 @@ 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", 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") +parser.add_argument("--offset", default=0, type=lambda x: int(x,0), help="Offset within file to patch") args = parser.parse_args() fname, fext = os.path.splitext(args.bin) assert fext == ".bin" -print("Target binary file:", args.bin) +fsize = os.path.getsize(args.bin) + +print("Target binary file %s, size 0x%X" % (args.bin, fsize)) + +if args.offset: + offset = args.offset +else: + offset = fsize - 0x800 # load and parse private key if not args.key: print("Key not modified") else: - key_offset = args.offset + key_offset = 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 @@ -68,7 +71,7 @@ if not args.ctr: print("Counter not modified") else: - ctr_offset = args.offset + 0x400 + ctr_offset = offset + 0x400 print("Injecting counter %d at 0x%0X" % (args.ctr, ctr_offset)) # fill authentication counter ctr_blob = struct.pack(" Date: Mon, 6 May 2019 11:05:11 +1200 Subject: [PATCH 10/13] Requires u2f-host package, not dfu-utils Signed-off-by: Eliot Blennerhassett --- test/yubico_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/yubico_test.sh b/test/yubico_test.sh index 287b6c4..c3a4b3a 100755 --- a/test/yubico_test.sh +++ b/test/yubico_test.sh @@ -1,5 +1,5 @@ # Basic test -# Requires dfu-utils to be installed. +# Requires u2f-host to be installed. # This runs through the steps described in https://github.com/Yubico/libu2f-host documentation # If run with -k argument, the json files are retainsd. set -x From abb859da0d5d3333c116b38baabe65b04b4610ff Mon Sep 17 00:00:00 2001 From: Eliot Blennerhassett Date: Mon, 6 May 2019 11:09:51 +1200 Subject: [PATCH 11/13] Narrow json ignore to test directory Signed-off-by: Eliot Blennerhassett --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 891c854..3eabd07 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ */.dep doc/chopstx.info .vs/ -*.json +test/*.json From f1c9c4e1445fbdcd4a6725e907e67e5c32567c49 Mon Sep 17 00:00:00 2001 From: Sergei Glushchenko Date: Tue, 7 May 2019 16:35:01 +1200 Subject: [PATCH 12/13] fix comment spelling Co-Authored-By: eliotb --- test/yubico_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/yubico_test.sh b/test/yubico_test.sh index c3a4b3a..a326054 100755 --- a/test/yubico_test.sh +++ b/test/yubico_test.sh @@ -1,7 +1,7 @@ # Basic test # Requires u2f-host to be installed. # This runs through the steps described in https://github.com/Yubico/libu2f-host documentation -# If run with -k argument, the json files are retainsd. +# If run with -k argument, the json files are retained. set -x user="tomu-$(date +%s)" From d64c74d32acfea2f1d7a9be36ed7608abf2fed7d Mon Sep 17 00:00:00 2001 From: Eliot Blennerhassett Date: Thu, 26 Mar 2020 15:56:04 +1300 Subject: [PATCH 13/13] Overwrite inject_key_bin.py with inject_key.py Signed-off-by: Eliot Blennerhassett --- src/inject_key.py | 85 +++++++++++++++++++++++++------------------ src/inject_key_bin.py | 81 ----------------------------------------- 2 files changed, 49 insertions(+), 117 deletions(-) delete mode 100755 src/inject_key_bin.py diff --git a/src/inject_key.py b/src/inject_key.py index 80a0c61..368e0af 100755 --- a/src/inject_key.py +++ b/src/inject_key.py @@ -10,8 +10,10 @@ # > 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.py --key key.der --ctr 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 @@ -20,49 +22,60 @@ import sys import struct import os -import tempfile -import subprocess parser = argparse.ArgumentParser() -parser.add_argument("--elf", default="build/u2f.elf", - help=".elf file to inject keys into") +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=1, type=int, help="value of auth counter") +parser.add_argument("--ctr", default=0, type=lambda x: int(x,0), help="Value of auth counter") +parser.add_argument("--offset", default=0, type=lambda x: int(x,0), help="Offset within file to patch") args = parser.parse_args() -# load and parse private key -if args.key: - with open(args.key, "rb") as f: - der = f.read() +fname, fext = os.path.splitext(args.bin) +assert fext == ".bin" + +fsize = os.path.getsize(args.bin) + +print("Target binary file %s, size 0x%X" % (args.bin, fsize)) + +if args.offset: + offset = args.offset else: - stdin = sys.stdin.buffer if hasattr(sys.stdin, "buffer") else sys.stdin - der = stdin.read() -key = ECPrivateKey.load(der) + offset = fsize - 0x800 -# 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() +# load and parse private key +if not args.key: + print("Key not modified") +else: + key_offset = 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) -# fill authentication counter -ctr_bytes = struct.pack(" 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 - -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=0, type=lambda x: int(x,0), help="Offset within file to patch") -args = parser.parse_args() - -fname, fext = os.path.splitext(args.bin) -assert fext == ".bin" - -fsize = os.path.getsize(args.bin) - -print("Target binary file %s, size 0x%X" % (args.bin, fsize)) - -if args.offset: - offset = args.offset -else: - offset = fsize - 0x800 - -# load and parse private key -if not args.key: - print("Key not modified") -else: - key_offset = 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 = offset + 0x400 - print("Injecting counter %d at 0x%0X" % (args.ctr, ctr_offset)) - # fill authentication counter - ctr_blob = struct.pack("