1# Copyright 2017 Linaro Limited
2# Copyright 2023 Arm Limited
3#
4# SPDX-License-Identifier: Apache-2.0
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18"""
19Cryptographic key management for imgtool.
20"""
21
22from cryptography.hazmat.backends import default_backend
23from cryptography.hazmat.primitives import serialization
24from cryptography.hazmat.primitives.asymmetric.rsa import (
25    RSAPrivateKey, RSAPublicKey)
26from cryptography.hazmat.primitives.asymmetric.ec import (
27    EllipticCurvePrivateKey, EllipticCurvePublicKey)
28from cryptography.hazmat.primitives.asymmetric.ed25519 import (
29    Ed25519PrivateKey, Ed25519PublicKey)
30from cryptography.hazmat.primitives.asymmetric.x25519 import (
31    X25519PrivateKey, X25519PublicKey)
32
33from .rsa import RSA, RSAPublic, RSAUsageError, RSA_KEY_SIZES
34from .ecdsa import (ECDSA256P1, ECDSA256P1Public,
35                    ECDSA384P1, ECDSA384P1Public, ECDSAUsageError)
36from .ed25519 import Ed25519, Ed25519Public, Ed25519UsageError
37from .x25519 import X25519, X25519Public, X25519UsageError
38
39
40class PasswordRequired(Exception):
41    """Raised to indicate that the key is password protected, but a
42    password was not specified."""
43    pass
44
45
46def load(path, passwd=None):
47    """Try loading a key from the given path.
48      Returns None if the password wasn't specified."""
49    with open(path, 'rb') as f:
50        raw_pem = f.read()
51    try:
52        pk = serialization.load_pem_private_key(
53                raw_pem,
54                password=passwd,
55                backend=default_backend())
56    # Unfortunately, the crypto library raises unhelpful exceptions,
57    # so we have to look at the text.
58    except TypeError as e:
59        msg = str(e)
60        if "private key is encrypted" in msg:
61            return None
62        raise e
63    except ValueError:
64        # This seems to happen if the key is a public key, let's try
65        # loading it as a public key.
66        pk = serialization.load_pem_public_key(
67                raw_pem,
68                backend=default_backend())
69
70    if isinstance(pk, RSAPrivateKey):
71        if pk.key_size not in RSA_KEY_SIZES:
72            raise Exception("Unsupported RSA key size: " + pk.key_size)
73        return RSA(pk)
74    elif isinstance(pk, RSAPublicKey):
75        if pk.key_size not in RSA_KEY_SIZES:
76            raise Exception("Unsupported RSA key size: " + pk.key_size)
77        return RSAPublic(pk)
78    elif isinstance(pk, EllipticCurvePrivateKey):
79        if pk.curve.name not in ('secp256r1', 'secp384r1'):
80            raise Exception("Unsupported EC curve: " + pk.curve.name)
81        if pk.key_size not in (256, 384):
82            raise Exception("Unsupported EC size: " + pk.key_size)
83        if pk.curve.name == 'secp256r1':
84            return ECDSA256P1(pk)
85        elif pk.curve.name == 'secp384r1':
86            return ECDSA384P1(pk)
87    elif isinstance(pk, EllipticCurvePublicKey):
88        if pk.curve.name not in ('secp256r1', 'secp384r1'):
89            raise Exception("Unsupported EC curve: " + pk.curve.name)
90        if pk.key_size not in (256, 384):
91            raise Exception("Unsupported EC size: " + pk.key_size)
92        if pk.curve.name == 'secp256r1':
93            return ECDSA256P1Public(pk)
94        elif pk.curve.name == 'secp384r1':
95            return ECDSA384P1Public(pk)
96    elif isinstance(pk, Ed25519PrivateKey):
97        return Ed25519(pk)
98    elif isinstance(pk, Ed25519PublicKey):
99        return Ed25519Public(pk)
100    elif isinstance(pk, X25519PrivateKey):
101        return X25519(pk)
102    elif isinstance(pk, X25519PublicKey):
103        return X25519Public(pk)
104    else:
105        raise Exception("Unknown key type: " + str(type(pk)))
106