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])
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)
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)
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)
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)
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)
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)