Exemplo n.º 1
0
    def test_verify(self):

        retrieved_msg, retrieved_dn = verify(SIGNED_MSG, self.ca_dir, False)

        if not retrieved_dn == TEST_CERT_DN:
            self.fail("The DN of the verified message didn't match the cert.")

        if not retrieved_msg.strip() == MSG:
            self.fail("The verified messge didn't match the original.")

        retrieved_msg2, retrieved_dn2 = verify(SIGNED_MSG2, self.ca_dir, False)

        if not retrieved_dn2 == CERT2_DN:
            print retrieved_dn2
            print CERT2_DN
            self.fail("The DN of the verified message didn't match the cert.")

        if not retrieved_msg2.strip() == MSG2:
            print retrieved_msg2
            print MSG2
            self.fail("The verified messge didn't match the original.")

        # Try empty string
        try:
            verify('', self.ca_dir, False)
        except CryptoException:
            pass
        # Try rubbish
        try:
            verify('Bibbly bobbly', self.ca_dir, False)
        except CryptoException:
            pass
        # Try None arguments
        try:
            verify('Bibbly bobbly', None, False)
        except CryptoException:
            pass
        try:
            verify(None, 'not a path', False)
        except CryptoException:
            pass
Exemplo n.º 2
0
 def test_verify(self):
     
     retrieved_msg, retrieved_dn = verify(SIGNED_MSG, self.ca_dir, False)
     
     if not retrieved_dn == TEST_CERT_DN:
         self.fail("The DN of the verified message didn't match the cert.")
         
     if not retrieved_msg.strip() == MSG:
         self.fail("The verified messge didn't match the original.")
         
     retrieved_msg2, retrieved_dn2 = verify(SIGNED_MSG2, self.ca_dir, False)
     
     if not retrieved_dn2 == TEST_CERT_DN:
         print retrieved_dn2
         print TEST_CERT_DN
         self.fail("The DN of the verified message didn't match the cert.")
         
     if not retrieved_msg2.strip() == MSG2:
         print retrieved_msg2
         print MSG2
         self.fail("The verified messge didn't match the original.")
         
     # Try empty string    
     try:
         verify('', self.ca_dir, False)
     except CryptoException:
         pass
     # Try rubbish
     try:
         verify('Bibbly bobbly', self.ca_dir, False)
     except CryptoException:
         pass
     # Try None arguments 
     try:
         verify('Bibbly bobbly', None, False)
     except CryptoException:
         pass
     try:
         verify(None, 'not a path', False)
     except CryptoException:
         pass
Exemplo n.º 3
0
    def test_message_tampering(self):
        """Test that a tampered message is not accepted as valid."""
        signed_message = sign(MSG, TEST_CERT_FILE, TEST_KEY_FILE)
        tampered_message = signed_message.replace(MSG, "Spam")

        # Verifying the orignal, un-tampered message should be fine.
        verified_message, verified_signer = verify(signed_message, TEST_CA_DIR,
                                                   False)
        self.assertEqual(verified_message, MSG)
        self.assertEqual(verified_signer, TEST_CERT_DN)

        # Verifying the tampered message should not be fine.
        self.assertRaises(CryptoException, verify, tampered_message,
                          TEST_CA_DIR, False)
Exemplo n.º 4
0
Arquivo: ssm2.py Projeto: pjcon/ssm
    def _handle_msg(self, text):
        """Deal with the raw message contents appropriately.

        Namely:
        - decrypt if necessary
        - verify signature
        - Return plain-text message, signer's DN and an error/None.
        """
        if text is None or text == '':
            warning = 'Empty text passed to _handle_msg.'
            log.warn(warning)
            return None, None, warning
#        if not text.startswith('MIME-Version: 1.0'):
#            raise Ssm2Exception('Not a valid message.')

# encrypted - this could be nicer
        if 'application/pkcs7-mime' in text or 'application/x-pkcs7-mime' in text:
            try:
                text = crypto.decrypt(text, self._cert, self._key)
            except crypto.CryptoException as e:
                error = 'Failed to decrypt message: %s' % e
                log.error(error)
                return None, None, error

        # always signed
        try:
            message, signer = crypto.verify(text, self._capath,
                                            self._check_crls)
        except crypto.CryptoException as e:
            error = 'Failed to verify message: %s' % e
            log.error(error)
            return None, None, error

        if signer not in self._valid_dns:
            warning = 'Signer not in valid DNs list: %s' % signer
            log.warn(warning)
            return None, signer, warning
        else:
            log.info('Valid signer: %s', signer)

        return message, signer, None
Exemplo n.º 5
0
    def test_sign(self):
        '''
        I haven't found a good way to test this yet.  Each time you sign a 
        message, the output has a random element, so you can't compare strings.
        '''
        signed = sign(MSG, self.certpath, self.keypath)
        
        if not 'MIME-Version' in signed:
            self.fail("Didn't get MIME message when signing.")
            
        if not MSG in signed:
            self.fail('The plaintext should be included in the signed message.')
        
        # Indirect testing, using the verify_message() method
        retrieved_msg, retrieved_dn = verify(signed, self.ca_dir, False)
        
        if not retrieved_dn == TEST_CERT_DN:
            self.fail("The DN of the verified message didn't match the cert.")

        if not retrieved_msg == MSG:
            self.fail("The verified message didn't match the original.")
Exemplo n.º 6
0
    def test_sign(self):
        '''
        I haven't found a good way to test this yet.  Each time you sign a
        message, the output has a random element, so you can't compare strings.
        '''
        signed = sign(MSG, TEST_CERT_FILE, TEST_KEY_FILE)

        if not 'MIME-Version' in signed:
            self.fail("Didn't get MIME message when signing.")

        if not MSG in signed:
            self.fail(
                'The plaintext should be included in the signed message.')

        # Indirect testing, using the verify_message() method
        retrieved_msg, retrieved_dn = verify(signed, TEST_CA_DIR, False)

        if not retrieved_dn == TEST_CERT_DN:
            self.fail("The DN of the verified message didn't match the cert.")

        if not retrieved_msg == MSG:
            self.fail("The verified message didn't match the original.")
Exemplo n.º 7
0
        if text is None or text == '':
            return None, None
#        if not text.startswith('MIME-Version: 1.0'):
#            raise Ssm2Exception('Not a valid message.')
        
        # encrypted - this could be nicer
        if 'application/pkcs7-mime' in text or 'application/x-pkcs7-mime' in text:
            try:
                text = crypto.decrypt(text, self._cert, self._key)
            except crypto.CryptoException, e:
                log.error('Failed to decrypt message: %s', e)
                return None, None
        
        # always signed
        try:
            message, signer = crypto.verify(text, self._capath, self._check_crls)
        except crypto.CryptoException, e:
            log.error('Failed to verify message: %s', e)
            return None, None
        
        if signer not in self._valid_dns:
            log.warn('Signer not in valid DNs list: %s', signer)
            return None, signer
        else:
            log.info('Valid signer: %s', signer)
            
        return message, signer
        
    def _send_msg(self, message, msgid):
        '''
        Send one message using stomppy.  The message will be signed using 
Exemplo n.º 8
0
    def test_verify(self):

        signed_msg = sign(MSG, TEST_CERT_FILE, TEST_KEY_FILE)

        # This is a manual 'fudge' to make MS2 appear like a
        # quoted-printable message when signed
        # Encode MSG2 so it's 'quoted-printable'
        quopri_msg = quopri.encodestring(MSG2)
        # Add Content-Type and Content-Transfer-Encoding
        # headers to message
        header_quopri_msg = ('Content-Type: text/xml; charset=utf8\n'
                             'Content-Transfer-Encoding: quoted-printable\n'
                             '\n'
                             '%s' % quopri_msg)

        # We can't use crypto.sign as that assumes the use of the '-text' option
        # which cause the message to be interpreted as plaintext
        p1 = Popen([
            'openssl', 'smime', '-sign', '-inkey', TEST_KEY_FILE, '-signer',
            TEST_CERT_FILE
        ],
                   stdin=PIPE,
                   stdout=PIPE,
                   stderr=PIPE)

        signed_msg2, error = p1.communicate(header_quopri_msg)

        if error != '':
            self.fail(error)

        retrieved_msg, retrieved_dn = verify(signed_msg, TEST_CA_DIR, False)

        if not retrieved_dn == TEST_CERT_DN:
            self.fail("The DN of the verified message didn't match the cert.")

        if not retrieved_msg.strip() == MSG:
            self.fail("The verified messge didn't match the original.")

        retrieved_msg2, retrieved_dn2 = verify(signed_msg2, TEST_CA_DIR, False)

        if not retrieved_dn2 == TEST_CERT_DN:
            print retrieved_dn2
            print TEST_CERT_DN
            self.fail("The DN of the verified message didn't match the cert.")

        if not retrieved_msg2.strip() == MSG2:
            print retrieved_msg2
            print MSG2
            self.fail("The verified messge didn't match the original.")

        # Try empty string
        try:
            verify('', TEST_CA_DIR, False)
        except CryptoException:
            pass
        # Try rubbish
        try:
            verify('Bibbly bobbly', TEST_CA_DIR, False)
        except CryptoException:
            pass
        # Try None arguments
        try:
            verify('Bibbly bobbly', None, False)
        except CryptoException:
            pass
        try:
            verify(None, 'not a path', False)
        except CryptoException:
            pass
Exemplo n.º 9
0
class Ssm2(stomp.ConnectionListener):
    '''
    Minimal SSM implementation.
    '''
    # Schema for the dirq message queue.
    QSCHEMA = {'body': 'string', 'signer': 'string', 'empaid': 'string?'}
    REJECT_SCHEMA = {
        'body': 'string',
        'signer': 'string?',
        'empaid': 'string?',
        'error': 'string'
    }
    CONNECTION_TIMEOUT = 10

    def __init__(self,
                 hosts_and_ports,
                 qpath,
                 cert,
                 key,
                 dest=None,
                 listen=None,
                 capath=None,
                 check_crls=False,
                 use_ssl=False,
                 username=None,
                 password=None,
                 enc_cert=None,
                 verify_enc_cert=True,
                 pidfile=None):
        '''
        Creates an SSM2 object.  If a listen value is supplied,
        this SSM2 will be a receiver.
        '''
        self._conn = None
        self._last_msg = None

        self._brokers = hosts_and_ports
        self._cert = cert
        self._key = key
        self._enc_cert = enc_cert
        self._capath = capath
        self._check_crls = check_crls
        self._user = username
        self._pwd = password
        self._use_ssl = use_ssl
        # use pwd auth if we're supplied both user and pwd
        self._use_pwd = username is not None and password is not None
        self.connected = False

        self._listen = listen
        self._dest = dest

        self._valid_dns = []
        self._pidfile = pidfile

        # create the filesystem queues for accepted and rejected messages
        if dest is not None and listen is None:
            self._outq = QueueSimple(qpath)
        elif listen is not None:
            inqpath = os.path.join(qpath, 'incoming')
            rejectqpath = os.path.join(qpath, 'reject')
            self._inq = Queue(inqpath, schema=Ssm2.QSCHEMA)
            self._rejectq = Queue(rejectqpath, schema=Ssm2.REJECT_SCHEMA)
        else:
            raise Ssm2Exception('SSM must be either producer or consumer.')
        # check that the cert and key match
        if not crypto.check_cert_key(self._cert, self._key):
            raise Ssm2Exception('Cert and key don\'t match.')

        # Check that the certificate has not expired.
        if not crypto.verify_cert_date(self._cert):
            raise Ssm2Exception('Certificate %s has expired.' % self._cert)

        # check the server certificate provided
        if enc_cert is not None:
            log.info('Messages will be encrypted using %s', enc_cert)
            if not os.path.isfile(self._enc_cert):
                raise Ssm2Exception(
                    'Specified certificate file does not exist: %s.' %
                    self._enc_cert)
            # Check that the encyption certificate has not expired.
            if not crypto.verify_cert_date(enc_cert):
                raise Ssm2Exception(
                    'Encryption certificate %s has expired. Please obtain the '
                    'new one from the final server receiving your messages.' %
                    enc_cert)
            if verify_enc_cert:
                if not crypto.verify_cert_path(self._enc_cert, self._capath,
                                               self._check_crls):
                    raise Ssm2Exception(
                        'Failed to verify server certificate %s against CA path %s.'
                        % (self._enc_cert, self._capath))

        # If the overall SSM log level is info, we want to only
        # see log entries from stomp.py at the warning level and above.
        if logging.getLogger("ssm.ssm2").getEffectiveLevel() == logging.INFO:
            logging.getLogger("stomp.py").setLevel(logging.WARNING)
        # If the overall SSM log level is debug, we want to only
        # see log entries from stomp.py at the info level and above.
        elif logging.getLogger(
                "ssm.ssm2").getEffectiveLevel() == logging.DEBUG:
            logging.getLogger("stomp.py").setLevel(logging.INFO)

    def set_dns(self, dn_list):
        '''
        Set the list of DNs which are allowed to sign incoming messages.
        '''
        self._valid_dns = dn_list

    ##########################################################################
    # Methods called by stomppy
    ##########################################################################

    def on_send(self, frame, unused_body=None):
        '''
        Called by stomppy when a message is sent.

        unused_body is only present to have a backward compatible
        method signature when using stomp.py v3.1.X
        '''
        try:
            # Try the stomp.py v4 way first
            empaid = frame.headers['empa-id']
        except KeyError:
            # Then you are most likely using stomp.py v4.
            # on_send is now triggered on non message frames
            # (such as 'CONNECT' frames) and as such without an empa-id.
            empaid = 'no empa-id'
        except AttributeError:
            # Then you are likely using stomp.py v3
            empaid = frame['empa-id']

        log.debug('Sent message: %s', empaid)

    def on_message(self, headers, body):
        '''
        Called by stomppy when a message is received.

        Handle the message according to its content and headers.
        '''
        try:
            empaid = headers['empa-id']
            if empaid == 'ping':  # ignore ping message
                log.info('Received ping message.')
                return
        except KeyError:
            empaid = 'noid'

        log.info("Received message. ID = %s", empaid)
        extracted_msg, signer, err_msg = self._handle_msg(body)

        try:
            # If the message is empty or the error message is not empty
            # then reject the message.
            if extracted_msg is None or err_msg is not None:
                if signer is None:  # crypto failed
                    signer = 'Not available.'
                elif extracted_msg is not None:
                    # If there is a signer then it was rejected for not being
                    # in the DNs list, so we can use the extracted msg, which
                    # allows the msg to be reloaded if needed.
                    body = extracted_msg

                log.warn("Message rejected: %s", err_msg)

                name = self._rejectq.add({
                    'body': body,
                    'signer': signer,
                    'empaid': empaid,
                    'error': err_msg
                })
                log.info("Message saved to reject queue as %s", name)

            else:  # message verified ok
                name = self._inq.add({
                    'body': extracted_msg,
                    'signer': signer,
                    'empaid': empaid
                })
                log.info("Message saved to incoming queue as %s", name)

        except (IOError, OSError) as e:
            log.error('Failed to read or write file: %s', e)

    def on_error(self, unused_headers, body):
        '''
        Called by stomppy when an error frame is received.
        '''
        log.warn('Error message received: %s', body)
        raise Ssm2Exception()

    def on_connected(self, unused_headers, unused_body):
        '''
        Called by stomppy when a connection is established.

        Track the connection.
        '''
        self.connected = True
        log.info('Connected.')

    def on_disconnected(self):
        '''
        Called by stomppy when disconnected from the broker.
        '''
        log.info('Disconnected from broker.')
        self.connected = False

    def on_receipt(self, headers, unused_body):
        '''
        Called by stomppy when the broker acknowledges receipt of a message.
        '''
        log.info('Broker received message: %s', headers['receipt-id'])
        self._last_msg = headers['receipt-id']

    def on_receiver_loop_completed(self, _unused_headers, _unused_body):
        """
        Called by stompy when the receiver loop ends.

        This is usually trigger as part of a disconnect.
        """
        log.debug('on_receiver_loop_completed called.')

    ##########################################################################
    # Message handling methods
    ##########################################################################

    def _handle_msg(self, text):
        '''
        Deal with the raw message contents appropriately:
        - decrypt if necessary
        - verify signature
        Return plain-text message, signer's DN and an error/None.
        '''
        if text is None or text == '':
            warning = 'Empty text passed to _handle_msg.'
            log.warn(warning)
            return None, None, warning
#        if not text.startswith('MIME-Version: 1.0'):
#            raise Ssm2Exception('Not a valid message.')

# encrypted - this could be nicer
        if 'application/pkcs7-mime' in text or 'application/x-pkcs7-mime' in text:
            try:
                text = crypto.decrypt(text, self._cert, self._key)
            except crypto.CryptoException, e:
                error = 'Failed to decrypt message: %s' % e
                log.error(error)
                return None, None, error

        # always signed
        try:
            message, signer = crypto.verify(text, self._capath,
                                            self._check_crls)
        except crypto.CryptoException, e:
            error = 'Failed to verify message: %s' % e
            log.error(error)
            return None, None, error
Exemplo n.º 10
0
    def test_verify(self):

        signed_msg = sign(MSG, TEST_CERT_FILE, TEST_KEY_FILE)

        # This is a manual 'fudge' to make MS2 appear like a
        # quoted-printable message when signed
        # Encode MSG2 so it's 'quoted-printable', after encoding it to ensure
        # it's a bytes object for Python 3. Latter is a no-op in Python 2.
        quopri_msg = quopri.encodestring(MSG2.encode())

        # In Python 3, encodestring() returns bytes so decode to a string while
        # Python 2 compatability is still required.
        if not isinstance(quopri_msg, str):
            quopri_msg = quopri_msg.decode()

        # Add Content-Type and Content-Transfer-Encoding
        # headers to message
        header_quopri_msg = ('Content-Type: text/xml; charset=utf8\n'
                             'Content-Transfer-Encoding: quoted-printable\n'
                             '\n'
                             '%s' % quopri_msg)

        # We can't use crypto.sign as that assumes the use of the '-text' option
        # which cause the message to be interpreted as plaintext
        p1 = Popen([
            'openssl', 'smime', '-sign', '-inkey', TEST_KEY_FILE, '-signer',
            TEST_CERT_FILE
        ],
                   stdin=PIPE,
                   stdout=PIPE,
                   stderr=PIPE,
                   universal_newlines=True)

        signed_msg2, error = p1.communicate(header_quopri_msg)

        if error != '':
            self.fail(error)

        retrieved_msg, retrieved_dn = verify(signed_msg, TEST_CA_DIR, False)

        if not retrieved_dn == TEST_CERT_DN:
            self.fail("The DN of the verified message didn't match the cert.")

        if not retrieved_msg.strip() == MSG:
            self.fail("The verified messge didn't match the original.")

        retrieved_msg2, retrieved_dn2 = verify(signed_msg2, TEST_CA_DIR, False)

        if not retrieved_dn2 == TEST_CERT_DN:
            print(retrieved_dn2)
            print(TEST_CERT_DN)
            self.fail("The DN of the verified message didn't match the cert.")

        if not retrieved_msg2.strip() == MSG2:
            print(retrieved_msg2)
            print(MSG2)
            self.fail("The verified messge didn't match the original.")

        # Try empty string
        try:
            verify('', TEST_CA_DIR, False)
        except CryptoException:
            pass
        # Try rubbish
        try:
            verify('Bibbly bobbly', TEST_CA_DIR, False)
        except CryptoException:
            pass
        # Try None arguments
        try:
            verify('Bibbly bobbly', None, False)
        except CryptoException:
            pass
        try:
            verify(None, 'not a path', False)
        except CryptoException:
            pass
Exemplo n.º 11
0
    def test_verify(self):

        signed_msg = sign(MSG, TEST_CERT_FILE, TEST_KEY_FILE)

        # This is a manual 'fudge' to make MS2 appear like a
        # quoted-printable message when signed
        # Encode MSG2 so it's 'quoted-printable'
        quopri_msg = quopri.encodestring(MSG2)
        # Add Content-Type and Content-Transfer-Encoding
        # headers to message
        header_quopri_msg = ('Content-Type: text/xml; charset=utf8\n'
                             'Content-Transfer-Encoding: quoted-printable\n'
                             '\n'
                             '%s' % quopri_msg)

        # We can't use crypto.sign as that assumes the use of the '-text' option
        # which cause the message to be interpreted as plaintext
        p1 = Popen(['openssl', 'smime', '-sign', '-inkey', TEST_KEY_FILE, '-signer', TEST_CERT_FILE],
                   stdin=PIPE, stdout=PIPE, stderr=PIPE)

        signed_msg2, error = p1.communicate(header_quopri_msg)

        if error != '':
            self.fail(error)

        retrieved_msg, retrieved_dn = verify(signed_msg, TEST_CA_DIR, False)
        
        if not retrieved_dn == TEST_CERT_DN:
            self.fail("The DN of the verified message didn't match the cert.")
            
        if not retrieved_msg.strip() == MSG:
            self.fail("The verified messge didn't match the original.")
            
        retrieved_msg2, retrieved_dn2 = verify(signed_msg2, TEST_CA_DIR, False)
        
        if not retrieved_dn2 == TEST_CERT_DN:
            print retrieved_dn2
            print TEST_CERT_DN
            self.fail("The DN of the verified message didn't match the cert.")
            
        if not retrieved_msg2.strip() == MSG2:
            print retrieved_msg2
            print MSG2
            self.fail("The verified messge didn't match the original.")
            
        # Try empty string    
        try:
            verify('', TEST_CA_DIR, False)
        except CryptoException:
            pass
        # Try rubbish
        try:
            verify('Bibbly bobbly', TEST_CA_DIR, False)
        except CryptoException:
            pass
        # Try None arguments 
        try:
            verify('Bibbly bobbly', None, False)
        except CryptoException:
            pass
        try:
            verify(None, 'not a path', False)
        except CryptoException:
            pass
Exemplo n.º 12
0
        if text is None or text == '':
            return None, None
#        if not text.startswith('MIME-Version: 1.0'):
#            raise Ssm2Exception('Not a valid message.')

# encrypted - this could be nicer
        if 'application/pkcs7-mime' in text or 'application/x-pkcs7-mime' in text:
            try:
                text = crypto.decrypt(text, self._cert, self._key)
            except crypto.CryptoException, e:
                log.error('Failed to decrypt message: %s' % e)
                return None, None

        # always signed
        try:
            message, signer = crypto.verify(text, self._capath,
                                            self._check_crls)
        except crypto.CryptoException, e:
            log.error('Failed to verify message: %s' % e)
            return None, None

        if signer not in self._valid_dns:
            log.error('Message signer not in the valid DNs list: %s' % signer)
            return None, signer
        else:
            log.info('Valid signer: %s' % signer)

        return message, signer

    def _send_msg(self, message, msgid):
        '''
        Send one message using stomppy.  The message will be signed using