Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scripts: Replace ecdsa library with cryptography #19788

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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