Skip to content

Commit a78288e

Browse files
committed
key: refactor SSlibKey.verify_signature
Signature verification for "securesystemslib keys" was previously implemented in 'rsa_keys', 'ecdsa_keys' and 'ed25519_keys' modules, which were called from `SSlibKey.verify_signature` via the legacy interface function `keys.verify_signature()`. This commit moves the entire implementation to SSlibKey, which will allow us (in a subsequent commit) to drastically decrease LOC count and drop 'nacl' optional dependency for ed25519 keys, in favour of 'pyca/cryptography', which we already use for all other sslib keys. An alternative design for this refactor used separate RSAKey, ECDSAKey and ED25510Key classes to replace SSlibKey, but that mostly added redundant boilerplate code. To the user it shouldn't matter, so let's do what makes sense from maintainer perspective. Signed-off-by: Lukas Puehringer <[email protected]>
1 parent 135567f commit a78288e

File tree

1 file changed

+106
-13
lines changed

1 file changed

+106
-13
lines changed

securesystemslib/signer/_key.py

Lines changed: 106 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,47 @@
11
"""Key interface and the default implementations"""
22
import logging
33
from abc import ABCMeta, abstractmethod
4-
from typing import Any, Dict, Optional, Tuple, Type
4+
from typing import Any, Dict, Optional, Tuple, Type, cast
55

6-
import securesystemslib.keys as sslib_keys
76
from securesystemslib import exceptions
7+
from securesystemslib._vendor.ed25519.ed25519 import (
8+
SignatureMismatch,
9+
checkvalid,
10+
)
811
from securesystemslib.signer._signature import Signature
912

13+
CRYPTO_IMPORT_ERROR = None
14+
try:
15+
from cryptography.exceptions import InvalidSignature
16+
from cryptography.hazmat.primitives.asymmetric.ec import (
17+
ECDSA,
18+
EllipticCurvePublicKey,
19+
)
20+
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
21+
Ed25519PublicKey,
22+
)
23+
from cryptography.hazmat.primitives.asymmetric.padding import (
24+
MGF1,
25+
PSS,
26+
PKCS1v15,
27+
)
28+
from cryptography.hazmat.primitives.asymmetric.rsa import (
29+
AsymmetricPadding,
30+
RSAPublicKey,
31+
)
32+
from cryptography.hazmat.primitives.asymmetric.types import PublicKeyTypes
33+
from cryptography.hazmat.primitives.hashes import (
34+
SHA224,
35+
SHA256,
36+
SHA384,
37+
SHA512,
38+
HashAlgorithm,
39+
)
40+
from cryptography.hazmat.primitives.serialization import load_pem_public_key
41+
except ImportError:
42+
CRYPTO_IMPORT_ERROR = "'pyca/cryptography' library required"
43+
44+
1045
logger = logging.getLogger(__name__)
1146

1247
# NOTE Key dispatch table is defined here so it's usable by Key,
@@ -180,22 +215,80 @@ def from_dict(cls, keyid: str, key_dict: Dict[str, Any]) -> "SSlibKey":
180215
def to_dict(self) -> Dict[str, Any]:
181216
return self._to_dict()
182217

218+
def _from_pem(self) -> "PublicKeyTypes":
219+
"""Helper to load public key instance from PEM-formatted keyval."""
220+
public_bytes = self.keyval["public"].encode("utf-8")
221+
return load_pem_public_key(public_bytes)
222+
223+
@staticmethod
224+
def _hash_algo(name) -> Type["HashAlgorithm"]:
225+
"""Helper to return hash algorithm class for name."""
226+
algos = {
227+
"sha224": SHA224,
228+
"sha256": SHA256,
229+
"sha384": SHA384,
230+
"sha512": SHA512,
231+
}
232+
return algos[name]
233+
183234
def verify_signature(self, signature: Signature, data: bytes) -> None:
184235
try:
185-
if not sslib_keys.verify_signature(
186-
self.to_securesystemslib_key(),
187-
signature.to_dict(),
188-
data,
236+
sig = bytes.fromhex(signature.signature)
237+
238+
if CRYPTO_IMPORT_ERROR:
239+
if self.scheme == "ed25519":
240+
# Verify using vendored ed25519 implementation
241+
pub = bytes.fromhex(self.keyval["public"])
242+
checkvalid(sig, data, pub)
243+
return
244+
245+
raise exceptions.UnsupportedLibraryError(CRYPTO_IMPORT_ERROR)
246+
247+
key: PublicKeyTypes
248+
if self.scheme in [
249+
"rsassa-pss-sha224",
250+
"rsassa-pss-sha256",
251+
"rsassa-pss-sha384",
252+
"rsassa-pss-sha512",
253+
"rsa-pkcs1v15-sha224",
254+
"rsa-pkcs1v15-sha256",
255+
"rsa-pkcs1v15-sha384",
256+
"rsa-pkcs1v15-sha512",
257+
]:
258+
key = cast(RSAPublicKey, self._from_pem())
259+
padding_name, algo_name = self.scheme.split("-")[1:]
260+
algo = self._hash_algo(algo_name)()
261+
padding: AsymmetricPadding
262+
if padding_name == "pss":
263+
padding = PSS(mgf=MGF1(algo), salt_length=PSS.AUTO)
264+
else:
265+
padding = PKCS1v15()
266+
key.verify(sig, data, padding, algo)
267+
268+
elif self.scheme in ["ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]:
269+
key = cast(EllipticCurvePublicKey, self._from_pem())
270+
algo_name = f"sha{self.scheme[-3:]}"
271+
algo = self._hash_algo(algo_name)()
272+
key.verify(sig, data, ECDSA(algo))
273+
274+
elif self.scheme in ["ed25519"]:
275+
public_bytes = bytes.fromhex(self.keyval["public"])
276+
key = Ed25519PublicKey.from_public_bytes(public_bytes)
277+
key.verify(sig, data)
278+
279+
else:
280+
raise ValueError(f"unknown scheme '{self.scheme}'")
281+
282+
# Workaround for 'except (SignatureMismatch, InvalidSignature)' to
283+
# conditionally evaluate the optional 'InvalidSignature':
284+
except Exception as e:
285+
if isinstance(e, SignatureMismatch) or (
286+
not CRYPTO_IMPORT_ERROR and isinstance(e, InvalidSignature)
189287
):
190288
raise exceptions.UnverifiedSignatureError(
191289
f"Failed to verify signature by {self.keyid}"
192-
)
193-
except (
194-
exceptions.CryptoError,
195-
exceptions.FormatError,
196-
exceptions.UnsupportedAlgorithmError,
197-
exceptions.UnsupportedLibraryError,
198-
) as e:
290+
) from e
291+
199292
logger.info("Key %s failed to verify sig: %s", self.keyid, str(e))
200293
raise exceptions.VerificationError(
201294
f"Unknown failure to verify signature by {self.keyid}"

0 commit comments

Comments
 (0)