Add configuration tool
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ Cargo.lock
|
|||||||
/reproducible/binaries.sha256sum
|
/reproducible/binaries.sha256sum
|
||||||
/reproducible/elf2tab.txt
|
/reproducible/elf2tab.txt
|
||||||
/reproducible/reproduced.tar
|
/reproducible/reproduced.tar
|
||||||
|
__pycache__
|
||||||
|
|||||||
43
deploy.py
43
deploy.py
@@ -710,6 +710,22 @@ class OpenSKInstaller:
|
|||||||
check=False,
|
check=False,
|
||||||
timeout=None,
|
timeout=None,
|
||||||
).returncode
|
).returncode
|
||||||
|
|
||||||
|
# Configure OpenSK through vendor specific command if needed
|
||||||
|
if any([
|
||||||
|
self.args.lock_device,
|
||||||
|
self.args.config_cert,
|
||||||
|
self.args.config_pkey,
|
||||||
|
]):
|
||||||
|
# pylint: disable=g-import-not-at-top,import-outside-toplevel
|
||||||
|
import tools.configure
|
||||||
|
tools.configure.main(
|
||||||
|
argparse.Namespace(
|
||||||
|
batch=False,
|
||||||
|
certificate=self.args.config_cert,
|
||||||
|
priv_key=self.args.config_pkey,
|
||||||
|
lock=self.args.lock_device,
|
||||||
|
))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -770,6 +786,33 @@ if __name__ == "__main__":
|
|||||||
help=("Erases the persistent storage when installing an application. "
|
help=("Erases the persistent storage when installing an application. "
|
||||||
"All stored data will be permanently lost."),
|
"All stored data will be permanently lost."),
|
||||||
)
|
)
|
||||||
|
main_parser.add_argument(
|
||||||
|
"--lock-device",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
dest="lock_device",
|
||||||
|
help=("Try to disable JTAG at the end of the operations. This "
|
||||||
|
"operation may fail if the device is already locked or if "
|
||||||
|
"the certificate/private key are not programmed."),
|
||||||
|
)
|
||||||
|
main_parser.add_argument(
|
||||||
|
"--inject-certificate",
|
||||||
|
default=None,
|
||||||
|
metavar="PEM_FILE",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
dest="config_cert",
|
||||||
|
help=("If this option is set, the corresponding certificate "
|
||||||
|
"will be programmed into the key as the last operation."),
|
||||||
|
)
|
||||||
|
main_parser.add_argument(
|
||||||
|
"--inject-private-key",
|
||||||
|
default=None,
|
||||||
|
metavar="PEM_FILE",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
dest="config_pkey",
|
||||||
|
help=("If this option is set, the corresponding private key "
|
||||||
|
"will be programmed into the key as the last operation."),
|
||||||
|
)
|
||||||
main_parser.add_argument(
|
main_parser.add_argument(
|
||||||
"--programmer",
|
"--programmer",
|
||||||
metavar="METHOD",
|
metavar="METHOD",
|
||||||
|
|||||||
3
setup.sh
3
setup.sh
@@ -44,3 +44,6 @@ rustup target add thumbv7em-none-eabi
|
|||||||
# Install dependency to create applications.
|
# Install dependency to create applications.
|
||||||
mkdir -p elf2tab
|
mkdir -p elf2tab
|
||||||
cargo install elf2tab --version 0.6.0 --root elf2tab/
|
cargo install elf2tab --version 0.6.0 --root elf2tab/
|
||||||
|
|
||||||
|
# Install python dependencies to factory configure OpenSK (crypto, JTAG lockdown)
|
||||||
|
pip3 install --user --upgrade colorama tqdm cryptography fido2
|
||||||
|
|||||||
197
tools/configure.py
Executable file
197
tools/configure.py
Executable file
@@ -0,0 +1,197 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2020 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
# Lint as: python3
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import getpass
|
||||||
|
import datetime
|
||||||
|
import sys
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import colorama
|
||||||
|
from tqdm.auto import tqdm
|
||||||
|
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import ec
|
||||||
|
|
||||||
|
from fido2 import ctap
|
||||||
|
from fido2 import ctap2
|
||||||
|
from fido2 import hid
|
||||||
|
|
||||||
|
OPENSK_VID_PID = (0x1915, 0x521F)
|
||||||
|
OPENSK_VENDOR_CONFIGURE = 0x40
|
||||||
|
|
||||||
|
|
||||||
|
def fatal(msg):
|
||||||
|
tqdm.write("{style_begin}fatal:{style_end} {message}".format(
|
||||||
|
style_begin=colorama.Fore.RED + colorama.Style.BRIGHT,
|
||||||
|
style_end=colorama.Style.RESET_ALL,
|
||||||
|
message=msg))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def error(msg):
|
||||||
|
tqdm.write("{style_begin}error:{style_end} {message}".format(
|
||||||
|
style_begin=colorama.Fore.RED,
|
||||||
|
style_end=colorama.Style.RESET_ALL,
|
||||||
|
message=msg))
|
||||||
|
|
||||||
|
|
||||||
|
def info(msg):
|
||||||
|
tqdm.write("{style_begin}info:{style_end} {message}".format(
|
||||||
|
style_begin=colorama.Fore.GREEN + colorama.Style.BRIGHT,
|
||||||
|
style_end=colorama.Style.RESET_ALL,
|
||||||
|
message=msg))
|
||||||
|
|
||||||
|
|
||||||
|
def get_opensk_devices(batch_mode):
|
||||||
|
devices = []
|
||||||
|
for dev in hid.CtapHidDevice.list_devices():
|
||||||
|
if (dev.descriptor["vendor_id"],
|
||||||
|
dev.descriptor["product_id"]) == OPENSK_VID_PID:
|
||||||
|
if dev.capabilities & hid.CAPABILITY.CBOR:
|
||||||
|
if batch_mode:
|
||||||
|
devices.append(ctap2.CTAP2(dev))
|
||||||
|
else:
|
||||||
|
return [ctap2.CTAP2(dev)]
|
||||||
|
return devices
|
||||||
|
|
||||||
|
|
||||||
|
def get_private_key(data, password=None):
|
||||||
|
# First we try without password
|
||||||
|
try:
|
||||||
|
return serialization.load_pem_private_key(data, password=None)
|
||||||
|
except TypeError:
|
||||||
|
# Maybe we need a password then
|
||||||
|
if sys.stdin.isatty():
|
||||||
|
password = getpass.getpass(prompt="Private key password: ")
|
||||||
|
else:
|
||||||
|
password = sys.stdin.readline().rstrip()
|
||||||
|
return get_private_key(data, password=password.encode(sys.stdin.encoding))
|
||||||
|
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
colorama.init()
|
||||||
|
# We need either both the certificate and the key or none
|
||||||
|
if bool(args.priv_key) ^ bool(args.certificate):
|
||||||
|
fatal("Certificate and private key must be set together or both omitted.")
|
||||||
|
|
||||||
|
cbor_data = {1: args.lock}
|
||||||
|
|
||||||
|
if args.priv_key:
|
||||||
|
cbor_data[1] = args.lock
|
||||||
|
priv_key = get_private_key(args.priv_key.read())
|
||||||
|
if not isinstance(priv_key, ec.EllipticCurvePrivateKey):
|
||||||
|
fatal("Private key must be an Elliptic Curve one.")
|
||||||
|
if not isinstance(priv_key.curve, ec.SECP256R1):
|
||||||
|
fatal("Private key must use Secp256r1 curve.")
|
||||||
|
if priv_key.key_size != 256:
|
||||||
|
fatal("Private key must be 256 bits long.")
|
||||||
|
info("Private key is valid.")
|
||||||
|
|
||||||
|
cert = x509.load_pem_x509_certificate(args.certificate.read())
|
||||||
|
# Some sanity/validity checks
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
if cert.not_valid_before > now:
|
||||||
|
fatal("Certificate validity starts in the future.")
|
||||||
|
if cert.not_valid_after <= now:
|
||||||
|
fatal("Certificate expired.")
|
||||||
|
pub_key = cert.public_key()
|
||||||
|
if not isinstance(pub_key, ec.EllipticCurvePublicKey):
|
||||||
|
fatal("Certificate public key must be an Elliptic Curve one.")
|
||||||
|
if not isinstance(pub_key.curve, ec.SECP256R1):
|
||||||
|
fatal("Certificate public key must use Secp256r1 curve.")
|
||||||
|
if pub_key.key_size != 256:
|
||||||
|
fatal("Certificate public key must be 256 bits long.")
|
||||||
|
if pub_key.public_numbers() != priv_key.public_key().public_numbers():
|
||||||
|
fatal("Certificate public doesn't match with the private key.")
|
||||||
|
info("Certificate is valid.")
|
||||||
|
|
||||||
|
cbor_data[2] = {
|
||||||
|
1:
|
||||||
|
cert.public_bytes(serialization.Encoding.DER),
|
||||||
|
2:
|
||||||
|
priv_key.private_numbers().private_value.to_bytes(
|
||||||
|
length=32, byteorder='big', signed=False)
|
||||||
|
}
|
||||||
|
|
||||||
|
for authenticator in tqdm(get_opensk_devices(args.batch)):
|
||||||
|
# If the device supports it, wink to show which device
|
||||||
|
# we're going to program
|
||||||
|
if authenticator.device.capabilities & hid.CAPABILITY.WINK:
|
||||||
|
authenticator.device.wink()
|
||||||
|
aaguid = uuid.UUID(bytes=authenticator.get_info().aaguid)
|
||||||
|
info(("Programming device {} AAGUID {} ({}). "
|
||||||
|
"Please touch the device to confirm...").format(
|
||||||
|
authenticator.device.descriptor.get("product_string", "Unknown"),
|
||||||
|
aaguid, authenticator.device))
|
||||||
|
try:
|
||||||
|
result = authenticator.send_cbor(
|
||||||
|
OPENSK_VENDOR_CONFIGURE,
|
||||||
|
data=cbor_data,
|
||||||
|
)
|
||||||
|
info("Certificate: {}".format("Present" if result[1] else "Missing"))
|
||||||
|
info("Private Key: {}".format("Present" if result[2] else "Missing"))
|
||||||
|
if result[3]:
|
||||||
|
info("Device locked down!")
|
||||||
|
except ctap.CtapError as ex:
|
||||||
|
if ex.code.value == ctap.CtapError.ERR.INVALID_COMMAND:
|
||||||
|
error("Failed to configure OpenSK (unsupported command).")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--batch",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help=(
|
||||||
|
"When batch processing is used, all plugged OpenSK devices will "
|
||||||
|
"be programmed the same way. Otherwise (default) only the first seen "
|
||||||
|
"device will be programmed."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--certificate",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
default=None,
|
||||||
|
metavar="PEM_FILE",
|
||||||
|
dest="certificate",
|
||||||
|
help=("PEM file containing the certificate to inject into "
|
||||||
|
"OpenSK authenticator."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--private-key",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
default=None,
|
||||||
|
metavar="PEM_FILE",
|
||||||
|
dest="priv_key",
|
||||||
|
help=("PEM file containing the private key associated "
|
||||||
|
"with the certificate."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--lock-device",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
dest="lock",
|
||||||
|
help=("Locks the device (i.e. bootloader and JTAG access). "
|
||||||
|
"This command can fail if the certificate or the private key "
|
||||||
|
"haven't been both programmed yet."),
|
||||||
|
)
|
||||||
|
main(parser.parse_args())
|
||||||
Reference in New Issue
Block a user