summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorMagnus Feuer <mfeuer@jaguarlandrover.com>2015-11-23 15:48:50 -0800
committerUlf Wiger <ulf@feuerlabs.com>2015-11-28 19:58:36 -0800
commita39e86223e6edd763013fd5b6859ef737bf70254 (patch)
tree29c98155e6fd3858a5b7df6337ac1df2ee229ff0 /scripts
parent60b9bd3c233233dfd1709f8b753e233adaf38423 (diff)
downloadrvi_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-xscripts/rvi_create_credential.py (renamed from scripts/rvi_create_certificate.py)124
-rwxr-xr-xscripts/rvi_create_device_key.py155
-rwxr-xr-xscripts/rvi_create_root_key.sh60
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."
-