Skip to content

Commit d3f8f0f

Browse files
fundakolnvlsianpu
authored andcommitted
scripts/bootloader: Add ed25519/sha512 to scripts
Python scripts implementing ed25519 and sha512 support needed for nsib image signing. Signed-off-by: Lukasz Fundakowski <[email protected]>
1 parent df2d439 commit d3f8f0f

File tree

9 files changed

+1011
-112
lines changed

9 files changed

+1011
-112
lines changed

scripts/bootloader/do_sign.py

100644100755
Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,103 @@
33
# Copyright (c) 2018 Nordic Semiconductor ASA
44
#
55
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
6+
from __future__ import annotations
67

7-
8-
import sys
98
import argparse
9+
import contextlib
1010
import hashlib
11-
from ecdsa import SigningKey
11+
import sys
12+
from pathlib import Path
13+
from typing import BinaryIO, Generator
14+
15+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
16+
from cryptography.hazmat.primitives.serialization import load_pem_private_key
17+
from ecdsa.keys import SigningKey # type: ignore[import-untyped]
18+
from intelhex import IntelHex # type: ignore[import-untyped]
1219

1320

14-
def parse_args():
21+
def parse_args(argv=None):
1522
parser = argparse.ArgumentParser(
1623
description='Sign data from stdin or file.',
1724
formatter_class=argparse.RawDescriptionHelpFormatter,
18-
allow_abbrev=False)
25+
allow_abbrev=False
26+
)
1927

20-
parser.add_argument('-k', '--private-key', required=True, type=argparse.FileType('rb'),
28+
parser.add_argument('-k', '--private-key', required=True, type=Path,
2129
help='Private key to use.')
2230
parser.add_argument('-i', '--in', '-in', required=False, dest='infile',
23-
type=argparse.FileType('rb'), default=sys.stdin.buffer,
31+
type=Path, default=sys.stdin.buffer,
2432
help='Sign the contents of the specified file instead of stdin.')
2533
parser.add_argument('-o', '--out', '-out', required=False, dest='outfile',
26-
type=argparse.FileType('wb'), default=sys.stdout.buffer,
34+
type=Path, default=None,
2735
help='Write the signature to the specified file instead of stdout.')
36+
parser.add_argument(
37+
'--algorithm', '-a', dest='algorithm',
38+
help='Signing algorithm (default: %(default)s)',
39+
action='store', choices=['ecdsa', 'ed25519'], default='ecdsa',
40+
)
2841

29-
args = parser.parse_args()
42+
args = parser.parse_args(argv)
3043

3144
return args
3245

3346

34-
if __name__ == '__main__':
35-
args = parse_args()
36-
private_key = SigningKey.from_pem(args.private_key.read())
37-
data = args.infile.read()
47+
@contextlib.contextmanager
48+
def open_stream(output_file: Path | None = None) -> Generator[BinaryIO, None, None]:
49+
if output_file is not None:
50+
stream = open(output_file, 'wb')
51+
try:
52+
yield stream
53+
finally:
54+
stream.close()
55+
else:
56+
yield sys.stdout.buffer
57+
58+
59+
def hex_to_binary(input_hex_file: str) -> bytes:
60+
ih = IntelHex(input_hex_file)
61+
ih.padding = 0xff # Allows hashing with empty regions
62+
data = ih.tobinstr()
63+
return data
64+
65+
66+
def sign_with_ecdsa(
67+
private_key_file: Path, input_file: Path, output_file: Path | None = None
68+
) -> int:
69+
with open(private_key_file, 'r') as f:
70+
private_key = SigningKey.from_pem(f.read())
71+
with open(input_file, 'rb') as f:
72+
data = f.read()
3873
signature = private_key.sign(data, hashfunc=hashlib.sha256)
39-
args.outfile.write(signature)
74+
with open_stream(output_file) as stream:
75+
stream.write(signature)
76+
return 0
77+
78+
79+
def sign_with_ed25519(
80+
private_key_file: Path, input_file: Path, output_file: Path | None = None
81+
) -> int:
82+
with open(private_key_file, 'rb') as f:
83+
private_key: Ed25519PrivateKey = load_pem_private_key(f.read(), password=None) # type: ignore[assignment]
84+
if str(input_file).endswith('.hex'):
85+
data = hex_to_binary(str(input_file))
86+
else:
87+
with open(input_file, 'rb') as f:
88+
data = f.read()
89+
signature = private_key.sign(data)
90+
with open_stream(output_file) as stream:
91+
stream.write(signature)
92+
return 0
93+
94+
95+
def main(argv=None) -> int:
96+
args = parse_args(argv)
97+
if args.algorithm == 'ecdsa':
98+
return sign_with_ecdsa(args.private_key, args.infile, args.outfile)
99+
if args.algorithm == 'ed25519':
100+
return sign_with_ed25519(args.private_key, args.infile, args.outfile)
101+
return 1
102+
103+
104+
if __name__ == '__main__':
105+
sys.exit(main())

scripts/bootloader/hash.py

100644100755
Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,20 @@
44
#
55
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
66

7+
"""
8+
Hash content of a file.
9+
"""
710

11+
import argparse
812
import hashlib
913
import sys
10-
import argparse
11-
from intelhex import IntelHex
14+
15+
from intelhex import IntelHex # type: ignore[import-untyped]
16+
17+
HASH_FUNCTION_FACTORY = {
18+
'sha256': hashlib.sha256,
19+
'sha512': hashlib.sha512,
20+
}
1221

1322

1423
def parse_args():
@@ -17,20 +26,37 @@ def parse_args():
1726
formatter_class=argparse.RawDescriptionHelpFormatter,
1827
allow_abbrev=False)
1928

20-
parser.add_argument('--infile', '-i', '--in', '-in', required=True,
21-
help='Hash the contents of the specified file. If a *.hex file is given, the contents will '
22-
'first be converted to binary, with all non-specified area being set to 0xff. '
23-
'For all other file types, no conversion is done.')
24-
return parser.parse_args()
29+
parser.add_argument(
30+
'--infile', '-i', '--in', '-in', required=True,
31+
help='Hash the contents of the specified file. If a *.hex file is given, the contents will '
32+
'first be converted to binary, with all non-specified area being set to 0xff. '
33+
'For all other file types, no conversion is done.'
34+
)
35+
parser.add_argument(
36+
'--type', '-t', dest='hash_function', help='Hash function (default: %(default)s)',
37+
action='store', choices=HASH_FUNCTION_FACTORY.keys(), default='sha256'
38+
)
2539

40+
return parser.parse_args()
2641

27-
if __name__ == '__main__':
28-
args = parse_args()
2942

30-
if args.infile.endswith('.hex'):
31-
ih = IntelHex(args.infile)
43+
def generate_hash_digest(file: str, hash_function_name: str) -> bytes:
44+
if file.endswith('.hex'):
45+
ih = IntelHex(file)
3246
ih.padding = 0xff # Allows hashing with empty regions
3347
to_hash = ih.tobinstr()
3448
else:
35-
to_hash = open(args.infile, 'rb').read()
36-
sys.stdout.buffer.write(hashlib.sha256(to_hash).digest())
49+
to_hash = open(file, 'rb').read()
50+
51+
hash_function = HASH_FUNCTION_FACTORY[hash_function_name]
52+
return hash_function(to_hash).digest()
53+
54+
55+
def main():
56+
args = parse_args()
57+
sys.stdout.buffer.write(generate_hash_digest(args.infile, args.hash_function))
58+
return 0
59+
60+
61+
if __name__ == '__main__':
62+
sys.exit(main())

0 commit comments

Comments
 (0)