Source code for request.operation_processor.sign
"""Signature operation processor classes."""
from typing import get_args
from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
from trustpoint_core.crypto_types import AllowedCertSignHashAlgos
from trustpoint_core.oid import SignatureSuite
from pki.models import CredentialModel
from request.request_context import BaseRequestContext
from trustpoint.logger import LoggerMixin
from .base import AbstractOperationProcessor
[docs]
class GenericSigner(LoggerMixin):
"""Provides general signing functionality."""
@staticmethod
[docs]
def sign(data: bytes, signer_credential: CredentialModel) -> bytes:
"""Sign the provided data with the given signer credential."""
signature_suite = SignatureSuite.from_certificate(signer_credential.get_certificate())
private_key = signer_credential.get_private_key_serializer().as_crypto()
hash_algorithm_enum = signature_suite.algorithm_identifier.hash_algorithm
if hash_algorithm_enum is None:
err_msg = 'Failed to get hash algorithm.'
raise ValueError(err_msg)
hash_algorithm = hash_algorithm_enum.hash_algorithm()
if not isinstance(hash_algorithm, get_args(AllowedCertSignHashAlgos)):
err_msg = f'The hash algorithm must be one of {AllowedCertSignHashAlgos}, but found {type(hash_algorithm)}'
raise TypeError(err_msg)
if isinstance(private_key, rsa.RSAPrivateKey):
signature = private_key.sign(
data,
padding.PKCS1v15(),
hash_algorithm,
)
elif isinstance(private_key, ec.EllipticCurvePrivateKey):
signature = private_key.sign(
data,
ec.ECDSA(hash_algorithm),
)
else:
exc_msg = 'Cannot sign due to unsupported private key type.'
raise TypeError(exc_msg)
GenericSigner.logger.debug('Signed %d bytes of data using %s', len(data), signer_credential)
return signature
[docs]
class GenericSignatureVerifier:
"""Provides general signature verification functionality."""
@staticmethod
[docs]
def verify(data: bytes, signature: bytes, signer_certificate: x509.Certificate) -> None:
"""Verify the provided signature over the data using the signer's certificate."""
signature_suite = SignatureSuite.from_certificate(signer_certificate)
public_key = signer_certificate.public_key()
hash_algorithm_enum = signature_suite.algorithm_identifier.hash_algorithm
if hash_algorithm_enum is None:
err_msg = 'Failed to get hash algorithm.'
raise ValueError(err_msg)
hash_algorithm = hash_algorithm_enum.hash_algorithm()
if not isinstance(hash_algorithm, get_args(AllowedCertSignHashAlgos)):
err_msg = f'The hash algorithm must be one of {AllowedCertSignHashAlgos}, but found {type(hash_algorithm)}'
raise TypeError(err_msg)
if isinstance(public_key, rsa.RSAPublicKey):
public_key.verify(
signature=signature,
data=data,
padding=padding.PKCS1v15(),
algorithm=hash_algorithm,
)
elif isinstance(public_key, ec.EllipticCurvePublicKey):
public_key.verify(
signature=signature,
data=data,
signature_algorithm=ec.ECDSA(hash_algorithm),
)
else:
exc_msg = 'Cannot verify signature due to unsupported public key type.'
raise TypeError(exc_msg)
[docs]
class LocalCaCmpSignatureProcessor(LoggerMixin, AbstractOperationProcessor):
"""Operation processor for signing a CMP message via a local CA."""
_data: bytes | None = None
_signature: bytes | None = None
def __init__(self, message: bytes | None = None) -> None:
"""Initialize the processor with data to be signed."""
# Air: Consider adding data to be signed to RequestContext
self._data = message
[docs]
def process_operation(self, context: BaseRequestContext) -> None:
"""Sign the provided data using the local CA's private key."""
if self._data is None:
exc_msg = 'Data to be signed must be set in the processor.'
raise ValueError(exc_msg)
# if this is an AOKI request, the DevOwnerID is used to sign the CMP message
if context.owner_credential:
signer_credential = context.owner_credential
else:
if not context.issuer_credential:
if not context.domain:
exc_msg = 'Domain must be set in the context to sign data.'
raise ValueError(exc_msg)
ca = context.domain.get_issuing_ca_or_value_error()
context.issuer_credential = ca.get_credential()
if not context.issuer_credential:
exc_msg = 'Issuing CA does not have a credential'
raise ValueError(exc_msg)
signer_credential = context.issuer_credential
self._signature = GenericSigner.sign(self._data, signer_credential)
[docs]
def get_signature(self) -> bytes:
"""Get the generated signature."""
if self._signature is None:
exc_msg = 'Signature not generated. Call process_operation first.'
raise ValueError(exc_msg)
return self._signature