def load_key(key_str: bytes, key_pass: str): """Function to load password protected key file in p12 or pem format.""" try: # First try to parse as a p12 file key, cert, _ = asymmetric.load_pkcs12(key_str, key_pass) except ValueError as e: # If it fails due to invalid password raise error here if e.args[0] == "Password provided is invalid": raise AS2Exception("Password not valid for Private Key.") # if not try to parse as a pem file key, cert = None, None for kc in split_pem(key_str): try: cert = asymmetric.load_certificate(kc) except (ValueError, TypeError): try: key = asymmetric.load_private_key(kc, key_pass) except OSError: raise AS2Exception( "Invalid Private Key or password is not correct.") if not key or not cert: raise AS2Exception( "Invalid Private key file or Public key not included.") return key, cert
def decrypt_message(encrypted_data, decryption_key): """Function parses an ASN.1 encrypted message and extracts/decrypts the original message. :param encrypted_data: A CMS ASN.1 byte string containing the encrypted data. :param decryption_key: The key to be used for decrypting the data. :return: A byte string containing the decrypted original message. """ cms_content = cms.ContentInfo.load(encrypted_data) cipher, decrypted_content = None, None if cms_content["content_type"].native == "enveloped_data": recipient_info = cms_content["content"]["recipient_infos"][0].parse() key_enc_alg = recipient_info["key_encryption_algorithm"][ "algorithm"].native encrypted_key = recipient_info["encrypted_key"].native if cms.KeyEncryptionAlgorithmId( key_enc_alg) == cms.KeyEncryptionAlgorithmId("rsa"): try: key = asymmetric.rsa_pkcs1v15_decrypt(decryption_key[0], encrypted_key) except Exception: raise DecryptionError( "Failed to decrypt the payload: Could not extract decryption key." ) alg = cms_content["content"]["encrypted_content_info"][ "content_encryption_algorithm"] encapsulated_data = cms_content["content"][ "encrypted_content_info"]["encrypted_content"].native try: if alg["algorithm"].native == "rc4": decrypted_content = symmetric.rc4_decrypt( key, encapsulated_data) elif alg.encryption_cipher == "tripledes": cipher = "tripledes_192_cbc" decrypted_content = symmetric.tripledes_cbc_pkcs5_decrypt( key, encapsulated_data, alg.encryption_iv) elif alg.encryption_cipher == "aes": decrypted_content = symmetric.aes_cbc_pkcs7_decrypt( key, encapsulated_data, alg.encryption_iv) elif alg.encryption_cipher == "rc2": decrypted_content = symmetric.rc2_cbc_pkcs5_decrypt( key, encapsulated_data, alg["parameters"]["iv"].native) else: raise AS2Exception("Unsupported Encryption Algorithm") except Exception as e: raise DecryptionError( "Failed to decrypt the payload: {}".format(e)) else: raise AS2Exception("Unsupported Encryption Algorithm") else: raise DecryptionError("Encrypted data not found in ASN.1 ") return cipher, decrypted_content
def verify_certificate_chain(cert_str, trusted_certs, ignore_self_signed=True): """ Verify a given certificate against a trust store""" # Load the certificate certificate = crypto.load_certificate(crypto.FILETYPE_ASN1, cert_str) # Create a certificate store and add your trusted certs try: store = crypto.X509Store() if ignore_self_signed: store.add_cert(certificate) # Assuming the certificates are in PEM format in a trusted_certs list for _cert in trusted_certs: store.add_cert(crypto.load_certificate(crypto.FILETYPE_ASN1, _cert)) # Create a certificate context using the store and the certificate store_ctx = crypto.X509StoreContext(store, certificate) # Verify the certificate, returns None if certificate is not valid store_ctx.verify_certificate() return True except crypto.X509StoreContextError as e: raise AS2Exception('Partner Certificate Invalid: %s' % e.args[-1][-1])
def encrypt_message(data_to_encrypt, enc_alg, encryption_cert): """Function encrypts data and returns the generated ASN.1 :param data_to_encrypt: A byte string of the data to be encrypted :param enc_alg: The algorithm to be used for encrypting the data :param encryption_cert: The certificate to be used for encrypting the data :return: A CMS ASN.1 byte string of the encrypted data. """ enc_alg_list = enc_alg.split("_") cipher, key_length, _ = enc_alg_list[0], enc_alg_list[1], enc_alg_list[2] # Generate the symmetric encryption key and encrypt the message key = util.rand_bytes(int(key_length) // 8) if cipher == "tripledes": algorithm_id = "1.2.840.113549.3.7" iv, encrypted_content = symmetric.tripledes_cbc_pkcs5_encrypt( key, data_to_encrypt, None) enc_alg_asn1 = algos.EncryptionAlgorithm({ "algorithm": algorithm_id, "parameters": cms.OctetString(iv) }) elif cipher == "rc2": algorithm_id = "1.2.840.113549.3.2" iv, encrypted_content = symmetric.rc2_cbc_pkcs5_encrypt( key, data_to_encrypt, None) enc_alg_asn1 = algos.EncryptionAlgorithm({ "algorithm": algorithm_id, "parameters": algos.Rc2Params({"iv": cms.OctetString(iv)}), }) elif cipher == "rc4": algorithm_id = "1.2.840.113549.3.4" encrypted_content = symmetric.rc4_encrypt(key, data_to_encrypt) enc_alg_asn1 = algos.EncryptionAlgorithm({ "algorithm": algorithm_id, }) elif cipher == "aes": if key_length == "128": algorithm_id = "2.16.840.1.101.3.4.1.2" elif key_length == "192": algorithm_id = "2.16.840.1.101.3.4.1.22" else: algorithm_id = "2.16.840.1.101.3.4.1.42" iv, encrypted_content = symmetric.aes_cbc_pkcs7_encrypt( key, data_to_encrypt, None) enc_alg_asn1 = algos.EncryptionAlgorithm({ "algorithm": algorithm_id, "parameters": cms.OctetString(iv) }) elif cipher == "des": algorithm_id = "1.3.14.3.2.7" iv, encrypted_content = symmetric.des_cbc_pkcs5_encrypt( key, data_to_encrypt, None) enc_alg_asn1 = algos.EncryptionAlgorithm({ "algorithm": algorithm_id, "parameters": cms.OctetString(iv) }) else: raise AS2Exception("Unsupported Encryption Algorithm") # Encrypt the key and build the ASN.1 message encrypted_key = asymmetric.rsa_pkcs1v15_encrypt(encryption_cert, key) return cms.ContentInfo({ "content_type": cms.ContentType("enveloped_data"), "content": cms.EnvelopedData({ "version": cms.CMSVersion("v0"), "recipient_infos": [ cms.KeyTransRecipientInfo({ "version": cms.CMSVersion("v0"), "rid": cms.RecipientIdentifier({ "issuer_and_serial_number": cms.IssuerAndSerialNumber({ "issuer": encryption_cert.asn1["tbs_certificate"]["issuer"], "serial_number": encryption_cert.asn1["tbs_certificate"] ["serial_number"], }) }), "key_encryption_algorithm": cms.KeyEncryptionAlgorithm( {"algorithm": cms.KeyEncryptionAlgorithmId("rsa")}), "encrypted_key": cms.OctetString(encrypted_key), }) ], "encrypted_content_info": cms.EncryptedContentInfo({ "content_type": cms.ContentType("data"), "content_encryption_algorithm": enc_alg_asn1, "encrypted_content": encrypted_content, }), }), }).dump()
def verify_message(data_to_verify, signature, verify_cert): """Function parses an ASN.1 encrypted message and extracts/decrypts the original message. :param data_to_verify: A byte string of the data to be verified against the signature. :param signature: A CMS ASN.1 byte string containing the signature. :param verify_cert: The certificate to be used for verifying the signature. :return: The digest algorithm that was used in the signature. """ cms_content = cms.ContentInfo.load(signature) digest_alg = None if cms_content["content_type"].native == "signed_data": for signer in cms_content["content"]["signer_infos"]: digest_alg = signer["digest_algorithm"]["algorithm"].native if digest_alg not in DIGEST_ALGORITHMS: raise Exception("Unsupported Digest Algorithm") sig_alg = signer["signature_algorithm"]["algorithm"].native sig = signer["signature"].native signed_data = data_to_verify if signer["signed_attrs"]: attr_dict = {} for attr in signer["signed_attrs"]: try: attr_dict[attr.native["type"]] = attr.native["values"] except (ValueError, KeyError): continue message_digest = bytes() for d in attr_dict["message_digest"]: message_digest += d digest_func = hashlib.new(digest_alg) digest_func.update(data_to_verify) calc_message_digest = digest_func.digest() if message_digest != calc_message_digest: raise IntegrityError( "Failed to verify message signature: Message Digest does not match." ) signed_data = signer["signed_attrs"].untag().dump() try: if sig_alg == "rsassa_pkcs1v15": asymmetric.rsa_pkcs1v15_verify(verify_cert, sig, signed_data, digest_alg) elif sig_alg == "rsassa_pss": asymmetric.rsa_pss_verify(verify_cert, sig, signed_data, digest_alg) else: raise AS2Exception("Unsupported Signature Algorithm") except Exception as e: import traceback traceback.print_exc() raise IntegrityError( "Failed to verify message signature: {}".format(e)) else: raise IntegrityError("Signed data not found in ASN.1 ") return digest_alg
def sign_message( data_to_sign, digest_alg, sign_key, sign_alg="rsassa_pkcs1v15", use_signed_attributes=True, ): """Function signs the data and returns the generated ASN.1 :param data_to_sign: A byte string of the data to be signed. :param digest_alg: The digest algorithm to be used for generating the signature. :param sign_key: The key to be used for generating the signature. :param sign_alg: The algorithm to be used for signing the message. :param use_signed_attributes: Optional attribute to indicate weather the CMS signature attributes should be included in the signature or not. :return: A CMS ASN.1 byte string of the signed data. """ if use_signed_attributes: digest_func = hashlib.new(digest_alg) digest_func.update(data_to_sign) message_digest = digest_func.digest() class SmimeCapability(core.Sequence): _fields = [ ("0", core.Any, { "optional": True }), ("1", core.Any, { "optional": True }), ("2", core.Any, { "optional": True }), ("3", core.Any, { "optional": True }), ("4", core.Any, { "optional": True }), ] class SmimeCapabilities(core.Sequence): _fields = [ ("0", SmimeCapability), ("1", SmimeCapability, { "optional": True }), ("2", SmimeCapability, { "optional": True }), ("3", SmimeCapability, { "optional": True }), ("4", SmimeCapability, { "optional": True }), ("5", SmimeCapability, { "optional": True }), ] smime_cap = OrderedDict([ ( "0", OrderedDict([ ("0", core.ObjectIdentifier("2.16.840.1.101.3.4.1.42")) ]), ), ( "1", OrderedDict([ ("0", core.ObjectIdentifier("2.16.840.1.101.3.4.1.2")) ]), ), ( "2", OrderedDict([("0", core.ObjectIdentifier("1.2.840.113549.3.7")) ]), ), ( "3", OrderedDict([ ("0", core.ObjectIdentifier("1.2.840.113549.3.2")), ("1", core.Integer(128)), ]), ), ( "4", OrderedDict([ ("0", core.ObjectIdentifier("1.2.840.113549.3.4")), ("1", core.Integer(128)), ]), ), ]) signed_attributes = cms.CMSAttributes([ cms.CMSAttribute({ "type": cms.CMSAttributeType("content_type"), "values": cms.SetOfContentType([cms.ContentType("data")]), }), cms.CMSAttribute({ "type": cms.CMSAttributeType("signing_time"), "values": cms.SetOfTime([ cms.Time({ "utc_time": core.UTCTime( datetime.utcnow().replace(tzinfo=timezone.utc)) }) ]), }), cms.CMSAttribute({ "type": cms.CMSAttributeType("message_digest"), "values": cms.SetOfOctetString([core.OctetString(message_digest)]), }), cms.CMSAttribute({ "type": cms.CMSAttributeType("1.2.840.113549.1.9.15"), "values": cms.SetOfAny([core.Any(SmimeCapabilities(smime_cap))]), }), ]) else: signed_attributes = None # Generate the signature data_to_sign = signed_attributes.dump( ) if signed_attributes else data_to_sign if sign_alg == "rsassa_pkcs1v15": signature = asymmetric.rsa_pkcs1v15_sign(sign_key[0], data_to_sign, digest_alg) elif sign_alg == "rsassa_pss": signature = asymmetric.rsa_pss_sign(sign_key[0], data_to_sign, digest_alg) else: raise AS2Exception("Unsupported Signature Algorithm") return cms.ContentInfo({ "content_type": cms.ContentType("signed_data"), "content": cms.SignedData({ "version": cms.CMSVersion("v1"), "digest_algorithms": cms.DigestAlgorithms([ algos.DigestAlgorithm( {"algorithm": algos.DigestAlgorithmId(digest_alg)}) ]), "encap_content_info": cms.ContentInfo({"content_type": cms.ContentType("data")}), "certificates": cms.CertificateSet( [cms.CertificateChoices({"certificate": sign_key[1].asn1})]), "signer_infos": cms.SignerInfos([ cms.SignerInfo({ "version": cms.CMSVersion("v1"), "sid": cms.SignerIdentifier({ "issuer_and_serial_number": cms.IssuerAndSerialNumber({ "issuer": sign_key[1].asn1["tbs_certificate"]["issuer"], "serial_number": sign_key[1].asn1["tbs_certificate"] ["serial_number"], }) }), "digest_algorithm": algos.DigestAlgorithm( {"algorithm": algos.DigestAlgorithmId(digest_alg)}), "signed_attrs": signed_attributes, "signature_algorithm": algos.SignedDigestAlgorithm( {"algorithm": algos.SignedDigestAlgorithmId(sign_alg)}), "signature": core.OctetString(signature), }) ]), }), }).dump()