Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | #------------------------------------------------------------------------------- |
| 3 | # SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors |
| 4 | # |
| 5 | # SPDX-License-Identifier: BSD-3-Clause |
| 6 | # |
| 7 | #------------------------------------------------------------------------------- |
| 8 | |
| 9 | from cryptography.hazmat.primitives.asymmetric import utils, ec |
| 10 | from cryptography.hazmat.primitives import hashes |
| 11 | from cryptography.hazmat.primitives.serialization import load_pem_private_key, Encoding, PublicFormat |
| 12 | |
| 13 | import pyhsslms |
| 14 | |
| 15 | from cryptography.hazmat.primitives.ciphers.aead import AESCCM |
| 16 | import secrets |
| 17 | |
| 18 | import argparse |
| 19 | import logging |
Raef Coles | cfc3124 | 2025-04-04 09:38:47 +0100 | [diff] [blame^] | 20 | logger = logging.getLogger("TF-M.{}".format(__name__)) |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 21 | from arg_utils import * |
| 22 | |
| 23 | def _asn1_sig_to_raw(sig : bytes , curve : ec.EllipticCurve) -> bytes: |
| 24 | point_size = curve.key_size // 8 |
| 25 | r, s = utils.decode_dss_signature(sig) |
| 26 | return bytes(0).join([x.to_bytes(point_size, byteorder="big") for x in [r, s]]) |
| 27 | |
| 28 | def _pubkey_ecdsa(key : str) -> bytes: |
| 29 | with open(key, "rb") as f: |
| 30 | key_data = f.read() |
| 31 | |
| 32 | priv_key = load_pem_private_key(key_data, password=None) |
| 33 | return priv_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo) |
| 34 | |
| 35 | def _pubkey_lms(key : str) -> bytes: |
| 36 | return pyhsslms.HssLmsPublicKey(key[:-4]).hss_pub.pub.serialize() |
| 37 | |
| 38 | pubkey_algs = { |
| 39 | "ECDSA": _pubkey_ecdsa, |
| 40 | "LMS": _pubkey_lms, |
| 41 | } |
| 42 | |
| 43 | def _sign_ecdsa(data : bytes, |
| 44 | key : str, |
| 45 | curve : ec.EllipticCurve = None, |
| 46 | hash_alg : hashes.HashAlgorithm = None, |
Raef Coles | 84ca7af | 2024-12-20 13:34:08 +0000 | [diff] [blame] | 47 | dsr_output_file = None, |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 48 | **kwargs : {}, |
| 49 | ) -> bytes: |
| 50 | with open(key, "rb") as f: |
| 51 | key_data = f.read() |
| 52 | |
| 53 | priv_key = load_pem_private_key(key_data, password=None) |
| 54 | |
| 55 | hash_alg_defaults = { |
| 56 | ec.SECP256R1: hashes.SHA256, |
| 57 | ec.SECP384R1: hashes.SHA384, |
| 58 | } |
| 59 | |
| 60 | if curve: |
| 61 | assert curve.name == priv_key.curve.name, "Key curve {} does not match required curve {}".format(priv_key.curve.name, curve.name) |
| 62 | |
| 63 | if not hash_alg: |
| 64 | hash_alg = hash_alg_defaults[type(priv_key.curve)] |
| 65 | |
| 66 | logger.info("Signing with ECDSA key {} (curve {}) and hash {}".format(key, priv_key.curve.name, hash_alg.name)) |
| 67 | |
| 68 | digest = hashes.Hash(hash_alg()) |
| 69 | digest.update(data) |
Raef Coles | 84ca7af | 2024-12-20 13:34:08 +0000 | [diff] [blame] | 70 | data_hash = digest.finalize() |
| 71 | logger.info("Signing hash {}".format(data_hash.hex())) |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 72 | |
Raef Coles | 84ca7af | 2024-12-20 13:34:08 +0000 | [diff] [blame] | 73 | if dsr_output_file: |
| 74 | dsr_output_file.write(data_hash) |
| 75 | exit(0) |
| 76 | |
| 77 | asn1_sig = priv_key.sign(data_hash, ec.ECDSA(utils.Prehashed(hash_alg()))) |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 78 | |
| 79 | return _asn1_sig_to_raw(asn1_sig, priv_key.curve) |
| 80 | |
| 81 | def _sign_lms(data : bytes, |
| 82 | key : str, |
Raef Coles | 84ca7af | 2024-12-20 13:34:08 +0000 | [diff] [blame] | 83 | dsr_output_file = None, |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 84 | **kwargs : {}, |
| 85 | ) -> bytes: |
Raef Coles | 84ca7af | 2024-12-20 13:34:08 +0000 | [diff] [blame] | 86 | if dsr_output_file: |
| 87 | dsr_output_file.write(data) |
| 88 | exit(0) |
| 89 | |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 90 | priv_key = pyhsslms.HssLmsPrivateKey(key[:-4]) |
| 91 | logger.info("Signing with LMS key {}".format(key)) |
| 92 | return priv_key.sign(data)[4:] |
| 93 | |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 94 | def sign_data(data : bytes, |
| 95 | sign_key : str, |
| 96 | sign_alg : str, |
| 97 | sign_hash_alg : str = None, |
Raef Coles | 84ca7af | 2024-12-20 13:34:08 +0000 | [diff] [blame] | 98 | signature : bytes = None, |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 99 | **kwargs, |
| 100 | ) -> bytes: |
Raef Coles | 84ca7af | 2024-12-20 13:34:08 +0000 | [diff] [blame] | 101 | if not signature: |
| 102 | assert(sign_key) |
| 103 | assert(sign_alg) |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 104 | |
Raef Coles | 84ca7af | 2024-12-20 13:34:08 +0000 | [diff] [blame] | 105 | signature = sign_algs[sign_alg](data = data, |
| 106 | key = sign_key, |
| 107 | hash_alg = sign_hash_alg, |
| 108 | **kwargs) |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 109 | |
Raef Coles | 84ca7af | 2024-12-20 13:34:08 +0000 | [diff] [blame] | 110 | return signature |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 111 | |
| 112 | def get_pubkey(sign_key : str, |
| 113 | sign_alg : str, |
| 114 | **kwargs |
| 115 | ) -> bytes: |
| 116 | return pubkey_algs[sign_alg](sign_key) |
| 117 | |
| 118 | sign_algs = { |
| 119 | "ECDSA": _sign_ecdsa, |
| 120 | "LMS": _sign_lms, |
| 121 | } |
| 122 | |
| 123 | def add_arguments(parser : argparse.ArgumentParser, |
| 124 | prefix : str = "", |
| 125 | required : bool = True, |
| 126 | ) -> None: |
| 127 | add_prefixed_argument(parser, "sign_key", prefix, help="signing key input file", |
| 128 | type=str, required=required) |
| 129 | add_prefixed_argument(parser, "sign_alg", prefix, help="signing algorithm", |
| 130 | choices=sign_algs.keys(), required=required) |
| 131 | add_prefixed_argument(parser, "sign_hash_alg", prefix, help="signing hash algorithm", |
| 132 | type=arg_type_hash, required=False) |
Raef Coles | 84ca7af | 2024-12-20 13:34:08 +0000 | [diff] [blame] | 133 | add_prefixed_argument(parser, "dsr_output_file", prefix, help="path to output Data Signing Request to", |
| 134 | type=arg_type_bytes_output_file, required=False) |
| 135 | add_prefixed_argument(parser, "signature", prefix, help="signature (if already signed)", |
| 136 | type=arg_type_bytes, required=False) |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 137 | |
| 138 | def parse_args(args : argparse.Namespace, |
| 139 | prefix : str = "", |
| 140 | ) -> dict: |
| 141 | out = parse_args_automatically(args, ["sign_key", "sign_alg", "sign_hash_alg"], prefix) |
| 142 | return out |
| 143 | |
| 144 | script_description = """ |
| 145 | Sign some data. |
| 146 | """ |
| 147 | if __name__ == "__main__": |
| 148 | import argparse |
| 149 | |
| 150 | parser = argparse.ArgumentParser(allow_abbrev=False, |
| 151 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, |
| 152 | description=script_description) |
| 153 | parser.add_argument("--log_level", help="log level", required=False, default="ERROR", choices=logging._levelToName.values()) |
| 154 | parser.add_argument("--data", help="data to sign", type=arg_type_bytes, required=True) |
| 155 | |
| 156 | add_arguments(parser, required=True) |
| 157 | |
| 158 | args = parser.parse_args() |
Raef Coles | cfc3124 | 2025-04-04 09:38:47 +0100 | [diff] [blame^] | 159 | logging.getLogger("TF-M").setLevel(args.log_level) |
| 160 | logger.addHandler(logging.StreamHandler()) |
Raef Coles | 59cf5d8 | 2024-12-09 15:41:13 +0000 | [diff] [blame] | 161 | |
| 162 | config = parse_args(args) |
| 163 | config |= parse_args_automatically(args, ["data"]) |
| 164 | |
| 165 | print(sign_data(**config).hex()) |