Skip to content

Commit

Permalink
key: refactor SSlibKey.verify_signature (WIP)
Browse files Browse the repository at this point in the history
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, drastically
decreasing LOC count and dropping 'nacl' optional dependency for ed25519
keys, in favour of 'pyca/cryptography', which we already use for all
other sslib keys.

An alternative design had RSAKey, ECDSAKey and ED25510Key classes, but
that mostly added redundant boilerplate code.  To the user it shouldn't
matter, so let's do what makes sense from maintainer perspective.

TODO:
- includes on mv-spx (secure-systems-lab#568)
- maybe polish _load_args()
  - using *args makes this hard to debug
  - schemes should not be hardcoded, but defined as sslib.signer-wide
    constants
  - args per scheme don't need to be hardcoded, but could be computed
    based on the scheme string

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
  • Loading branch information
lukpueh committed May 23, 2023
1 parent d5194eb commit 767c36e
Showing 1 changed file with 97 additions and 13 deletions.
110 changes: 97 additions & 13 deletions securesystemslib/signer/_key.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
"""Key interface and the default implementations"""
import logging
from abc import ABCMeta, abstractmethod
from typing import Any, Dict, Optional, Tuple, Type
from typing import Any, Dict, Optional, Tuple, Type, Union

import securesystemslib.keys as sslib_keys
from securesystemslib import exceptions
from securesystemslib.signer._signature import Signature

CRYPTO_IMPORT_ERROR = None
try:
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
from cryptography.hazmat.primitives.asymmetric.ec import (
ECDSA,
EllipticCurvePublicKey,
)
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PublicKey,
)
from cryptography.hazmat.primitives.asymmetric.padding import (
MGF1,
PSS,
PKCS1v15,
)
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from cryptography.hazmat.primitives.hashes import (
SHA224,
SHA256,
SHA384,
SHA512,
)
from cryptography.hazmat.primitives.serialization import load_pem_public_key
except ImportError:
CRYPTO_IMPORT_ERROR = "'pyca/cryptography' library required"


logger = logging.getLogger(__name__)

# NOTE Key dispatch table is defined here so it's usable by Key,
Expand Down Expand Up @@ -180,20 +206,78 @@ def from_dict(cls, keyid: str, key_dict: Dict[str, Any]) -> "SSlibKey":
def to_dict(self) -> Dict[str, Any]:
return self._to_dict()

def _load_key(
self,
) -> Union[RSAPublicKey, Ed25519PublicKey, EllipticCurvePublicKey]:
"""Helper to load public key instance based on keytype.
NOTE: raises ValueError, if self.keytype is not recognized.
"""
if self.keytype in [
"rsa",
"ecdsa",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
]:
public_bytes = self.keyval["public"].encode("utf-8")
return load_pem_public_key(public_bytes)

if self.keytype == "ed25519":
public_bytes = bytes.fromhex(self.keyval["public"])
return Ed25519PublicKey.from_public_bytes(public_bytes)

raise ValueError(f"unknown keytype '{self.keytype}'")

def _load_args(self) -> Tuple[Any]:
"""Helper to get additional verification arguments based on scheme.
NOTE: returns empty tuple, if scheme is not recognized.
"""
verify_args = {
"rsassa-pss-sha224": (
PSS(mgf=MGF1(SHA224()), salt_length=PSS.AUTO),
SHA224(),
),
"rsassa-pss-sha256": (
PSS(mgf=MGF1(SHA256()), salt_length=PSS.AUTO),
SHA256(),
),
"rsassa-pss-sha384": (
PSS(mgf=MGF1(SHA384()), salt_length=PSS.AUTO),
SHA384(),
),
"rsassa-pss-sha512": (
PSS(mgf=MGF1(SHA512()), salt_length=PSS.AUTO),
SHA512(),
),
"rsa-pkcs1v15-sha224": (PKCS1v15(), SHA224()),
"rsa-pkcs1v15-sha256": (PKCS1v15(), SHA256()),
"rsa-pkcs1v15-sha384": (PKCS1v15(), SHA384()),
"rsa-pkcs1v15-sha512": (PKCS1v15(), SHA512()),
"ecdsa-sha2-nistp256": (ECDSA(SHA256()),),
"ecdsa-sha2-nistp384": (ECDSA(SHA384()),),
}
return verify_args.get(self.scheme, ())

def verify_signature(self, signature: Signature, data: bytes) -> None:
try:
if not sslib_keys.verify_signature(
self.to_securesystemslib_key(),
signature.to_dict(),
data,
):
raise exceptions.UnverifiedSignatureError(
f"Failed to verify signature by {self.keyid}"
)
if CRYPTO_IMPORT_ERROR:
raise exceptions.UnsupportedLibraryError(CRYPTO_IMPORT_ERROR)

key = self._load_key()
args = self._load_args()

sig_bytes = bytes.fromhex(signature.signature)
key.verify(sig_bytes, data, *args)

except InvalidSignature as e:
raise exceptions.UnverifiedSignatureError(
f"Failed to verify signature by {self.keyid}"
) from e
except (
exceptions.CryptoError,
exceptions.FormatError,
exceptions.UnsupportedAlgorithmError,
ValueError,
UnsupportedAlgorithm,
KeyError,
exceptions.UnsupportedLibraryError,
) as e:
logger.info("Key %s failed to verify sig: %s", self.keyid, str(e))
Expand Down

0 comments on commit 767c36e

Please sign in to comment.