async def get_old_timestamp(signature, timestamp_url=None): """Retrieve an old style timestamp countersignature. Args: signature (str): the signature to get a counter signature for. This is usally the encryptedDigest of our file's signerInfo section. timestamp_url (str): what service to use to fetch the timestamp countersignature from. defaults to 'http://timestamp.digicert.com'. Returns: SignedData object """ req = OldTimeStampReq() req["type"] = univ.ObjectIdentifier("1.3.6.1.4.1.311.3.2.1") req["blob"]["signature"] = signature req["blob"]["type"] = univ.ObjectIdentifier("1.2.840.113549.1.7.1") encoded_req = der_encode(req) b64_req = base64.b64encode(encoded_req) url = timestamp_url or "http://timestamp.digicert.com" async with aiohttp.request( "POST", url, data=b64_req, headers={"Content-Type": "application/octet-stream"} ) as resp: # Uncomment below to capture a real response # open('old-ts.dat', 'wb').write(resp.content) ci, _ = der_decode(base64.b64decode(await resp.read()), ContentInfo()) ts, _ = der_decode(ci["content"], SignedData()) return ts
def verify_pefile_rfc3161_timestamp(f, pe): """Verifies that the timestamp in this PE file is valid.""" certificates = pe.certificates if not certificates: return True, "No certificates present" messages = [] x509_certs_by_serial = get_x509_certificates(pe) passed = True for pe_cert in certificates: content_info, _ = der_decode(pe_cert.data, ContentInfo()) signed_data, _ = der_decode(content_info["content"], SignedData()) for info in signed_data["signerInfos"]: # Check if there are any timestamps in unauthenticatedAttributes for a in info["unauthenticatedAttributes"]: if a["type"] == id_timestampSignature: # RFC3161 timestamps # Calculate the hash of our signature # TODO: don't hardcode the digest algorithm digest_algo = DIGEST_NAME_BY_OID[info["digestAlgorithm"] ["algorithm"]] signature_digest = hashlib.new( digest_algo, info["encryptedDigest"].asOctets()).digest() counter_sig = der_decode(a["values"][0], ContentInfo())[0] counter_sig = der_decode(counter_sig["content"], CMSSignedData())[0] tst_info = der_decode( counter_sig["encapContentInfo"]["eContent"], TSTInfo())[0] message_digest = tst_info["messageImprint"][ "hashedMessage"].asOctets() if message_digest != signature_digest: passed = False messages.append( f"counter signature is over the wrong data (hash: {hexlify(signature_digest)})" ) t_passed, message = verify_signed_data( counter_sig, x509_certs_by_serial) passed = passed and t_passed messages.append(message) return passed, "\n".join(messages)
async def make_authenticode_signeddata( cert, signer, authenticode_digest, digest_algo, timestamp=None, opus_info=None, opus_url=None, ): """Creates a SignedData object containing the signature for a PE file. Arguments: cert (X509): public certificate used for signing signer (function): signing function authenticode_digest (bytes): Authenticode digest of PE file to sign NB. This is not simply the hash of the file! digest_algo (str): digest algorithm to use. e.g. 'sha256' timestamp (UTCTime): optional. timestamp to include in the signature. If not provided, the current time is used. opus_info (string): Additional information to include in the signature opus_url (string): URL to include in the signature Returns: A ContentInfo ASN1 object """ if not timestamp: timestamp = useful.UTCTime.fromDateTime(datetime.now()) asn_digest_algo = ASN_DIGEST_ALGO_MAP[digest_algo] spc = make_spc(digest_algo, authenticode_digest) encoded_spc = der_encode(spc) pkcs7_cert = x509_to_pkcs7(cert) signer_info = make_signer_info(pkcs7_cert, digest_algo, timestamp, calc_spc_digest(encoded_spc, digest_algo)) signer_digest = calc_signerinfo_digest(signer_info, digest_algo) signer_info["encryptedDigest"] = await signer(signer_digest, digest_algo) sig = SignedData() sig["version"] = 1 sig["digestAlgorithms"][0] = asn_digest_algo sig["certificates"][0]["certificate"] = pkcs7_cert sig["contentInfo"]["contentType"] = id_spcIndirectDataContext sig["contentInfo"]["content"] = encoded_spc sig["signerInfos"][0] = signer_info ci = ContentInfo() ci["contentType"] = id_signedData ci["content"] = sig return ci
def get_signatures_from_certificates(certificates): """Retrieve the signatures from a list of certificates.""" retval = [] for cert in certificates: ci, _ = der_decode(cert["data"], ContentInfo()) signed_data, _ = der_decode(ci["content"], SignedData()) spc, _ = der_decode(signed_data["contentInfo"]["content"], SpcIndirectDataContent()) signed_data["contentInfo"]["content"] = spc retval.append(signed_data) return retval
def get_x509_certificates(pe): """Returns a mapping of (issuer, serial) to x509 certificates.""" certificates = pe.certificates x509_certs_by_serial = {} for pe_cert in certificates: content_info, _ = der_decode(pe_cert.data, ContentInfo()) signed_data, _ = der_decode(content_info["content"], SignedData()) for cert in signed_data["certificates"]: cert = der_encode(cert["certificate"]) x509_cert = x509.load_der_x509_certificate(cert, default_backend()) x509_certs_by_serial[x509_cert.issuer, x509_cert.serial_number] = x509_cert return x509_certs_by_serial
async def resign(old_sig, certs, signer): """Resigns an old signature with a new certificate. Replaces the encrypted signature digest in the given signature with new one generated with the given signer function. Args: old_sig (SignedData): the original signature as a SignedData object certs (list of x509 certificates): certificates to attach to the new signature signer (function): function to call to generate the new encrypted digest. The function is passed two arguments: (signer_digest, digest_algo) Returns: ContentInfo object with the new signature embedded """ new_sig = SignedData() new_sig["version"] = old_sig["version"] new_sig["contentInfo"] = old_sig["contentInfo"] new_sig["digestAlgorithms"] = old_sig["digestAlgorithms"] for i, cert in enumerate(certs): pkcs7_cert = x509_to_pkcs7(cert) new_sig["certificates"][i]["certificate"] = pkcs7_cert new_si = copy_signer_info(old_sig["signerInfos"][0], new_sig["certificates"][0]["certificate"]) if new_si["digestAlgorithm"]["algorithm"] == id_sha1: digest_algo = "sha1" elif new_si["digestAlgorithm"]["algorithm"] == id_sha256: digest_algo = "sha256" signer_digest = calc_signerinfo_digest(new_si, digest_algo) log.debug("Digest to sign is: %s", hexlify(signer_digest)) new_si["encryptedDigest"] = await signer(signer_digest, digest_algo) new_sig["signerInfos"][0] = new_si ci = ContentInfo() ci["contentType"] = id_signedData ci["content"] = new_sig sig = der_encode(ci) return sig
def verify_pefile_signature(f, pe): """Verifies that the signature in this PE file is valid.""" # TODO: Check that the message being signed refers to something. # e.g. the authenticatedAttributes' digest is our hash certificates = pe.certificates if not certificates: return True, "No certificates present" messages = [] x509_certs_by_serial = get_x509_certificates(pe) passed = True for pe_cert in certificates: content_info, _ = der_decode(pe_cert.data, ContentInfo()) signed_data, _ = der_decode(content_info["content"], SignedData()) spc = der_decode(signed_data["contentInfo"]["content"], SpcIndirectDataContent())[0] digest_algo_oid = spc["messageDigest"]["digestAlgorithm"]["algorithm"] digest_algo = DIGEST_NAME_BY_OID[digest_algo_oid] a_digest = calc_authenticode_digest(f, digest_algo) e_spc = der_encode(make_spc(digest_algo, a_digest)) spc_digest = calc_spc_digest(e_spc, digest_algo) for info in signed_data["signerInfos"]: # Check that the signature is on the right hash info_digest = der_decode( get_attribute(info["authenticatedAttributes"], id_messageDigest)[0])[0].asOctets() if info_digest != spc_digest: passed = False messages.append( f"Wrong digest: {hexlify(info_digest)} != {hexlify(spc_digest)}" ) continue t_passed, message = verify_signer_info(info, x509_certs_by_serial) passed = passed and t_passed messages.append(message) return passed, "\n".join(messages)
def verify_pefile_old_timestamp(f, pe): """Verifies that the timestamp in this PE file is valid.""" certificates = pe.certificates if not certificates: return True, "No certificates present" messages = [] x509_certs_by_serial = get_x509_certificates(pe) passed = True for pe_cert in certificates: content_info, _ = der_decode(pe_cert.data, ContentInfo()) signed_data, _ = der_decode(content_info["content"], SignedData()) for info in signed_data["signerInfos"]: # Check if there are any timestamps in unauthenticatedAttributes for a in info["unauthenticatedAttributes"]: if a["type"] == id_counterSignature: # Old timestamps # Calculate the hash of our signature # These timestamps are always sha1 signature_digest = hashlib.new( "sha1", info["encryptedDigest"].asOctets()).digest() counter_sig = der_decode(a["values"][0], SignerInfo())[0] counter_sig_digest = der_decode( get_attribute(counter_sig["authenticatedAttributes"], id_messageDigest)[0])[0].asOctets() # Check that the counter signature is of the right data if counter_sig_digest != signature_digest: passed = False messages.append( f"counter signature is over the wrong data (hash: {hexlify(signature_digest)})" ) # Check that the timestamp signature itself is valid t_passed, message = verify_signer_info( counter_sig, x509_certs_by_serial) passed = passed and t_passed messages.append(message) return passed, "\n".join(messages)
def verify_pefile_digest(f, pe): """Verifies that the authenticode digest in this PE file is valid.""" certificates = pe.certificates if not certificates: return True, "No certificates present" for pe_cert in certificates: content_info, _ = der_decode(pe_cert.data, ContentInfo()) signed_data, _ = der_decode(content_info["content"], SignedData()) spc = der_decode(signed_data["contentInfo"]["content"], SpcIndirectDataContent())[0] file_digest = spc["messageDigest"]["digest"].asOctets() digest_algo_oid = spc["messageDigest"]["digestAlgorithm"]["algorithm"] a_digest = calc_authenticode_digest( f, DIGEST_NAME_BY_OID[digest_algo_oid]) if file_digest != a_digest: return ( False, f"authenticode digests don't match: {hexlify(file_digest)} != {hexlify(a_digest)}", ) return True, "authenticode digests match"
def get_signeddata(s): """Gets the SignedData from an encoded ContentInfo object.""" ci = der_decode(s, ContentInfo())[0] sd = der_decode(ci["content"], SignedData())[0] return sd