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 29, 2024
1 parent d839e3c commit 74b17df
Show file tree
Hide file tree
Showing 9 changed files with 1,011 additions and 112 deletions.
94 changes: 80 additions & 14 deletions scripts/bootloader/do_sign.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,103 @@
# Copyright (c) 2018 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
from __future__ import annotations


import sys
import argparse
import contextlib
import hashlib
from ecdsa import SigningKey
import sys
from pathlib import Path
from typing import BinaryIO, Generator

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from ecdsa.keys import SigningKey # type: ignore[import-untyped]
from intelhex import IntelHex # type: ignore[import-untyped]


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'),
parser.add_argument('-k', '--private-key', required=True, type=Path,
help='Private key to use.')
parser.add_argument('-i', '--in', '-in', required=False, dest='infile',
type=argparse.FileType('rb'), default=sys.stdin.buffer,
type=Path, default=sys.stdin.buffer,
help='Sign the contents of the specified file instead of stdin.')
parser.add_argument('-o', '--out', '-out', required=False, dest='outfile',
type=argparse.FileType('wb'), default=sys.stdout.buffer,
type=Path, default=None,
help='Write the signature to the specified file instead of stdout.')
parser.add_argument(
'--algorithm', '-a', dest='algorithm',
help='Signing algorithm (default: %(default)s)',
action='store', choices=['ecdsa', 'ed25519'], default='ecdsa',
)

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

return args


if __name__ == '__main__':
args = parse_args()
private_key = SigningKey.from_pem(args.private_key.read())
data = args.infile.read()
@contextlib.contextmanager
def open_stream(output_file: Path | None = None) -> Generator[BinaryIO, None, None]:
if output_file is not None:
stream = open(output_file, 'wb')
try:
yield stream
finally:
stream.close()
else:
yield sys.stdout.buffer


def hex_to_binary(input_hex_file: str) -> bytes:
ih = IntelHex(input_hex_file)
ih.padding = 0xff # Allows hashing with empty regions
data = ih.tobinstr()
return data


def sign_with_ecdsa(
private_key_file: Path, input_file: Path, output_file: Path | None = None
) -> int:
with open(private_key_file, 'r') as f:
private_key = SigningKey.from_pem(f.read())
with open(input_file, 'rb') as f:
data = f.read()
signature = private_key.sign(data, hashfunc=hashlib.sha256)
args.outfile.write(signature)
with open_stream(output_file) as stream:
stream.write(signature)
return 0


def sign_with_ed25519(
private_key_file: Path, input_file: Path, output_file: Path | None = None
) -> int:
with open(private_key_file, 'rb') as f:
private_key: Ed25519PrivateKey = load_pem_private_key(f.read(), password=None) # type: ignore[assignment]
if str(input_file).endswith('.hex'):
data = hex_to_binary(str(input_file))
else:
with open(input_file, 'rb') as f:
data = f.read()
signature = private_key.sign(data)
with open_stream(output_file) as stream:
stream.write(signature)
return 0


def main(argv=None) -> int:
args = parse_args(argv)
if args.algorithm == 'ecdsa':
return sign_with_ecdsa(args.private_key, args.infile, args.outfile)
if args.algorithm == 'ed25519':
return sign_with_ed25519(args.private_key, args.infile, args.outfile)
return 1


if __name__ == '__main__':
sys.exit(main())
52 changes: 39 additions & 13 deletions scripts/bootloader/hash.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause

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

import argparse
import hashlib
import sys
import argparse
from intelhex import IntelHex

from intelhex import IntelHex # type: ignore[import-untyped]

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


def parse_args():
Expand All @@ -17,20 +26,37 @@ def parse_args():
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.')
return parser.parse_args()
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__':
args = parse_args()

if args.infile.endswith('.hex'):
ih = IntelHex(args.infile)
def generate_hash_digest(file: str, hash_function_name: str) -> bytes:
if file.endswith('.hex'):
ih = IntelHex(file)
ih.padding = 0xff # Allows hashing with empty regions
to_hash = ih.tobinstr()
else:
to_hash = open(args.infile, 'rb').read()
sys.stdout.buffer.write(hashlib.sha256(to_hash).digest())
to_hash = open(file, 'rb').read()

hash_function = HASH_FUNCTION_FACTORY[hash_function_name]
return hash_function(to_hash).digest()


def main():
args = parse_args()
sys.stdout.buffer.write(generate_hash_digest(args.infile, args.hash_function))
return 0


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

0 comments on commit 74b17df

Please sign in to comment.