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