diff options
| author | Magnus Feuer <mfeuer@jaguarlandrover.com> | 2015-11-23 15:48:50 -0800 |
|---|---|---|
| committer | Ulf Wiger <ulf@feuerlabs.com> | 2015-11-28 19:58:36 -0800 |
| commit | a39e86223e6edd763013fd5b6859ef737bf70254 (patch) | |
| tree | 29c98155e6fd3858a5b7df6337ac1df2ee229ff0 /scripts | |
| parent | 60b9bd3c233233dfd1709f8b753e233adaf38423 (diff) | |
| download | rvi_core-a39e86223e6edd763013fd5b6859ef737bf70254.tar.gz | |
First stab at new credentials generation based on x.509 device and root certificates.
Check doc/rvi_protocol.md for details on certificate management.
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/rvi_create_credential.py (renamed from scripts/rvi_create_certificate.py) | 124 | ||||
| -rwxr-xr-x | scripts/rvi_create_device_key.py | 155 | ||||
| -rwxr-xr-x | scripts/rvi_create_root_key.sh | 60 |
3 files changed, 77 insertions, 262 deletions
diff --git a/scripts/rvi_create_certificate.py b/scripts/rvi_create_credential.py index 6e09fdc..112f939 100755 --- a/scripts/rvi_create_certificate.py +++ b/scripts/rvi_create_credential.py @@ -41,22 +41,57 @@ def long_to_base64(n): s = base64.urlsafe_b64encode(data).rstrip(b'=') return s + +def read_x509_cert_pem_file(file_name): + CONST_PEM_CERT_BEGIN="-----BEGIN CERTIFICATE-----\n" + CONST_PEM_CERT_END="-----END CERTIFICATE-----\n" + + try: + fp = open(file_name, "r") + dev_pem = fp.read() + fp.close() + + except IOError as e: + print "Could read device certificate from {0}: {1}".format(a, e.strerror) + sys.exit(255) + + # Check that cert file starts with the correct tag + pem_start = dev_pem.find(CONST_PEM_CERT_BEGIN) + if pem_start != 0: + print "Device certificate file {} does not start".format(file_name) + print "with certificate identifier." + sys.exit(255) + + # Move to first character after start tag. + pem_start += len(CONST_PEM_CERT_BEGIN) + + # Find end tag. + pem_end = dev_pem.find(CONST_PEM_CERT_END) + if pem_end == -1: + print "Device certificate file {} does not end".format(file_name) + print "with certificate identifier." + sys.exit(255) + + # Return string between tags, stripped of \r and \n + return dev_pem[pem_start: pem_end].replace("\n", "").replace("\r", "") + + def usage(): print "Usage:", sys.argv[0], "--id=<id> --invoke='<services>' -register='<services>' \\" print " --root_key=<file> --start='<date/time>' --stop='<date/time>' \\" print " --out=<file>" print - print " --id=<id> System-wide unique certificate ID" + print " --id=<id> System-wide unique credential ID" print print " --invoke='<services>' Right to invoke service. Space separate multiple services." print print " --register='<services>' Right to register service. Space separate multiple services." print " At least one --invoke or --register must be given." print - print " --root_key=<file> Private root key to sign certificate with" + print " --root_key=<file> Private, PEM-encoded root key to sign credential with" print " Mandatory" print - print " --device_key=<file> Public device key to include in certificate" + print " --device_cert=<file> Device X.509 certificate to include in credential" print " Mandatory" print print " --start='<YYYY-MM-DD HH:MM:SS>' Date and time when certificate is activated." @@ -68,35 +103,36 @@ def usage(): print " --jwt_out=<file> File name to store JWT-encoded certificate in." print " Default: stdout" print - print " --cert_out=<file> File name to unencoded JSON certificate in." + print " --cred_out=<file> File name to unencoded JSON credential in." print " Default: Do not store certificate" print - print " --issuer=issuer Name of the issuer. Default: jaguarlandrover.com" + print " --issuer=issuer Name of the issuer." + print " Mandatory" print - print "Root key file is generated by rvi_create_root_key.sh" + print "Root key file is generated by steps described in doc/rvi_protocol.md" print - print "Device key is the '_pub.pem'-suffixed file created by rvi_create_device_key.py" + print "Device X.509 certificate is generated by steps described in doc/rvi_protocol.md" print - print "Certificate file specified by out should be placed in 'priv/certs'" + print "Credentials file specified by out should be placed in 'priv/credentials'" print print print "Example:" - print "./rvi_create_certificate.py --id=317624d8-2ccf-11e5-993c-7f3b5182c649 \\" - print " --device_key=device_key_pub.pem \\" - print " --start='2015-12-01 00:00:00' \\" - print " --stop='2015-12-31 23:59:59' \\" - print " --root_key=root_key_priv.pem \\" + print "./rvi_create_credential.py --id=317624d8-2ccf-11e5-993c-7f3b5182c649 \\" + print " --device_cert=device_cert.crt \\" + print " --start='2014-12-01 00:00:00' \\" + print " --stop='2020-12-31 23:59:59' \\" + print " --root_key=root_key.pem \\" print " --register='jlr.com/vin/abc/unlock jlr.com/vin/abc/lock' \\" print " --invoke='jlr.com/backend/report jlr.com/backend/set_state' \\" print " --jwt_out=lock_cert.jwt \\" - print " --cert_out=lock_cert.json" + print " --cred_out=lock_credential.json" sys.exit(255) try: opts, args = getopt.getopt(sys.argv[1:], "", [ 'issuer=', 'invoke=', 'register=', 'root_key=', 'start=', - 'stop=', 'cert_out=', 'id=', - 'jwt_out=', 'device_key=']) + 'stop=', 'cred_out=', 'id=', + 'jwt_out=', 'device_cert=']) except getopt.GetoptError as e: print print e @@ -106,13 +142,13 @@ except getopt.GetoptError as e: start=int(time.time()) stop=int(time.time()) + 86400 * 365 -issuer='jaguarlandrover.com' +issuer=None invoke=None register=None root_key=None -device_key=None +device_cert=None jwt_out_file=None -cert_out_file=None +cred_out_file=None id_string=None for o, a in opts: if o == "--start": @@ -134,14 +170,8 @@ for o, a in opts: print "Coould read root cert from {0}: {1}".format(a, e.strerror) sys.exit(255) - elif o == '--device_key': - try: - device_key_file = open(a, "r") - device_key = RSA.importKey(device_key_file.read()) - device_key_file.close() - except IOError as e: - print "Coould read root cert from {0}: {1}".format(a, e.strerror) - sys.exit(255) + elif o == '--device_cert': + device_cert = read_x509_cert_pem_file(a) elif o == "--stop": try: @@ -168,14 +198,14 @@ for o, a in opts: try: jwt_out_file = open(a, "w") except IOError as e: - print "Coould write to JWT file {0}: {1}".format(a, e.strerror) + print "Could not write to JWT file {0}: {1}".format(a, e.strerror) sys.exit(255) - elif o == '--cert_out': + elif o == '--cred_out': try: - cert_out_file = open(a, "w") + cred_out_file = open(a, "w") except IOError as e: - print "Coould write to certificate file {0}: {1}".format(a, e.strerror) + print "Could not write to credentials file {0}: {1}".format(a, e.strerror) sys.exit(255) else: @@ -199,9 +229,15 @@ if not root_key: print usage() -if not device_key: +if not issuer: print - print "No --device_key=<device_public_key_file.pem> specified" + print "No --issuer=<credential issuer> specified" + print + usage() + +if not device_cert: + print + print "No --device_cert=<device_public_key_file.pem> specified" print usage() @@ -213,21 +249,15 @@ if not id_string: # Create a JSON Web Key based off our public device key PEM file + - -cert = { +cred = { 'iss': issuer, 'id': id_string, 'sources': register, 'destinations': invoke, 'create_timestamp': int(time.time()), - 'keys': [{ - "kty": "RSA", - "alg": "RS256", - "use": "sig", - "e": long_to_base64(device_key.e), - "n": long_to_base64(device_key.n) - }], + 'device_cert': device_cert, 'validity': { 'start': start, 'stop': stop @@ -235,7 +265,8 @@ cert = { } -encoded = jwt.encode(cert, root_key.exportKey("PEM"), algorithm='RS256') + +encoded = jwt.encode(cred, root_key.exportKey("PEM"), algorithm='RS256') # Validate try: @@ -244,11 +275,10 @@ except: print "FAILED: Could not verify signed JSON Web Token using public part of" print " root key {}".format(root_key_fname) - jwt_out_file.write(encoded) jwt_out_file.close() -if cert_out_file: - cert_out_file.write(json.dumps(cert, sort_keys=True, indent=4, separators=(',', ': ')) + '\n') - cert_out_file.close() +if cred_out_file: + cred_out_file.write(json.dumps(cred, sort_keys=True, indent=4, separators=(',', ': ')) + '\n') + cred_out_file.close() diff --git a/scripts/rvi_create_device_key.py b/scripts/rvi_create_device_key.py deleted file mode 100755 index 060ea8b..0000000 --- a/scripts/rvi_create_device_key.py +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/python - -# -# Copyright (C) 2015, Jaguar Land Rover -# -# This program is licensed under the terms and conditions of the -# Mozilla Public License, version 2.0. T full text of the -# Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ -# -# -# 1. Generate a device key through openssl. -# 2. Sign the public key using the provided private root key. -# 3. Create a JWT version of the signed device key. -# - -import getopt -import sys -from Crypto.PublicKey import RSA -import json -import base64 -import os - -# apt-get install python-dev -# apt-get install libffi-dev -# pip install cryptography -# pip install PyJWT - -import jwt -import struct - -def long2intarr(long_int): - _bytes = [] - while long_int: - long_int, r = divmod(long_int, 256) - _bytes.insert(0, r) - return _bytes - -# copied from https://github.com/rohe/pyjwkest -def long_to_base64(n): - bys = long2intarr(n) - data = struct.pack('%sB' % len(bys), *bys) - if not len(data): - data = '\x00' - s = base64.urlsafe_b64encode(data).rstrip(b'=') - return s - -def usage(): - print "Usage:", sys.argv[0], "-p <priv_root_key> -o <prefix> -b <bits>" - print " -p <priv_root_key> Private root key file to sign public device key with." - print " -o <prefix> File name prefix for generated device keys." - print " -b <bits> Device key length. Default 2048." - sys.exit(255) - -opts, args= getopt.getopt(sys.argv[1:], "p:o:b:") - -priv_root_key_fname="" -fname_prefix="" -key_lenth=2048 -for o, a in opts: - if o == "-p": - priv_root_key_fname = a - elif o == "-b": - key_length = int(a) - elif o == "-o": - fname_prefix = a - else: - usage() - -if priv_root_key_fname == "" or fname_prefix == "": - usage() - - -# -# Generate the device RSA key pair -# -new_key = RSA.generate(bits=key_length) - - -# -# Create private key file, which also contains the public key. -# -priv_key_fname = "{}_priv.pem".format(fname_prefix) -priv_key_file = open(priv_key_fname, 'w') -priv_key = new_key.exportKey("PEM") -priv_key_file.write(priv_key) -priv_key_file.close() - - -# Read the root private key -priv_root_key_file = open(priv_root_key_fname, 'r') -priv_root_key = RSA.importKey(priv_root_key_file.read()) -priv_root_key_file.close() - - -# -# Extract the device public key that we are to sign with the private root key. -# Dump it in a file to be used by rvi_create_certificate.py -# -pub_key_fname = "{}_pub.pem".format(fname_prefix) -pub_key_file = open(pub_key_fname, 'w') -pub_key = new_key.publickey() -pub_key_pem = pub_key.exportKey("PEM") -pub_key_file.write(pub_key_pem) -pub_key_file.close() - -print "pub_key.e = ", pub_key.e -print "pub_key.n = ", pub_key.n - -key_obj = { - 'keys': [{ - "kty": "RSA", - "alg": "RS256", - "use": "sig", - "e": long_to_base64(pub_key.e), - "n": long_to_base64(pub_key.n) - }], -} - -# Generate a JWT signature based on the pub key and the private root key -signature = jwt.encode(key_obj, priv_root_key.exportKey('PEM'), algorithm='RS256') - - -# Verify that we can use the public root key to verify the key. -pub_root_key = priv_root_key.publickey().exportKey('PEM') - -try: - jwt.decode(signature, pub_root_key) -except: - print "FAILED VERIFICATION!" - print "The public portion of the generated device key, signed by the provided private root key," - print "could not be verified using the public root key." - os.remove(priv_key_fname) - sys.exit(0) - -# -# Create signed public JWT file -# -pub_sign_key_fname = "{}_pub_sign.jwt".format(fname_prefix) - -pub_sign_key_file = open(pub_sign_key_fname, 'w') -pub_sign_key_file.write(signature) -pub_sign_key_file.close() - -print "Device private/public key pair stored in: ", priv_key_fname -print "Device public-only key stored in: ", pub_key_fname -print "Device JWT-formatted public key signed by private " -print " root key, stored in: ", pub_sign_key_fname -print "Root key used to sign the device public key read from: ", priv_root_key_fname -print -print "Set rvi node's device_key_pair config parameter to point to: ", priv_root_key_fname -print "Set rvi node's authorize_jwt config parameter to point to: ", pub_sign_key_fname -print -print "use ./rvi_create_certificate.py ... --device_key={} to include".format(pub_key_fname) -print "device public key in a certificate." - diff --git a/scripts/rvi_create_root_key.sh b/scripts/rvi_create_root_key.sh deleted file mode 100755 index b6e3399..0000000 --- a/scripts/rvi_create_root_key.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh - -# -# Copyright (C) 2015, Jaguar Land Rover -# -# This program is licensed under the terms and conditions of the -# Mozilla Public License, version 2.0. The full text of the -# Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ -# - -# -# Create a new root key and break it up into a public and a private PEM file -# -OUTPREFIX="" -BITS="" - - -while getopts ":o:b:" opt -do - case $opt in - o) - OUTPREFIX=$OPTARG - ;; - - b) - BITS=$OPTARG - ;; - - \?) - echo "Invalid option: -$OPTARG" >&2 - exit 1 - ;; - :) - echo "Option -$OPTARG requires an argument." >&2 - exit 1 - ;; - esac -done - - -if [ "${OUTPREFIX}" = "" -o "$BITS" = "" ] -then - echo "Usage: $0 -o <out_file> -b <bits>" - echo " -o <out_file> Name prefix of generated private and public file names" - echo " -b <bits> Key bit length. 2048 or higher." - exit 255 -fi - -openssl genrsa -out ${OUTPREFIX}_priv.pem ${BITS} -openssl rsa -pubout -in ${OUTPREFIX}_priv.pem -out ${OUTPREFIX}_pub.pem - -echo "${OUTPREFIX}_priv.pem - private root key" -echo "${OUTPREFIX}_pub.pem - public root key" -echo -echo "Use ./rvi_create_device_key.py -p ${OUTPREFIX}_priv.pem -b <bits> -o <device_key_file>" -echo "to create a device key pair signed by the generated private root key." -echo -echo "Use ./rvi_create_certificate.py ... --root_key=${OUTPREFIX}_priv.pem ..." -echo "to sign a created certificate with the generated private root key." - |
