• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2021 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Facilities for keys generation, importing, signing and verification.
15
16IMPORTANT: THESE FACILITIES ARE FOR LOCAL NON-PRODUCTION USE ONLY!!
17
18These are not suited for production use because:
19
201. The private keys are not generated without ANY supervision or authorization.
212. The private keys are not stored securely.
223. The underlying crypto library is not audited.
23"""
24
25import argparse
26import hashlib
27from pathlib import Path
28
29from cryptography.hazmat.primitives import hashes
30from cryptography.hazmat.primitives.asymmetric import ec
31from cryptography.hazmat.primitives.asymmetric.utils import (
32    decode_dss_signature, encode_dss_signature)
33from cryptography.hazmat.primitives.serialization import (
34    Encoding, NoEncryption, PrivateFormat, PublicFormat, load_pem_private_key,
35    load_pem_public_key)
36
37from pw_software_update.tuf_pb2 import (Key, KeyMapping, KeyScheme, KeyType,
38                                        Signature)
39
40
41def parse_args():
42    """Parse CLI arguments."""
43    parser = argparse.ArgumentParser(description=__doc__)
44    parser.add_argument('-o',
45                        '--out',
46                        type=Path,
47                        required=True,
48                        help='Output path for the generated key')
49    return parser.parse_args()
50
51
52def gen_ecdsa_keypair(out: Path) -> None:
53    """Generates and writes to disk a NIST-P256 EC key pair.
54
55    Args:
56      out: The path to write the private key to. The public key is written
57        to the same path as the private key using the suffix '.pub'.
58    """
59    private_key = ec.generate_private_key(ec.SECP256R1())
60    public_key = private_key.public_key()
61    private_pem = private_key.private_bytes(
62        encoding=Encoding.PEM,
63        format=PrivateFormat.PKCS8,
64        encryption_algorithm=NoEncryption())
65    public_pem = public_key.public_bytes(
66        encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo)
67
68    out.write_bytes(private_pem)
69    public_out = (out.parent / f'{out.name}.pub')
70    public_out.write_bytes(public_pem)
71
72
73def gen_key_id(key: Key) -> bytes:
74    """Computes the key ID of a Key object."""
75    sha = hashlib.sha256()
76    sha.update(key.key_type.to_bytes(1, 'big'))
77    sha.update(key.scheme.to_bytes(1, 'big'))
78    sha.update(key.keyval)
79    return sha.digest()
80
81
82def import_ecdsa_public_key(pem: bytes) -> KeyMapping:
83    """Imports an EC NIST-P256 public key in pem format."""
84    ec_key = load_pem_public_key(pem)
85
86    if not isinstance(ec_key, ec.EllipticCurvePublicKey):
87        raise TypeError(
88            f'Not an elliptic curve public key type: {type(ec_key)}.'
89            'Try generate a key with gen_ecdsa_keypair()?')
90
91    # pylint: disable=no-member
92    if not (ec_key.curve.name == 'secp256r1' and ec_key.key_size == 256):
93        raise TypeError(f'Unsupported curve: {ec_key.curve.name}.'
94                        'Try generate a key with gen_ecdsa_keypair()?')
95    # pylint: enable=no-member
96
97    tuf_key = Key(key_type=KeyType.ECDSA_SHA2_NISTP256,
98                  scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
99                  keyval=ec_key.public_bytes(Encoding.X962,
100                                             PublicFormat.UncompressedPoint))
101    return KeyMapping(key_id=gen_key_id(tuf_key), key=tuf_key)
102
103
104def create_ecdsa_signature(data: bytes, key: bytes) -> Signature:
105    """Creates an ECDSA-SHA2-NISTP256 signature."""
106    ec_key = load_pem_private_key(key, password=None)
107    if not isinstance(ec_key, ec.EllipticCurvePrivateKey):
108        raise TypeError(f'Not an elliptic curve private key: {type(ec_key)}.'
109                        'Try generate a key with gen_ecdsa_keypair()?')
110
111    tuf_key = Key(key_type=KeyType.ECDSA_SHA2_NISTP256,
112                  scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
113                  keyval=ec_key.public_key().public_bytes(
114                      Encoding.X962, PublicFormat.UncompressedPoint))
115
116    der_signature = ec_key.sign(data, ec.ECDSA(hashes.SHA256()))  # pylint: disable=no-value-for-parameter
117    int_r, int_s = decode_dss_signature(der_signature)
118    sig_bytes = int_r.to_bytes(32, 'big') + int_s.to_bytes(32, 'big')
119
120    return Signature(key_id=gen_key_id(tuf_key), sig=sig_bytes)
121
122
123def verify_ecdsa_signature(sig: bytes, data: bytes, key: Key) -> bool:
124    """Verifies an ECDSA-SHA2-NISTP256 signature with a given public key.
125
126    Args:
127      sig: the ECDSA signature as raw bytes (r||s).
128      data: the message as plain text.
129      key: the ECDSA-NISTP256 public key.
130
131    Returns:
132      True if the signature is verified. False otherwise.
133    """
134    ec_key = ec.EllipticCurvePublicKey.from_encoded_point(
135        ec.SECP256R1(), key.keyval)
136    try:
137        dss_sig = encode_dss_signature(int.from_bytes(sig[:32], 'big'),
138                                       int.from_bytes(sig[-32:], 'big'))
139        ec_key.verify(dss_sig, data, ec.ECDSA(hashes.SHA256()))
140    except:  # pylint: disable=bare-except
141        return False
142
143    return True
144
145
146def main(out: Path) -> None:
147    """Generates and writes to disk key pairs for development use."""
148
149    # Currently only supports the "ecdsa-sha2-nistp256" key scheme.
150    #
151    # TODO(alizhang): Add support for "rsassa-pss-sha256" and "ed25519" key
152    # schemes.
153    gen_ecdsa_keypair(out)
154
155
156if __name__ == '__main__':
157    main(**vars(parse_args()))
158