Пример #1
0
def test_encryption():
    """Test the encryption and decryption functions."""
    with open(os.path.join(TEST_DIR, "cert_test.p12"), "rb") as fp:
        decrypt_key = Organization.load_key(fp.read(), "test")
    with open(os.path.join(TEST_DIR, "cert_test_public.pem"), "rb") as fp:
        encrypt_cert = asymmetric.load_certificate(fp.read())

    with pytest.raises(DecryptionError):
        cms.decrypt_message(INVALID_DATA, None)

    # Test all the encryption algorithms
    enc_algorithms = [
        "rc2_128_cbc",
        "rc4_128_cbc",
        "aes_128_cbc",
        "aes_192_cbc",
        "aes_256_cbc",
    ]
    for enc_algorithm in enc_algorithms:
        encrypted_data = cms.encrypt_message(b"data", enc_algorithm, encrypt_cert)
        _, decrypted_data = cms.decrypt_message(encrypted_data, decrypt_key)
        assert decrypted_data == b"data"

    # Test no encryption algorithm
    with pytest.raises(AS2Exception):
        cms.encrypt_message(b"data", "rc5_128_cbc", encrypt_cert)

    # Test no encryption algorithm on decrypt
    encrypted_data = cms.encrypt_message(b"data", "des_64_cbc", encrypt_cert)
    with pytest.raises(AS2Exception):
        cms.decrypt_message(encrypted_data, decrypt_key)
Пример #2
0
    def build(self,
              data,
              filename=None,
              subject='AS2 Message',
              content_type='application/edi-consent',
              additional_headers=None):
        """Function builds the AS2 message. Compresses, signs and encrypts
        the payload if applicable.

        :param data: A byte string of the data to be transmitted.

        :param filename:
            Optional filename to be included in the Content-disposition header.

        :param subject:
            The subject for the AS2 message, used by some AS2 servers for
            additional routing of messages. (default "AS2 Message")

        :param content_type:
            The content type for the AS2 message, to be used in the MIME
            header. (default "application/edi-consent")

        :param additional_headers:
            Any additional headers to be included as part of the AS2 message.

        """

        # Validations
        assert type(data) is bytes, \
            'Parameter data must be of bytes type.'

        additional_headers = additional_headers if additional_headers else {}
        assert type(additional_headers) is dict

        if self.receiver.sign and not self.sender.sign_key:
            raise ImproperlyConfigured(
                'Signing of messages is enabled but sign key is not set '
                'for the sender.')

        if self.receiver.encrypt and not self.receiver.encrypt_cert:
            raise ImproperlyConfigured(
                'Encryption of messages is enabled but encrypt key is not set '
                'for the receiver.')

        # Generate message id using UUID 1 as it uses both hostname and time
        self.message_id = email_utils.make_msgid().lstrip('<').rstrip('>')

        # Set up the message headers
        as2_headers = {
            'AS2-Version': AS2_VERSION,
            'ediint-features': EDIINT_FEATURES,
            'Message-ID': '<{}>'.format(self.message_id),
            'AS2-From': quote_as2name(self.sender.as2_name),
            'AS2-To': quote_as2name(self.receiver.as2_name),
            'Subject': subject,
            'Date': email_utils.formatdate(localtime=True),
            # 'recipient-address': message.partner.target_url,
        }
        as2_headers.update(additional_headers)

        # Read the input and convert to bytes if value is unicode/str
        # using utf-8 encoding and finally Canonicalize the payload
        self.payload = email_message.Message()
        self.payload.set_payload(data)
        self.payload.set_type(content_type)
        encoders.encode_7or8bit(self.payload)

        if filename:
            self.payload.add_header('Content-Disposition',
                                    'attachment',
                                    filename=filename)
        del self.payload['MIME-Version']

        if self.receiver.compress:
            self.compressed = True
            compressed_message = email_message.Message()
            compressed_message.set_type('application/pkcs7-mime')
            compressed_message.set_param('name', 'smime.p7z')
            compressed_message.set_param('smime-type', 'compressed-data')
            compressed_message.add_header('Content-Disposition',
                                          'attachment',
                                          filename='smime.p7z')
            compressed_message.add_header('Content-Transfer-Encoding',
                                          'binary')
            compressed_message.set_payload(
                compress_message(mime_to_bytes(self.payload, 0)))

            self.payload = compressed_message

            logger.debug('Compressed message %s payload as:\n%s' %
                         (self.message_id, self.payload.as_string()))

        if self.receiver.sign:
            self.signed, self.digest_alg = True, self.receiver.digest_alg
            signed_message = MIMEMultipart(
                'signed', protocol="application/pkcs7-signature")
            del signed_message['MIME-Version']
            signed_message.attach(self.payload)

            # Calculate the MIC Hash of the message to be verified
            mic_content = canonicalize(self.payload)
            digest_func = hashlib.new(self.digest_alg)
            digest_func.update(mic_content)
            self.mic = binascii.b2a_base64(digest_func.digest()).strip()

            # Create the signature mime message
            signature = email_message.Message()
            signature.set_type('application/pkcs7-signature')
            signature.set_param('name', 'smime.p7s')
            signature.set_param('smime-type', 'signed-data')
            signature.add_header('Content-Disposition',
                                 'attachment',
                                 filename='smime.p7s')
            del signature['MIME-Version']
            signature.set_payload(
                sign_message(mic_content, self.digest_alg,
                             self.sender.sign_key))
            encoders.encode_base64(signature)

            signed_message.set_param('micalg', self.digest_alg)
            signed_message.attach(signature)
            self.payload = signed_message

            logger.debug('Signed message %s payload as:\n%s' %
                         (self.message_id, mime_to_bytes(self.payload, 0)))

        if self.receiver.encrypt:
            self.encrypted, self.enc_alg = True, self.receiver.enc_alg
            encrypted_message = email_message.Message()
            encrypted_message.set_type('application/pkcs7-mime')
            encrypted_message.set_param('name', 'smime.p7m')
            encrypted_message.set_param('smime-type', 'enveloped-data')
            encrypted_message.add_header('Content-Disposition',
                                         'attachment',
                                         filename='smime.p7m')
            encrypted_message.add_header('Content-Transfer-Encoding', 'binary')
            encrypt_cert = self.receiver.load_encrypt_cert()
            encrypted_message.set_payload(
                encrypt_message(mime_to_bytes(self.payload, 0), self.enc_alg,
                                encrypt_cert))

            self.payload = encrypted_message
            logger.debug('Encrypted message %s payload as:\n%s' %
                         (self.message_id, self.payload.as_string()))

        if self.receiver.mdn_mode:
            as2_headers['disposition-notification-to'] = '*****@*****.**'
            if self.receiver.mdn_digest_alg:
                as2_headers['disposition-notification-options'] = \
                    'signed-receipt-protocol=required, pkcs7-signature; ' \
                    'signed-receipt-micalg=optional, {}'.format(
                        self.receiver.mdn_digest_alg)
            if self.receiver.mdn_mode == 'ASYNC':
                if not self.sender.mdn_url:
                    raise ImproperlyConfigured(
                        'MDN URL must be set in the organization when MDN mode '
                        'is set to ASYNC')
                as2_headers['receipt-delivery-option'] = self.sender.mdn_url

        # Update the headers of the final payload and set its boundary
        for k, v in as2_headers.items():
            if self.payload.get(k):
                self.payload.replace_header(k, v)
            else:
                self.payload.add_header(k, v)

        if self.payload.is_multipart():
            self.payload.set_boundary(make_mime_boundary())
Пример #3
0
    def build(
        self,
        data,
        filename=None,
        subject="AS2 Message",
        content_type="application/edi-consent",
        additional_headers=None,
        disposition_notification_to="*****@*****.**",
    ):
        """Function builds the AS2 message. Compresses, signs and encrypts
        the payload if applicable.

        :param data: A byte string of the data to be transmitted.

        :param filename:
            Optional filename to be included in the Content-disposition header.

        :param subject:
            The subject for the AS2 message, used by some AS2 servers for
            additional routing of messages. (default "AS2 Message")

        :param content_type:
            The content type for the AS2 message, to be used in the MIME
            header. (default "application/edi-consent")

        :param additional_headers:
            Any additional headers to be included as part of the AS2 message.

        :param disposition_notification_to:
            Email address for disposition-notification-to header entry.
            (default "*****@*****.**")

        """

        # Validations
        assert isinstance(data, bytes), "Parameter data must be of bytes type."

        additional_headers = additional_headers if additional_headers else {}
        assert isinstance(additional_headers, dict)

        if self.receiver.sign and not self.sender.sign_key:
            raise ImproperlyConfigured(
                "Signing of messages is enabled but sign key is not set for the sender."
            )

        if self.receiver.encrypt and not self.receiver.encrypt_cert:
            raise ImproperlyConfigured(
                "Encryption of messages is enabled but encrypt key is not set for the receiver."
            )

        # Generate message id using UUID 1 as it uses both hostname and time
        self.message_id = email_utils.make_msgid().lstrip("<").rstrip(">")

        # Set up the message headers
        as2_headers = {
            "AS2-Version": AS2_VERSION,
            "ediint-features": EDIINT_FEATURES,
            "Message-ID": f"<{self.message_id}>",
            "AS2-From": quote_as2name(self.sender.as2_name),
            "AS2-To": quote_as2name(self.receiver.as2_name),
            "Subject": subject,
            "Date": email_utils.formatdate(localtime=True),
        }
        as2_headers.update(additional_headers)

        # Read the input and convert to bytes if value is unicode/str
        # using utf-8 encoding and finally Canonicalize the payload
        self.payload = email_message.Message()
        self.payload.set_payload(data)
        self.payload.set_type(content_type)

        if content_type.lower().startswith("application/octet-stream"):
            self.payload["Content-Transfer-Encoding"] = "binary"
        else:
            encoders.encode_7or8bit(self.payload)

        if filename:
            self.payload.add_header("Content-Disposition",
                                    "attachment",
                                    filename=filename)
        del self.payload["MIME-Version"]

        if self.receiver.compress:
            self.compressed = True
            compressed_message = email_message.Message()
            compressed_message.set_type("application/pkcs7-mime")
            compressed_message.set_param("name", "smime.p7z")
            compressed_message.set_param("smime-type", "compressed-data")
            compressed_message.add_header("Content-Disposition",
                                          "attachment",
                                          filename="smime.p7z")
            compressed_message.add_header("Content-Transfer-Encoding",
                                          "binary")
            compressed_message.set_payload(
                compress_message(mime_to_bytes(self.payload)))
            self.payload = compressed_message

            logger.debug(
                f"Compressed message {self.message_id} payload as:\n{mime_to_bytes(self.payload)}"
            )

        if self.receiver.sign:
            self.signed, self.digest_alg = True, self.receiver.digest_alg
            signed_message = MIMEMultipart(
                "signed", protocol="application/pkcs7-signature")
            del signed_message["MIME-Version"]
            signed_message.attach(self.payload)

            # Calculate the MIC Hash of the message to be verified
            mic_content = canonicalize(self.payload)
            digest_func = hashlib.new(self.digest_alg)
            digest_func.update(mic_content)
            self.mic = binascii.b2a_base64(digest_func.digest()).strip()

            # Create the signature mime message
            signature = email_message.Message()
            signature.set_type("application/pkcs7-signature")
            signature.set_param("name", "smime.p7s")
            signature.set_param("smime-type", "signed-data")
            signature.add_header("Content-Disposition",
                                 "attachment",
                                 filename="smime.p7s")
            del signature["MIME-Version"]
            signature_data = sign_message(mic_content, self.digest_alg,
                                          self.sender.sign_key)
            signature.set_payload(signature_data)
            encoders.encode_base64(signature)

            signed_message.set_param("micalg", self.digest_alg)
            signed_message.attach(signature)
            self.payload = signed_message

            logger.debug(
                f"Signed message {self.message_id} payload as:\n{mime_to_bytes(self.payload)}"
            )

        if self.receiver.encrypt:
            self.encrypted, self.enc_alg = True, self.receiver.enc_alg
            encrypted_message = email_message.Message()
            encrypted_message.set_type("application/pkcs7-mime")
            encrypted_message.set_param("name", "smime.p7m")
            encrypted_message.set_param("smime-type", "enveloped-data")
            encrypted_message.add_header("Content-Disposition",
                                         "attachment",
                                         filename="smime.p7m")
            encrypted_message.add_header("Content-Transfer-Encoding", "binary")
            encrypt_cert = self.receiver.load_encrypt_cert()
            encrypted_data = encrypt_message(mime_to_bytes(self.payload),
                                             self.enc_alg, encrypt_cert)
            encrypted_message.set_payload(encrypted_data)

            self.payload = encrypted_message
            logger.debug(
                f"Encrypted message {self.message_id} payload as:\n{mime_to_bytes(self.payload)}"
            )

        if self.receiver.mdn_mode:
            as2_headers[
                "disposition-notification-to"] = disposition_notification_to
            if self.receiver.mdn_digest_alg:
                as2_headers["disposition-notification-options"] = (
                    f"signed-receipt-protocol=required, pkcs7-signature; "
                    f"signed-receipt-micalg=optional, {self.receiver.mdn_digest_alg}"
                )
            if self.receiver.mdn_mode == "ASYNC":
                if not self.sender.mdn_url:
                    raise ImproperlyConfigured(
                        "MDN URL must be set in the organization when MDN mode is set to ASYNC"
                    )
                as2_headers["receipt-delivery-option"] = self.sender.mdn_url

        # Update the headers of the final payload and set its boundary
        for k, v in as2_headers.items():
            if self.payload.get(k):
                self.payload.replace_header(k, v)
            else:
                self.payload.add_header(k, v)

        if self.payload.is_multipart():
            self.payload.set_boundary(make_mime_boundary())