Ejemplo n.º 1
0
    def compute_digest(self) -> bytes:
        """
        Compute the ``/ByteRange`` digest of this signature.
        The result will be cached.

        :return:
            The digest value.
        """
        if self.external_digest is not None:
            return self.external_digest

        md_spec = get_pyca_cryptography_hash(self.external_md_algorithm)
        md = hashes.Hash(md_spec)
        stream = self.reader.stream

        # compute the digest
        # here, we allow arbitrary byte ranges
        # for the coverage check, we'll impose more constraints
        total_len = 0
        chunk_buf = bytearray(DEFAULT_CHUNK_SIZE)
        for lo, chunk_len in misc.pair_iter(self.byte_range):
            stream.seek(lo)
            misc.chunked_digest(chunk_buf, stream, md, max_read=chunk_len)
            total_len += chunk_len

        self.total_len = total_len
        self.external_digest = digest = md.finalize()
        return digest
Ejemplo n.º 2
0
def compute_signature_tst_digest(signer_info: cms.SignerInfo) \
        -> Optional[bytes]:
    """
    Compute the digest of the signature according to the message imprint
    algorithm information in a signature timestamp token.

    Internal API.

    :param signer_info:
        A ``SignerInfo`` value.
    :return:
        The computed digest, or ``None`` if there is no signature timestamp.
    """

    tst_data = extract_tst_data(signer_info)
    if tst_data is None:
        return None

    eci = tst_data['encap_content_info']
    mi = eci['content'].parsed['message_imprint']
    tst_md_algorithm = mi['hash_algorithm']['algorithm'].native

    signature_bytes = signer_info['signature'].native
    tst_md_spec = get_pyca_cryptography_hash(tst_md_algorithm)
    md = hashes.Hash(tst_md_spec)
    md.update(signature_bytes)
    return md.finalize()
Ejemplo n.º 3
0
def _hash_fully(digest_algorithm):
    md_spec = get_pyca_cryptography_hash(digest_algorithm)

    def _h(data: bytes):
        h = hashes.Hash(md_spec)
        h.update(data)
        return h.finalize()

    return _h
Ejemplo n.º 4
0
    def sign_raw(self, data: bytes, digest_algorithm: str,
                 dry_run=False) -> bytes:
        from cryptography.hazmat.primitives import serialization
        priv_key = serialization.load_der_private_key(
            self.signing_key.dump(), password=None
        )

        from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
        padding = PKCS1v15()
        hash_algo = get_pyca_cryptography_hash(digest_algorithm)
        return priv_key.sign(data, padding, hash_algo)
Ejemplo n.º 5
0
def base64_digest(data: bytes, digest_algorithm: str) -> str:
    """
    Digest some bytes and base64-encode the result.

    :param data:
        Data to digest.
    :param digest_algorithm:
        Name of the digest algorihtm to use.
    :return:
        A base64-encoded hash.
    """

    hash_spec = get_pyca_cryptography_hash(digest_algorithm)
    md = hashes.Hash(hash_spec)
    md.update(data)
    return base64.b64encode(md.finalize()).decode('ascii')
Ejemplo n.º 6
0
async def async_validate_detached_cms(
        input_data: Union[bytes, IO, cms.ContentInfo,
                          cms.EncapsulatedContentInfo],
        signed_data: cms.SignedData,
        signer_validation_context: ValidationContext = None,
        ts_validation_context: ValidationContext = None,
        ac_validation_context: ValidationContext = None,
        key_usage_settings: KeyUsageConstraints = None,
        chunk_size=misc.DEFAULT_CHUNK_SIZE,
        max_read=None) -> StandardCMSSignatureStatus:
    """
    .. versionadded: 0.9.0

    .. versionchanged: 0.11.0
        Added ``ac_validation_context`` param.

    Validate a detached CMS signature.

    :param input_data:
        The input data to sign. This can be either a :class:`bytes` object,
        a file-like object or a :class:`cms.ContentInfo` /
        :class:`cms.EncapsulatedContentInfo` object.

        If a CMS content info object is passed in, the `content` field
        will be extracted.
    :param signed_data:
        The :class:`cms.SignedData` object containing the signature to verify.
    :param signer_validation_context:
        Validation context to use to verify the signer certificate's trust.
    :param ts_validation_context:
        Validation context to use to verify the TSA certificate's trust, if
        a timestamp token is present.
        By default, the same validation context as that of the signer is used.
    :param ac_validation_context:
        Validation context to use to validate attribute certificates.
        If not supplied, no AC validation will be performed.

        .. note::
            :rfc:`5755` requires attribute authority trust roots to be specified
            explicitly; hence why there's no default.
    :param key_usage_settings:
        Key usage parameters for the signer.
    :param chunk_size:
        Chunk size to use when consuming input data.
    :param max_read:
        Maximal number of bytes to read from the input stream.
    :return:
        A description of the signature's status.
    """

    if ts_validation_context is None:
        ts_validation_context = signer_validation_context
    signer_info = extract_signer_info(signed_data)
    digest_algorithm = signer_info['digest_algorithm']['algorithm'].native
    h = hashes.Hash(get_pyca_cryptography_hash(digest_algorithm))
    if isinstance(input_data, bytes):
        h.update(input_data)
    elif isinstance(input_data,
                    (cms.ContentInfo, cms.EncapsulatedContentInfo)):
        h.update(bytes(input_data['content']))
    else:
        temp_buf = bytearray(chunk_size)
        misc.chunked_digest(temp_buf, input_data, h, max_read=max_read)
    digest_bytes = h.finalize()

    status_kwargs = await collect_timing_info(
        signer_info,
        ts_validation_context=ts_validation_context,
        raw_digest=digest_bytes)
    status_kwargs = await cms_basic_validation(
        signed_data,
        status_cls=StandardCMSSignatureStatus,
        raw_digest=digest_bytes,
        validation_context=signer_validation_context,
        status_kwargs=status_kwargs,
        key_usage_settings=key_usage_settings)
    cert_info = extract_certificate_info(signed_data)
    if ac_validation_context is not None:
        ac_validation_context.certificate_registry.register_multiple(
            cert_info.other_certs)
    status_kwargs.update(await collect_signer_attr_status(
        sd_attr_certificates=cert_info.attribute_certs,
        signer_cert=cert_info.signer_cert,
        validation_context=ac_validation_context,
        sd_signed_attrs=signer_info['signed_attrs']))
    return StandardCMSSignatureStatus(**status_kwargs)
Ejemplo n.º 7
0
async def cms_basic_validation(signed_data: cms.SignedData,
                               status_cls: Type[StatusType] = SignatureStatus,
                               raw_digest: bytes = None,
                               validation_context: ValidationContext = None,
                               status_kwargs: dict = None,
                               key_usage_settings: KeyUsageConstraints = None,
                               encap_data_invalid=False):
    """
    Perform basic validation of CMS and PKCS#7 signatures in isolation
    (i.e. integrity and trust checks).

    Internal API.
    """
    signer_info = extract_signer_info(signed_data)
    cert_info = extract_certs_for_validation(signed_data)
    cert = cert_info.signer_cert
    other_certs = cert_info.other_certs

    weak_hash_algos = None
    if validation_context is not None:
        weak_hash_algos = validation_context.weak_hash_algos
    if weak_hash_algos is None:
        weak_hash_algos = DEFAULT_WEAK_HASH_ALGORITHMS

    signature_algorithm: cms.SignedDigestAlgorithm = \
        signer_info['signature_algorithm']
    mechanism = signature_algorithm['algorithm'].native
    md_algorithm = signer_info['digest_algorithm']['algorithm'].native
    eci = signed_data['encap_content_info']
    expected_content_type = eci['content_type'].native
    if raw_digest is None:
        # this means that there should be encapsulated data
        raw = bytes(eci['content'])
        md_spec = get_pyca_cryptography_hash(md_algorithm)
        md = hashes.Hash(md_spec)
        md.update(raw)
        raw_digest = md.finalize()

    # first, do the cryptographic identity checks
    intact, valid = validate_sig_integrity(
        signer_info,
        cert,
        expected_content_type=expected_content_type,
        actual_digest=raw_digest,
        weak_hash_algorithms=weak_hash_algos)

    # if the data being encapsulated by the signature is itself invalid,
    #  this flag is set
    intact &= not encap_data_invalid
    valid &= intact

    # next, validate trust
    ades_status = path = None
    if valid:
        validator = CertificateValidator(cert,
                                         intermediate_certs=other_certs,
                                         validation_context=validation_context)
        ades_status, path = await status_cls.validate_cert_usage(
            validator, key_usage_settings=key_usage_settings)

    status_kwargs = status_kwargs or {}
    status_kwargs.update(intact=intact,
                         valid=valid,
                         signing_cert=cert,
                         md_algorithm=md_algorithm,
                         pkcs7_signature_mechanism=mechanism,
                         trust_problem_indic=ades_status,
                         validation_path=path)
    return status_kwargs
Ejemplo n.º 8
0
    def fill(self,
             writer: BasePdfFileWriter,
             md_algorithm,
             in_place=False,
             output=None,
             chunk_size=misc.DEFAULT_CHUNK_SIZE):
        """
        Generator coroutine that handles the document hash computation and
        the actual filling of the placeholder data.

        .. danger::
            This is internal API; you should use use :class:`.PdfSigner`
            wherever possible. If you *really* need fine-grained control,
            use :class:`~pyhanko.sign.signers.cms_embedder.PdfCMSEmbedder`
            instead.
        """

        if in_place:
            if not isinstance(writer, IncrementalPdfFileWriter):
                raise TypeError(
                    "in_place is only meaningful for incremental writers."
                )  # pragma: nocover
            output = writer.prev.stream
            writer.write_in_place()
        else:
            output = misc.prepare_rw_output_stream(output)

            writer.write(output)

        # retcon time: write the proper values of the /ByteRange entry
        #  in the signature object
        eof = output.tell()
        sig_start, sig_end = self.contents.offsets
        self.byte_range.fill_offsets(output, sig_start, sig_end, eof)

        # compute the digests
        md_spec = get_pyca_cryptography_hash(md_algorithm)
        md = hashes.Hash(md_spec)

        # attempt to get a memoryview for automatic buffering
        output_buffer = None
        if isinstance(output, BytesIO):
            output_buffer = output.getbuffer()
        else:
            try:
                output_buffer = memoryview(output)
            except (TypeError, IOError):
                pass

        if output_buffer is not None:
            # these are memoryviews, so slices should not copy stuff around
            #   (also, the interface files for pyca/cryptography don't specify
            #    that memoryviews are allowed, but they are)
            # noinspection PyTypeChecker
            md.update(output_buffer[:sig_start])
            # noinspection PyTypeChecker
            md.update(output_buffer[sig_end:eof])
            output_buffer.release()
        else:
            temp_buffer = bytearray(chunk_size)
            output.seek(0)
            misc.chunked_digest(temp_buffer, output, md, max_read=sig_start)
            output.seek(sig_end)
            misc.chunked_digest(temp_buffer,
                                output,
                                md,
                                max_read=eof - sig_end)

        digest_value = md.finalize()
        prepared_br_digest = PreparedByteRangeDigest(
            document_digest=digest_value,
            md_algorithm=md_algorithm,
            reserved_region_start=sig_start,
            reserved_region_end=sig_end)
        cms_data = yield prepared_br_digest, output
        yield prepared_br_digest.fill_with_cms(output, cms_data)