Skip to content

Commit

Permalink
scripts: Replace ecdsa library with cryptography
Browse files Browse the repository at this point in the history
Simplify python scripts by removing ecdsa library and
rewriting code with cryptography library,
which provides same functionality.

Signed-off-by: Lukasz Fundakowski <[email protected]>
  • Loading branch information
fundakol committed Jan 8, 2025
1 parent b2298e7 commit 9711359
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 165 deletions.
32 changes: 21 additions & 11 deletions scripts/bootloader/do_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@

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

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
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]


Expand Down Expand Up @@ -66,11 +67,14 @@ def hex_to_binary(input_hex_file: str) -> bytes:
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(private_key_file, 'rb') as f:
private_key = load_pem_private_key(f.read(), password=None)
if not isinstance(private_key, EllipticCurvePrivateKey):
raise SystemExit(f'Private key file {private_key_file} is not Elliptic Curve key')

with open(input_file, 'rb') as f:
data = f.read()
signature = private_key.sign(data, hashfunc=hashlib.sha256)
signature = private_key.sign(data, ec.ECDSA(hashes.SHA256()))
with open_stream(output_file) as stream:
stream.write(signature)
return 0
Expand All @@ -80,7 +84,10 @@ 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]
private_key = load_pem_private_key(f.read(), password=None)
if not isinstance(private_key, Ed25519PrivateKey):
raise SystemExit(f'Private key file {private_key_file} is not Ed25519 key')

if str(input_file).endswith('.hex'):
data = hex_to_binary(str(input_file))
else:
Expand All @@ -92,13 +99,16 @@ def sign_with_ed25519(
return 0


ALGORITHMS = {
'ecdsa': sign_with_ecdsa,
'ed25519': sign_with_ed25519,
}


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
sign_function = ALGORITHMS[args.algorithm]
return sign_function(args.private_key, args.infile, args.outfile)


if __name__ == '__main__':
Expand Down
147 changes: 79 additions & 68 deletions scripts/bootloader/keygen.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@

from __future__ import annotations

import abc
import argparse
import sys
from hashlib import sha256, sha512
from typing import BinaryIO
from typing import BinaryIO, Type

from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, ed25519
from cryptography.hazmat.primitives.serialization import load_pem_private_key


Expand Down Expand Up @@ -62,44 +61,53 @@ def generate_legal_key_for_ed25519():
return key


class EllipticCurveKeysGenerator:
"""Generate private and public keys for Elliptic Curve cryptography."""
class KeysGeneratorBase(abc.ABC):

def __init__(self, infile: BinaryIO | None = None) -> None:
"""
:param infile: A file-like object to read the private key.
"""
if infile is None:
self.private_key = generate_legal_key_for_elliptic_curve()
self.private_key = self._generate_private_key()
else:
self.private_key = load_pem_private_key(infile.read(), password=None)
self.public_key = self.private_key.public_key()

@property
@abc.abstractmethod
def private_key_pem(self) -> bytes:
return self.private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
pass

@property
@abc.abstractmethod
def public_key_pem(self) -> bytes:
pass

@abc.abstractmethod
def _generate_private_key(self):
pass

@staticmethod
@abc.abstractmethod
def sign_message(private_key, message: bytes) -> bytes:
pass

@staticmethod
@abc.abstractmethod
def verify_signature(
public_key, message: bytes, signature: bytes
) -> bool:
pass

def write_private_key_pem(self, outfile: BinaryIO) -> bytes:
"""
Write private key pem to file and return it.
:param outfile: A file-like object to write the private key.
"""
if outfile is not None:
outfile.write(self.private_key_pem)
outfile.write(self.private_key_pem)
return self.private_key_pem

@property
def public_key_pem(self) -> bytes:
return self.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

def write_public_key_pem(self, outfile: BinaryIO) -> bytes:
"""
Write public key pem to file and return it.
Expand All @@ -109,31 +117,48 @@ def write_public_key_pem(self, outfile: BinaryIO) -> bytes:
outfile.write(self.public_key_pem)
return self.public_key_pem


class EllipticCurveKeysGenerator(KeysGeneratorBase):
"""Generate private and public keys for Elliptic Curve cryptography."""

def _generate_private_key(self):
return generate_legal_key_for_elliptic_curve()

@property
def private_key_pem(self) -> bytes:
return self.private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)

@property
def public_key_pem(self) -> bytes:
return self.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

@staticmethod
def verify_signature(public_key, message: bytes, signature: bytes) -> bool:
def verify_signature(
public_key: ec.EllipticCurvePublicKey, message: bytes, signature: bytes
) -> bool:
try:
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
return True
except InvalidSignature:
return False

@staticmethod
def sign_message(private_key, message: bytes) -> bytes:
def sign_message(private_key: ec.EllipticCurvePrivateKey, message: bytes) -> bytes:
return private_key.sign(message, ec.ECDSA(hashes.SHA256()))


class Ed25519KeysGenerator:
class Ed25519KeysGenerator(KeysGeneratorBase):
"""Generate private and public keys for ED25519 cryptography."""

def __init__(self, infile: BinaryIO | None = None) -> None:
"""
:param infile: A file-like object to read the private key.
"""
if infile is None:
self.private_key: ed25519.Ed25519PrivateKey = generate_legal_key_for_ed25519()
else:
self.private_key = load_pem_private_key(infile.read(), password=None) # type: ignore[assignment]
self.public_key: ed25519.Ed25519PublicKey = self.private_key.public_key()
def _generate_private_key(self):
return generate_legal_key_for_ed25519()

@property
def private_key_pem(self) -> bytes:
Expand All @@ -143,50 +168,40 @@ def private_key_pem(self) -> bytes:
encryption_algorithm=serialization.NoEncryption()
)

def write_private_key_pem(self, outfile: BinaryIO) -> bytes:
"""
Write private key pem to file and return it.
:param outfile: A file-like object to write the private key.
"""
outfile.write(self.private_key_pem)
return self.private_key_pem

@property
def public_key_pem(self) -> bytes:
return self.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)

def write_public_key_pem(self, outfile: BinaryIO) -> bytes:
"""
Write public key pem to file and return it.
:param outfile: A file-like object to write the public key.
"""
if outfile is not None:
outfile.write(self.public_key_pem)
return self.public_key_pem

@staticmethod
def verify_signature(public_key: ed25519.Ed25519PublicKey, message: bytes, signature: bytes) -> bool:
def verify_signature(
public_key: ed25519.Ed25519PublicKey, message: bytes, signature: bytes
) -> bool:
try:
public_key.verify(signature, message)
return True
except InvalidSignature:
return False

@staticmethod
def sign_message(private_key, message: bytes) -> bytes:
def sign_message(private_key: ed25519.Ed25519PrivateKey, message: bytes) -> bytes:
return private_key.sign(message)


ALGORITHMS: dict[str, Type[KeysGeneratorBase]] = {
"ed25519": Ed25519KeysGenerator,
"ec": EllipticCurveKeysGenerator,
}


def main(argv=None) -> int:
parser = argparse.ArgumentParser(
description='Generate PEM file.',
formatter_class=argparse.RawDescriptionHelpFormatter,
allow_abbrev=False)
allow_abbrev=False
)

priv_pub_group = parser.add_mutually_exclusive_group(required=True)
priv_pub_group.add_argument('--private', required=False, action='store_true',
Expand All @@ -207,19 +222,15 @@ def main(argv=None) -> int:

args = parser.parse_args(argv)

if args.algorithm == 'ed25519':
ed25519_generator = Ed25519KeysGenerator(args.infile)
if args.private:
ed25519_generator.write_private_key_pem(args.out)
if args.public:
ed25519_generator.write_public_key_pem(args.out)
else:
ec_generator = EllipticCurveKeysGenerator(args.infile)
if args.private:
ec_generator.write_private_key_pem(args.out)
elif args.public:
ec_generator.write_public_key_pem(args.out)
try:
generator = ALGORITHMS[args.algorithm](args.infile)
except KeyError:
sys.exit(f'Unknown algorithm {args.algorithm}.')

if args.private:
generator.write_private_key_pem(args.out)
if args.public:
generator.write_public_key_pem(args.out)
return 0


Expand Down
Loading

0 comments on commit 9711359

Please sign in to comment.