Esempio n. 1
0
 def test_all_trusted_certificates_are_trusted(self):
     context = VerificationContext(trusted_certificate_store)
     for certificate in trusted_certificate_store:
         # Trust depends on the timestamp
         context.timestamp = certificate.valid_to
         chain = certificate.verify(context)
         self.assertListEqual(chain, [certificate])
Esempio n. 2
0
    def verify(self,
               context=None,
               trusted_certificate_store=TRUSTED_CERTIFICATE_STORE):
        """Verifies the RFC3161 SignedData object. The context that is passed in must account for the certificate
        store of this object, or be left None.
        """

        # Verify that the two digest algorithms are identical
        if self.digest_algorithm != self.hash_algorithm:
            raise AuthenticodeVerificationError(
                "SignedData.digestAlgorithm must equal "
                "TstInfo.messageImprint.hashAlgorithm")

        # We should ensure that the hash in the SignerInfo matches the hash of the content
        # This is similar to the normal verification process, where the SpcInfo is verified
        # Note that the mapping between the RFC3161 SignedData object is ensured by the verifier in SignedData
        blob_hash = self.digest_algorithm(
            bytes(self.data['encapContentInfo']['eContent'])).digest()
        if blob_hash != self.signer_info.message_digest:
            raise AuthenticodeVerificationError(
                'The expected hash of the TstInfo does not match SignerInfo')

        if context is None:
            context = VerificationContext(
                trusted_certificate_store,
                self.certificates,
                extended_key_usages=['time_stamping'])

        # The context is set correctly by the 'verify' function, including the current certificate store
        self.signer_info.verify(context)
Esempio n. 3
0
 def test_trust_fails(self):
     # we get a certificate we currently trust
     for certificate in trusted_certificate_store:
         # we add it to an untrusted store
         store = CertificateStore(trusted=False)
         store.append(certificate)
         # and verify using this store
         context = VerificationContext(store, timestamp=certificate.valid_to)
         self.assertRaises(VerificationError, certificate.verify, context)
Esempio n. 4
0
 def test_potential_chains(self):
     with open(
             str(root_dir / "test_data" /
                 "19e818d0da361c4feedd456fca63d68d4b024fbbd3d9265f606076c7ee72e8f8.ViR"
                 ), "rb") as f:
         pefile = SignedPEFile(f)
         for signed_data in pefile.signed_datas:
             context = VerificationContext(TRUSTED_CERTIFICATE_STORE,
                                           signed_data.certificates)
             potential_chains = list(
                 signed_data.signer_info.potential_chains(context))
             self.assertEqual(len(potential_chains), 2)
Esempio n. 5
0
    def test_revoked_certificate(self):
        root = FileSystemCertificateStore(root_dir / "certs" /
                                          'digicert-global-root-ca.pem',
                                          trusted=True)
        intermediate = FileSystemCertificateStore(
            root_dir / "certs" / 'digicert-sha2-secure-server-ca.pem')
        with open(str(root_dir / "certs" / 'revoked.badssl.com.pem'),
                  "rb") as f:
            cert = Certificate.from_pem(f.read())

        # check that when we do not verify the CRL it does not fail
        context = VerificationContext(root, intermediate)
        context.verify(cert)

        context = VerificationContext(root,
                                      intermediate,
                                      allow_fetching=True,
                                      revocation_mode='hard-fail')
        with self.assertRaises(VerificationError):
            context.verify(cert)
Esempio n. 6
0
    def verify(self,
               expected_hash=None,
               verification_context=None,
               cs_verification_context=None,
               verification_context_kwargs={}):
        """Verifies the SignedData structure:

        * Verifies that the digest algorithms match across the structure
        * Ensures that the hash in the :class:`SpcInfo` structure matches the expected hash. If no expected hash is
          provided to this function, it is calculated using the :class:`Fingerprinter` obtained from the
          :class:`SignedPEFile` object.
        * Verifies that the SpcInfo is signed by the :class:`SignerInfo`
        * In the case of a countersigner, verifies that the :class:`CounterSignerInfo` has the hashed encrypted digest
          of the :class:`SignerInfo`
        * Verifies the chain of the countersigner up to a trusted root
        * Verifies the chain of the signer up to a trusted root

        In the case of a countersigner, the verification is performed using the timestamp of the
        :class:`CounterSignerInfo`, otherwise now is assumed. If there is no countersigner, you can override this
        by specifying a different timestamp in the :class:`VerificationContext`

        :param bytes expected_hash: The expected hash digest of the :class:`SignedPEFile`.
        :param VerificationContext verification_context: The VerificationContext for verifying the chain of the
            :class:`SignerInfo`. The timestamp is overridden in the case of a countersigner. Default stores are
            TRUSTED_CERTIFICATE_STORE and the certificates of this :class:`SignedData` object. EKU is code_signing
        :param VerificationContext cs_verification_context: The VerificationContext for verifying the chain of the
            :class:`CounterSignerInfo`. The timestamp is overridden in the case of a countersigner. Default stores are
            TRUSTED_CERTIFICATE_STORE and the certificates of this :class:`SignedData` object. EKU is time_stamping
        :param dict verification_context_kwargs: If provided, keyword arguments that are passed to the instantiation of
            :class:`VerificationContext`s created in this function. Used for e.g. providing a timestamp.
        :raises AuthenticodeVerificationError: when the verification failed
        :return: :const:`None`
        """

        # Check that the digest algorithms match
        if self.digest_algorithm != self.spc_info.digest_algorithm:
            raise AuthenticodeVerificationError(
                "SignedData.digestAlgorithm must equal SpcInfo.digestAlgorithm"
            )

        if self.digest_algorithm != self.signer_info.digest_algorithm:
            raise AuthenticodeVerificationError(
                "SignedData.digestAlgorithm must equal SignerInfo.digestAlgorithm"
            )

        # Check that the hashes are correct
        # 1. The hash of the file
        if expected_hash is None:
            fingerprinter = self.pefile.get_fingerprinter()
            fingerprinter.add_authenticode_hashers(self.digest_algorithm)
            expected_hash = fingerprinter.hash()[self.digest_algorithm().name]

        if expected_hash != self.spc_info.digest:
            raise AuthenticodeVerificationError(
                "The expected hash does not match the digest in SpcInfo")

        # 2. The hash of the spc blob
        # According to RFC2315, 9.3, identifier (tag) and length need to be
        # stripped for hashing. We do this by having the parser just strip
        # out the SEQUENCE part of the spcIndirectData.
        # Alternatively this could be done by re-encoding and concatenating
        # the individual elements in spc_value, I _think_.
        _, hashable_spc_blob = ber_decoder.decode(
            self.data['contentInfo']['content'], recursiveFlag=0)
        spc_blob_hash = self.digest_algorithm(
            bytes(hashable_spc_blob)).digest()
        if spc_blob_hash != self.signer_info.message_digest:
            raise AuthenticodeVerificationError(
                'The expected hash of the SpcInfo does not match SignerInfo')

        # Can't check authAttr hash against encrypted hash, done implicitly in
        # M2's pubkey.verify.

        # 3. Check the countersigner hash.
        if self.signer_info.countersigner:
            # Make sure to use the same digest_algorithm that the countersigner used
            auth_attr_hash = self.signer_info.countersigner.digest_algorithm(
                self.signer_info.encrypted_digest).digest()
            if auth_attr_hash != self.signer_info.countersigner.message_digest:
                raise AuthenticodeVerificationError(
                    'The expected hash of the encryptedDigest does not match '
                    'countersigner\'s SignerInfo')

        if verification_context is None:
            verification_context = VerificationContext(
                TRUSTED_CERTIFICATE_STORE,
                self.certificates,
                extended_key_usages=['code_signing'],
                **verification_context_kwargs)

        if self.signer_info.countersigner:
            if cs_verification_context is None:
                cs_verification_context = VerificationContext(
                    TRUSTED_CERTIFICATE_STORE,
                    self.certificates,
                    extended_key_usages=['time_stamping'],
                    **verification_context_kwargs)
            cs_verification_context.timestamp = self.signer_info.countersigner.signing_time

            self.signer_info.countersigner.verify(cs_verification_context)

            # TODO: What to do when the verification fails? Check it as if the countersignature is not present?
            # Or fail all together? (Which is done now)
            verification_context.timestamp = self.signer_info.countersigner.signing_time

        self.signer_info.verify(verification_context)
Esempio n. 7
0
    def verify(self,
               expected_hash=None,
               verification_context=None,
               cs_verification_context=None,
               trusted_certificate_store=TRUSTED_CERTIFICATE_STORE,
               verification_context_kwargs={},
               allow_countersignature_errors=False):
        """Verifies the SignedData structure:

        * Verifies that the digest algorithms match across the structure
        * Ensures that the hash in the :class:`SpcInfo` structure matches the expected hash. If no expected hash is
          provided to this function, it is calculated using the :class:`Fingerprinter` obtained from the
          :class:`SignedPEFile` object.
        * Verifies that the SpcInfo is signed by the :class:`SignerInfo`
        * In the case of a countersigner, verifies that the :class:`CounterSignerInfo` has the hashed encrypted digest
          of the :class:`SignerInfo`
        * Verifies the chain of the countersigner up to a trusted root
        * Verifies the chain of the signer up to a trusted root

        In the case of a countersigner, the verification is performed using the timestamp of the
        :class:`CounterSignerInfo`, otherwise now is assumed. If there is no countersigner, you can override this
        by specifying a different timestamp in the :class:`VerificationContext`

        :param bytes expected_hash: The expected hash digest of the :class:`SignedPEFile`.
        :param VerificationContext verification_context: The VerificationContext for verifying the chain of the
            :class:`SignerInfo`. The timestamp is overridden in the case of a countersigner. Default stores are
            TRUSTED_CERTIFICATE_STORE and the certificates of this :class:`SignedData` object. EKU is code_signing
        :param VerificationContext cs_verification_context: The VerificationContext for verifying the chain of the
            :class:`CounterSignerInfo`. The timestamp is overridden in the case of a countersigner. Default stores are
            TRUSTED_CERTIFICATE_STORE and the certificates of this :class:`SignedData` object. EKU is time_stamping
        :param CertificateStore trusted_certificate_store: A :class:`CertificateStore` object that contains a list of
            trusted certificates to be used when :const:`None` is passed to either :param:`verification_context` or
            :param:`cs_verification_context` and a :class:`VerificationContext` is created.
        :param dict verification_context_kwargs: If provided, keyword arguments that are passed to the instantiation of
            :class:`VerificationContext`s created in this function. Used for e.g. providing a timestamp.
        :param bool allow_countersignature_errors: If this is set to True, errors in the countersignature cause the
            binary to be verified as if it was never countersigned
        :raises AuthenticodeVerificationError: when the verification failed
        :return: :const:`None`
        """

        if verification_context is None:
            verification_context = VerificationContext(
                trusted_certificate_store,
                self.certificates,
                extended_key_usages=['code_signing'],
                **verification_context_kwargs)

        if cs_verification_context is None and self.signer_info.countersigner:
            cs_verification_context = VerificationContext(
                trusted_certificate_store,
                self.certificates,
                extended_key_usages=['time_stamping'],
                **verification_context_kwargs)
            # Add the local certificate store for the countersignature (in the case of RFC3161SignedData)
            if hasattr(self.signer_info.countersigner, 'certificates'):
                cs_verification_context.add_store(
                    self.signer_info.countersigner.certificates)

        # Check that the digest algorithms match
        if self.digest_algorithm != self.spc_info.digest_algorithm:
            raise AuthenticodeVerificationError(
                "SignedData.digestAlgorithm must equal SpcInfo.digestAlgorithm"
            )

        if self.digest_algorithm != self.signer_info.digest_algorithm:
            raise AuthenticodeVerificationError(
                "SignedData.digestAlgorithm must equal SignerInfo.digestAlgorithm"
            )

        # Check that the hashes are correct
        # 1. The hash of the file
        if expected_hash is None:
            fingerprinter = self.pefile.get_fingerprinter()
            fingerprinter.add_authenticode_hashers(self.digest_algorithm)
            expected_hash = fingerprinter.hash()[self.digest_algorithm().name]

        if expected_hash != self.spc_info.digest:
            raise AuthenticodeVerificationError(
                "The expected hash does not match the digest in SpcInfo")

        # 2. The hash of the spc blob
        # According to RFC2315, 9.3, identifier (tag) and length need to be
        # stripped for hashing. We do this by having the parser just strip
        # out the SEQUENCE part of the spcIndirectData.
        # Alternatively this could be done by re-encoding and concatenating
        # the individual elements in spc_value, I _think_.
        _, hashable_spc_blob = ber_decoder.decode(
            self.data['contentInfo']['content'], recursiveFlag=0)
        spc_blob_hash = self.digest_algorithm(
            bytes(hashable_spc_blob)).digest()
        if spc_blob_hash != self.signer_info.message_digest:
            raise AuthenticodeVerificationError(
                'The expected hash of the SpcInfo does not match SignerInfo')

        # Can't check authAttr hash against encrypted hash, done implicitly in
        # M2's pubkey.verify.

        if self.signer_info.countersigner:
            try:
                # 3. Check the countersigner hash.
                # Make sure to use the same digest_algorithm that the countersigner used
                if not self.signer_info.countersigner.check_message_digest(
                        self.signer_info.encrypted_digest):
                    raise AuthenticodeVerificationError(
                        'The expected hash of the encryptedDigest does not match '
                        'countersigner\'s SignerInfo')

                cs_verification_context.timestamp = self.signer_info.countersigner.signing_time

                # We could be calling SignerInfo.verify or RFC3161SignedData.verify here, but those have identical
                # signatures. Note that RFC3161SignedData accepts a trusted_certificate_store argument, but we pass in
                # an explicit context anyway
                self.signer_info.countersigner.verify(cs_verification_context)
            except Exception:
                if allow_countersignature_errors:
                    pass
                else:
                    raise AuthenticodeVerificationError(
                        "An error occurred while validating the countersignature."
                    )
            else:
                # If no errors occur, we should be fine setting the timestamp to the countersignature's timestamp
                verification_context.timestamp = self.signer_info.countersigner.signing_time

        self.signer_info.verify(verification_context)