Skip to content

Commit

Permalink
scripts/bootloader: Add ed25519/sha512 to scripts
Browse files Browse the repository at this point in the history
Python scripts implementing ed25519 and sha512 support needed
for nsib image signing.

Signed-off-by: Lukasz Fundakowski <[email protected]>
  • Loading branch information
fundakol committed Nov 19, 2024
1 parent d839e3c commit b436a1d
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 45 deletions.
33 changes: 26 additions & 7 deletions scripts/bootloader/asn1parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause


from subprocess import check_output
import argparse
import re
import sys
import argparse
from subprocess import check_output

from cryptography.hazmat.primitives.asymmetric import ed25519

def get_ecdsa_signature(der, clength):

def get_ecdsa_signature(der: bytes, clength: int) -> bytes:
# The der consists of a SEQUENCE with 2 INTEGERS (r and s)
# The expected byte format of the file is
# 0: type(SEQ), len(SEQ)
Expand All @@ -24,7 +26,7 @@ def get_ecdsa_signature(der, clength):
# leading 0 byte.
# clength is the expected length of r and s.
# The following code parses the output of openssl asnparse which prints
# the values in hex, together with human-readble metadata.
# the values in hex, together with human-readable metadata.

# Disable pylint error as 'input' keyword has specific handling in 'check_output'
# pylint: disable=unexpected-keyword-arg
Expand All @@ -36,13 +38,19 @@ def get_ecdsa_signature(der, clength):
return sig


def get_ed25519_signature(der: bytes) -> bytes:
private_key = ed25519.Ed25519PrivateKey.generate()
signature = private_key.sign(der)
return signature


def parse_args():
parser = argparse.ArgumentParser(
description='Decode DER format using OpenSSL.',
formatter_class=argparse.RawDescriptionHelpFormatter,
allow_abbrev=False)

parser.add_argument('-a', '--alg', required=True, choices=['rsa', 'ecdsa'],
parser.add_argument('-a', '--alg', required=True, choices=['rsa', 'ecdsa', 'ed25519'],
help='Expected algorithm')
parser.add_argument('-c', '--contents', required=True, choices=['signature'],
help='Expected contents')
Expand All @@ -55,9 +63,20 @@ def parse_args():
return args


if __name__ == '__main__':
def main() -> int:
args = parse_args()
assert args.alg == 'ecdsa' # Only ecdsa is currently supported.

if args.alg == 'ecdsa':
if args.contents == 'signature':
sys.stdout.buffer.write(get_ecdsa_signature(args.infile.read(), 32))
elif args.alg == 'ed25519':
if args.contents == 'signature':
sys.stdout.buffer.write(get_ed25519_signature(args.infile.read()))
else:
sys.exit(f'Algorythm not supported {args.alg}')

return 0


if __name__ == '__main__':
sys.exit(main())
41 changes: 30 additions & 11 deletions scripts/bootloader/do_sign.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
# Copyright (c) 2018 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause


import sys
import argparse
import hashlib
import sys

from cryptography.hazmat.primitives.serialization import load_pem_private_key
from ecdsa import SigningKey


def parse_args():
def parse_args(argv=None):
parser = argparse.ArgumentParser(
description='Sign data from stdin or file.',
formatter_class=argparse.RawDescriptionHelpFormatter,
allow_abbrev=False)
allow_abbrev=False
)

parser.add_argument('-k', '--private-key', required=True, type=argparse.FileType('rb'),
help='Private key to use.')
Expand All @@ -25,15 +26,33 @@ def parse_args():
parser.add_argument('-o', '--out', '-out', required=False, dest='outfile',
type=argparse.FileType('wb'), default=sys.stdout.buffer,
help='Write the signature to the specified file instead of stdout.')
parser.add_argument(
'--alg', '-a', dest='algorythm', help='Encryption algorythm (default: %(default)s)',
action='store', choices=['ecdsa', 'ed25519'], default='ecdsa',
)

args = parser.parse_args()
args = parser.parse_args(argv)

return args


def main(argv=None) -> int:
args = parse_args(argv)
if args.algorythm == 'ecdsa':
private_key = SigningKey.from_pem(args.private_key.read())
data = args.infile.read()
signature = private_key.sign(data, hashfunc=hashlib.sha256)
args.outfile.write(signature)
return 0
if args.algorythm == 'ed25519':
private_key = load_pem_private_key(args.private_key.read(), password=None)
data = args.infile.read()
signature = private_key.sign(data)
args.outfile.write(signature)
return 0

return 1


if __name__ == '__main__':
args = parse_args()
private_key = SigningKey.from_pem(args.private_key.read())
data = args.infile.read()
signature = private_key.sign(data, hashfunc=hashlib.sha256)
args.outfile.write(signature)
sys.exit(main())
35 changes: 29 additions & 6 deletions scripts/bootloader/hash.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,43 @@
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause

"""
Hash content of a file.
"""

import hashlib
import sys
import argparse
from intelhex import IntelHex


HASH_FUNCTION_FACTORY = {
'sha256': hashlib.sha256,
'sha512': hashlib.sha512,
}


def parse_args():
parser = argparse.ArgumentParser(
description='Hash data from file.',
formatter_class=argparse.RawDescriptionHelpFormatter,
allow_abbrev=False)

parser.add_argument('--infile', '-i', '--in', '-in', required=True,
help='Hash the contents of the specified file. If a *.hex file is given, the contents will '
'first be converted to binary, with all non-specified area being set to 0xff. '
'For all other file types, no conversion is done.')
parser.add_argument(
'--infile', '-i', '--in', '-in', required=True,
help='Hash the contents of the specified file. If a *.hex file is given, the contents will '
'first be converted to binary, with all non-specified area being set to 0xff. '
'For all other file types, no conversion is done.'
)
parser.add_argument(
'--type', '-t', dest='hash_function', help='Hash function (default: %(default)s)',
action='store', choices=HASH_FUNCTION_FACTORY.keys(), default='sha256'
)

return parser.parse_args()


if __name__ == '__main__':
def main():
args = parse_args()

if args.infile.endswith('.hex'):
Expand All @@ -33,4 +49,11 @@ def parse_args():
to_hash = ih.tobinstr()
else:
to_hash = open(args.infile, 'rb').read()
sys.stdout.buffer.write(hashlib.sha256(to_hash).digest())

hash_function = HASH_FUNCTION_FACTORY[args.hash_function]
sys.stdout.buffer.write(hash_function(to_hash).digest())
return 0


if __name__ == '__main__':
sys.exit(main())
105 changes: 84 additions & 21 deletions scripts/bootloader/keygen.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause


import argparse
import sys
from hashlib import sha256, sha512

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.hazmat.primitives.serialization import load_pem_private_key as load_pem
from hashlib import sha256
import argparse
import sys


def generate_legal_key():
Expand All @@ -23,8 +25,8 @@ def generate_legal_key():
while True:
key = ec.generate_private_key(ec.SECP256R1())
public_bytes = key.public_key().public_bytes(
encoding=serialization.Encoding.X962,
format=serialization.PublicFormat.UncompressedPoint,
encoding=serialization.Encoding.X962,
format=serialization.PublicFormat.UncompressedPoint,
)

# The digest don't contain the first byte as it denotes
Expand All @@ -35,7 +37,69 @@ def generate_legal_key():
return key


if __name__ == '__main__':
def generate_legal_key_sha512():
"""
Ensure that we don't have 0xFFFF in the hash of the public key of
the generated keypair.
:return: A key who's SHA512 digest does not contain 0xFFFF
"""
while True:
key = ed25519.Ed25519PrivateKey.generate()
public_bytes = key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

# The digest don't contain the first byte as it denotes
# if it is compressed/UncompressedPoint.
digest = sha512(public_bytes[1:]).digest()[:16]
if not any([digest[n:n + 2] == b'\xff\xff' for n in range(0, len(digest), 2)]):
return key


def encrypt_with_elliptic_curve(args):
"""Generate private and public keys for Elliptic curve cryptography."""
sk = (load_pem(args.infile.read(), password=None) if args.infile else generate_legal_key())
if args.private:
private_pem = sk.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
args.out.write(private_pem)

if args.public:
public_pem = sk.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
args.out.write(public_pem)


def encrypt_with_ed25519(args):
"""Generate private and public keys for ED25519 cryptography."""
if args.infile:
private_key = load_pem(args.infile.read(), password=None)
else:
private_key = generate_legal_key_sha512()
if args.private:
private_bytes = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
args.out.write(private_bytes)
if args.public:
public_key = private_key.public_key()
public_bytes = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
args.out.write(public_bytes)


def main(argv=None) -> int:
parser = argparse.ArgumentParser(
description='Generate PEM file.',
formatter_class=argparse.RawDescriptionHelpFormatter,
Expand All @@ -53,21 +117,20 @@ def generate_legal_key():
type=argparse.FileType('rb'),
help='Read private key from specified PEM file instead '
'of generating it.')
parser.add_argument(
'--algorithm', '-a', help='Encryption Algorithm (default: %(default)s)',
required=False, action='store', choices=('ec', 'ed25519'), default='ec'
)

args = parser.parse_args()
sk = (load_pem(args.infile.read(), password=None) if args.infile else generate_legal_key())
args = parser.parse_args(argv)

if args.private:
private_pem = sk.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
args.out.write(private_pem)
if args.algorithm == 'ed25519':
encrypt_with_ed25519(args)
else:
encrypt_with_elliptic_curve(args)

if args.public:
public_pem = sk.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
args.out.write(public_pem)
return 0


if __name__ == '__main__':
sys.exit(main())
Loading

0 comments on commit b436a1d

Please sign in to comment.