Пример #1
0
    def _startSendingNextPacket(self):
        "Helper: begin transmitting the next available packet."
        # There _is_ a next available packet, right?
        assert self.packets and self._isConnected
        pkt = self.packets.pop(0)

        if pkt.isJunk():
            control = "JUNK\r\n"
            serverControl = "RECEIVED\r\n"
            hashExtra = "JUNK"
            serverHashExtra = "RECEIVED JUNK"
        else:
            control = "SEND\r\n"
            serverControl = "RECEIVED\r\n"
            hashExtra = "SEND"
            serverHashExtra = "RECEIVED"
            EventStats.log.attemptedRelay()

        m = pkt.getContents()
        if m == 'RENEGOTIATE':
            # Renegotiate has been removed from the spec.
            return

        data = "".join([control, m, sha1(m + hashExtra)])
        assert len(data) == self.MESSAGE_LEN
        acceptedAck = serverControl + sha1(m + serverHashExtra)
        rejectedAck = "REJECTED\r\n" + sha1(m + "REJECTED")
        assert len(acceptedAck) == len(rejectedAck) == self.ACK_LEN
        self.expectedAcks.append((acceptedAck, rejectedAck))
        self.pendingPackets.append(pkt)
        self.beginWriting(data)
        self.nPacketsSent += 1
Пример #2
0
    def _startSendingNextPacket(self):
        "Helper: begin transmitting the next available packet."
        # There _is_ a next available packet, right?
        assert self.packets and self._isConnected
        pkt = self.packets.pop(0)

        if pkt.isJunk():
            control = "JUNK\r\n"
            serverControl = "RECEIVED\r\n"
            hashExtra = "JUNK"
            serverHashExtra = "RECEIVED JUNK"
        else:
            control = "SEND\r\n"
            serverControl = "RECEIVED\r\n"
            hashExtra = "SEND"
            serverHashExtra = "RECEIVED"
            EventStats.elog.attemptedRelay()

        m = pkt.getContents()
        if m == 'RENEGOTIATE':
            # Renegotiate has been removed from the spec.
            return

        data = "".join([control, m, sha1(m+hashExtra)])
        assert len(data) == self.MESSAGE_LEN
        acceptedAck = serverControl + sha1(m+serverHashExtra)
        rejectedAck = "REJECTED\r\n" + sha1(m+"REJECTED")
        assert len(acceptedAck) == len(rejectedAck) == self.ACK_LEN
        self.expectedAcks.append( (acceptedAck, rejectedAck) )
        self.pendingPackets.append(pkt)
        self.beginWriting(data)
        self.nPacketsSent += 1
Пример #3
0
    def onDataRead(self):
        while self.inbuflen >= self.MESSAGE_LEN:
            data = self.getInbuf(self.MESSAGE_LEN, clear=1)
            control = data[:SEND_CONTROL_LEN]
            pkt = data[SEND_CONTROL_LEN:-DIGEST_LEN]
            digest = data[-DIGEST_LEN:]
            if control == JUNK_CONTROL:
                expectedDigest = sha1(pkt + "JUNK")
                replyDigest = sha1(pkt + "RECEIVED JUNK")
                replyControl = RECEIVED_CONTROL
                isJunk = 1
            elif control == SEND_CONTROL:
                expectedDigest = sha1(pkt + "SEND")
                if self.rejectPackets:
                    replyDigest = sha1(pkt + "REJECTED")
                    replyControl = REJECTED_CONTROL
                else:
                    replyDigest = sha1(pkt + "RECEIVED")
                    replyControl = RECEIVED_CONTROL
                isJunk = 0
            else:
                LOG.warn(
                    "Unrecognized command (%r) from %s.  Closing connection.",
                    control, self.address)
                #failed
                self.startShutdown()
                return

            if expectedDigest != digest:
                LOG.warn("Invalid checksum from %s. Closing connection.",
                         self.address)
                #failed
                self.startShutdown()
                return
            else:
                if isJunk:
                    LOG.debug("Link padding received from %s; Checksum valid.",
                              self.address)
                else:
                    LOG.debug("Packet received from %s; Checksum valid.",
                              self.address)

            # Make sure we process the packet before we queue the ack.
            if isJunk:
                self.junkCallback()
            elif self.rejectPackets:
                self.rejectCallback()
            else:
                self.packetConsumer(pkt)

            # Queue the ack.
            self.beginWriting(replyControl + replyDigest)
Пример #4
0
    def onDataRead(self):
        while self.inbuflen >= self.MESSAGE_LEN:
            data = self.getInbuf(self.MESSAGE_LEN, clear=1)
            control = data[:SEND_CONTROL_LEN]
            pkt = data[SEND_CONTROL_LEN:-DIGEST_LEN]
            digest = data[-DIGEST_LEN:]
            if control == JUNK_CONTROL:
                expectedDigest = sha1(pkt+"JUNK")
                replyDigest = sha1(pkt+"RECEIVED JUNK")
                replyControl = RECEIVED_CONTROL
                isJunk = 1
            elif control == SEND_CONTROL:
                expectedDigest = sha1(pkt+"SEND")
                if self.rejectPackets:
                    replyDigest = sha1(pkt+"REJECTED")
                    replyControl = REJECTED_CONTROL
                else:
                    replyDigest = sha1(pkt+"RECEIVED")
                    replyControl = RECEIVED_CONTROL
                isJunk = 0
            else:
                LOG.warn("Unrecognized command (%r) from %s.  Closing connection.",
                         control, self.address)
                #failed
                self.startShutdown()
                return

            if expectedDigest != digest:
                LOG.warn("Invalid checksum from %s. Closing connection.",
                         self.address)
                #failed
                self.startShutdown()
                return
            else:
                if isJunk:
                    LOG.debug("Link padding received from %s; Checksum valid.",
                              self.address)
                else:
                    LOG.debug("Packet received from %s; Checksum valid.",
                              self.address)

            # Make sure we process the packet before we queue the ack.
            if isJunk:
                self.junkCallback()
            elif self.rejectPackets:
                self.rejectCallback()
            else:
                self.packetConsumer(pkt)

            # Queue the ack.
            self.beginWriting(replyControl+replyDigest)
Пример #5
0
 def reconstruct(self, store):
     """If any of the chunks in this message are pending reconstruction,
        reconstruct them in a given store."""
     if not self.readyChunks:
         return
     for chunkno in self.readyChunks.keys():
         # Get the first K fragments in the chunk. (list of h,fm)
         ch = self.fragmentsByChunk[chunkno].values()[:self.params.k]
         minDate = min([fm.insertedDate for h, fm in ch])
         # Build a list of (position-within-chunk, fragment-contents).
         frags = [(self.params.getPosition(fm.idx)[1],
                   store.messageContents(h)) for h,fm in ch]
         chunkText = "".join(self.params.getFEC().decode(frags))
         del frags
         fm2 = FragmentMetadata(messageid=self.messageid,
                                idx=chunkno, size=self.params.length,
                                isChunk=1, chunkNum=chunkno,
                                overhead=self.overhead,
                                insertedDate=minDate, nym=self.nym,
                                digest=sha1(chunkText))
         # Queue the chunk.
         h2 = store.queueMessageAndMetadata(chunkText, fm2)
         del chunkText
         # Remove superceded fragments.
         for h, fm in ch:
             store.removeMessage(h)
         # Update this MessageState object.
         self.fragmentsByChunk[chunkno] = {}
         del self.readyChunks[chunkno]
         self.addChunk(h2, fm2)
Пример #6
0
 def reconstruct(self, store):
     """If any of the chunks in this message are pending reconstruction,
        reconstruct them in a given store."""
     if not self.readyChunks:
         return
     for chunkno in self.readyChunks.keys():
         # Get the first K fragments in the chunk. (list of h,fm)
         ch = self.fragmentsByChunk[chunkno].values()[:self.params.k]
         minDate = min([fm.insertedDate for h, fm in ch])
         # Build a list of (position-within-chunk, fragment-contents).
         frags = [(self.params.getPosition(fm.idx)[1],
                   store.messageContents(h)) for h, fm in ch]
         chunkText = "".join(self.params.getFEC().decode(frags))
         del frags
         fm2 = FragmentMetadata(messageid=self.messageid,
                                idx=chunkno,
                                size=self.params.length,
                                isChunk=1,
                                chunkNum=chunkno,
                                overhead=self.overhead,
                                insertedDate=minDate,
                                nym=self.nym,
                                digest=sha1(chunkText))
         # Queue the chunk.
         h2 = store.queueMessageAndMetadata(chunkText, fm2)
         del chunkText
         # Remove superceded fragments.
         for h, fm in ch:
             store.removeMessage(h)
         # Update this MessageState object.
         self.fragmentsByChunk[chunkno] = {}
         del self.readyChunks[chunkno]
         self.addChunk(h2, fm2)
Пример #7
0
def _writeEncryptedFile(fname, password, magic, data):
    """Write 'data' into an encrypted file named 'fname', replacing it
       if necessary.  Encrypts the data with the password 'password',
       and uses the filetype 'magic'."""
    assert len(magic) == MAGIC_LEN
    prng = getCommonPRNG()
    length = struct.pack("!L", len(data))
    paddingLen = ceilDiv(len(data), 1024) * 1024 - len(data)
    padding = prng.getBytes(paddingLen)
    data = "".join([length, data, padding])
    salt = prng.getBytes(SALT_LEN)
    key = sha1(salt + password + salt)[:AES_KEY_LEN]
    digest = sha1("".join([data, salt, magic]))
    encrypted = ctr_crypt(data + digest, key)
    contents = "".join([magic, "\x00", salt, encrypted])
    writeFile(fname,
              armorText(contents, "TYPE III KEYRING", [("Version", "0.1")]))
Пример #8
0
def _writeEncryptedFile(fname, password, magic, data):
    """Write 'data' into an encrypted file named 'fname', replacing it
       if necessary.  Encrypts the data with the password 'password',
       and uses the filetype 'magic'."""
    assert len(magic) == MAGIC_LEN
    prng = getCommonPRNG()
    length = struct.pack("!L", len(data))
    paddingLen = ceilDiv(len(data), 1024)*1024 - len(data)
    padding = prng.getBytes(paddingLen)
    data = "".join([length,data,padding])
    salt = prng.getBytes(SALT_LEN)
    key = sha1(salt+password+salt)[:AES_KEY_LEN]
    digest = sha1("".join([data,salt,magic]))
    encrypted = ctr_crypt(data+digest, key)
    contents = "".join([magic,"\x00",salt,encrypted])
    writeFile(fname, armorText(contents,
                               "TYPE III KEYRING", [("Version","0.1")]))
Пример #9
0
def _readEncryptedFile(fname, password, magicList):
    """Read encrypted data from the file named 'fname', using the password
       'password' and checking for a magic string contained in 'magicList'.
       Returns the magic string and the plaintext file contents on success.

       If the file is corrupt or the password is wrong, raises BadPassword.
       If the magic is incorrect, raises ValueError.
    """
    assert list(map(len, magicList)) == [8]*len(magicList)

    text = readFile(fname)
    r = unarmorText(text, ["TYPE III KEYRING"])
    if len(r) != 1:
        raise ValueError("Bad ascii armor on keyring")
    tp, headers, s = r[0]
    assert tp == "TYPE III KEYRING"
    vers = [ v for k,v in headers if k == 'Version' ]
    if not vers or vers[0] != '0.1':
        raise ValueError("Unrecognized version on keyring")

    if len(s) < MAGIC_LEN+1 or s[MAGIC_LEN] != '\x00':
        raise ValueError("Unrecognized encryption format on %s"%fname)
    if s[:MAGIC_LEN] not in magicList:
        raise ValueError("Invalid versioning on %s"%fname)
    magic = s[:8]
    s = s[MAGIC_LEN+1:]
    if len(s) < 28:
        raise MixError("File %s is too short."%fname)
    salt = s[:SALT_LEN]
    s = s[SALT_LEN:]
    key = sha1(salt+password+salt)[:AES_KEY_LEN]
    s = ctr_crypt(s, key)
    data = s[:-DIGEST_LEN]
    digest = s[-DIGEST_LEN:]
    if digest != sha1(data+salt+magic):
        raise BadPassword()

    # We've decrypted it; now let's extract the data from the padding.
    if len(data) < 4:
        raise MixError("File %s is too short"%fname)
    length, = struct.unpack("!L", data[:4])
    if len(data) < length+4:
        raise MixError("File %s is too short"%fname)

    return magic, data[4:4+length]
Пример #10
0
def _readEncryptedFile(fname, password, magicList):
    """Read encrypted data from the file named 'fname', using the password
       'password' and checking for a magic string contained in 'magicList'.
       Returns the magic string and the plaintext file contents on success.

       If the file is corrupt or the password is wrong, raises BadPassword.
       If the magic is incorrect, raises ValueError.
    """
    assert list(map(len, magicList)) == [8] * len(magicList)

    text = readFile(fname)
    r = unarmorText(text, ["TYPE III KEYRING"])
    if len(r) != 1:
        raise ValueError("Bad ascii armor on keyring")
    tp, headers, s = r[0]
    assert tp == "TYPE III KEYRING"
    vers = [v for k, v in headers if k == 'Version']
    if not vers or vers[0] != '0.1':
        raise ValueError("Unrecognized version on keyring")

    if len(s) < MAGIC_LEN + 1 or s[MAGIC_LEN] != '\x00':
        raise ValueError("Unrecognized encryption format on %s" % fname)
    if s[:MAGIC_LEN] not in magicList:
        raise ValueError("Invalid versioning on %s" % fname)
    magic = s[:8]
    s = s[MAGIC_LEN + 1:]
    if len(s) < 28:
        raise MixError("File %s is too short." % fname)
    salt = s[:SALT_LEN]
    s = s[SALT_LEN:]
    key = sha1(salt + password + salt)[:AES_KEY_LEN]
    s = ctr_crypt(s, key)
    data = s[:-DIGEST_LEN]
    digest = s[-DIGEST_LEN:]
    if digest != sha1(data + salt + magic):
        raise BadPassword()

    # We've decrypted it; now let's extract the data from the padding.
    if len(data) < 4:
        raise MixError("File %s is too short" % fname)
    length, = struct.unpack("!L", data[:4])
    if len(data) < length + 4:
        raise MixError("File %s is too short" % fname)

    return magic, data[4:4 + length]
Пример #11
0
def _getMultisignedDirectoryDigest(directory):
    try:
        if directory.startswith("[Directory-Info]"):
            idx = 0
        else:
            idx = directory.index("\n[Directory-Info]\n") + 1
    except IndexError:
        raise ConfigError("No [Directory-Info] found.")
    digest = sha1(directory[idx:])
    return digest
Пример #12
0
def _getMultisignedDirectoryDigest(directory):
    try:
        if directory.startswith("[Directory-Info]"):
            idx = 0
        else:
            idx = directory.index("\n[Directory-Info]\n")+1
    except IndexError:
        raise ConfigError("No [Directory-Info] found.")
    digest = sha1(directory[idx:])
    return digest
Пример #13
0
def _getDigestImpl(info, regex, digestField=None, sigField=None, rsa=None):
    """Helper method.  Calculates the correct digest of a server descriptor
       or directory
       (as provided in a string).  If rsa is provided, signs the digest and
       creates a new descriptor.  Otherwise just returns the digest.

       info -- the string to digest or sign.
       regex -- a compiled regex that matches the line containing the digest
          and the line containing the signature.
       digestField -- If not signing, None.  Otherwise, the name of the digest
          field.
       sigField -- If not signing, None.  Otherwise, the name of the signature
          field.
       rsa -- our public key
       """
    info = _cleanForDigest(info)

    def replaceFn(m):
        s = m.group(0)
        return s[:s.index(':') + 1]

    info = regex.sub(replaceFn, info, 2)
    digest = sha1(info)

    if rsa is None:
        return digest

    signature = pk_sign(digest, rsa)
    digest = formatBase64(digest)
    signature = formatBase64(signature)

    def replaceFn2(s,
                   digest=digest,
                   signature=signature,
                   digestField=digestField,
                   sigField=sigField):
        if s.group(0).startswith(digestField):
            return "%s: %s" % (digestField, digest)
        else:
            assert s.group(0).startswith(sigField)
            return "%s: %s" % (sigField, signature)

    info = regex.sub(replaceFn2, info, 2)
    return info
Пример #14
0
def _getDigestImpl(info, regex, digestField=None, sigField=None, rsa=None):
    """Helper method.  Calculates the correct digest of a server descriptor
       or directory
       (as provided in a string).  If rsa is provided, signs the digest and
       creates a new descriptor.  Otherwise just returns the digest.

       info -- the string to digest or sign.
       regex -- a compiled regex that matches the line containing the digest
          and the line containing the signature.
       digestField -- If not signing, None.  Otherwise, the name of the digest
          field.
       sigField -- If not signing, None.  Otherwise, the name of the signature
          field.
       rsa -- our public key
       """
    info = _cleanForDigest(info)
    def replaceFn(m):
        s = m.group(0)
        return s[:s.index(':')+1]
    info = regex.sub(replaceFn, info, 2)
    digest = sha1(info)

    if rsa is None:
        return digest

    signature = pk_sign(digest,rsa)
    digest = formatBase64(digest)
    signature = formatBase64(signature)
    def replaceFn2(s, digest=digest, signature=signature,
                   digestField=digestField, sigField=sigField):
        if s.group(0).startswith(digestField):
            return "%s: %s" % (digestField, digest)
        else:
            assert s.group(0).startswith(sigField)
            return "%s: %s" % (sigField, signature)

    info = regex.sub(replaceFn2, info, 2)
    return info
Пример #15
0
 def getIdentityDigest(self):
     """Return the digest of this server's public identity key.
        (SHA-1 digest of ASN.1-encoded key).
     """
     return sha1(pk_encode_public_key(self.getIdentity()))
Пример #16
0
 def getKeyDigest(self):
     """Returns a hash of this server's identity key."""
     return sha1(pk_encode_public_key(self['Server']['Identity']))
Пример #17
0
    def addFragment(self, fragmentPacket, nym=None, now=None, verbose=0):
        """Given an instance of mixminion.Packet.FragmentPayload, record
           the fragment if appropriate and update the state of the
           fragment pool if necessary.  Returns the message ID that was
           updated, or None if the fragment was redundant or misformed.

              fragmentPacket -- the new fragment to add.
              nym -- a string representing the identity that received this
                  fragment.  [Tracking nyms is important, to prevent an
                  attack where we send 2 fragments to 'MarkTwain' and 2
                  fragments to 'SClemens', and see that the message is
                  reconstructed.]
              verbose -- if true, log information at the INFO level;
                  otherwise, log at DEBUG.
        """
        if verbose:
            say = LOG.info
        else:
            say = LOG.debug
        if now is None:
            now = time.time()
        today = previousMidnight(now)

        # If the message has already been rejected or completed, we can
        # drop this packet.
        s = self.db.getStatusAndTime(fragmentPacket.msgID)
        if s:
            say("Dropping fragment of %s message %r",
                s[0].lower(), disp64(fragmentPacket.msgID,12))
            return None

        # Otherwise, create a new metadata object for this fragment...
        meta = FragmentMetadata(messageid=fragmentPacket.msgID,
                                 idx=fragmentPacket.index,
                                 size=fragmentPacket.msgLen,
                                 isChunk=0,
                                 chunkNum=None,
                                 overhead=fragmentPacket.getOverhead(),
                                 insertedDate=today,
                                 nym=nym,
                                 digest=sha1(fragmentPacket.data))
        # ... and allocate or find the MessageState for this message.
        state = self._getState(meta)
        try:
            # Check whether we can/should add this message, but do not
            # add it.
            state.addFragment(None, meta, noop=1)
            # No exception was thrown; queue the message.
            h = self.store.queueMessageAndMetadata(fragmentPacket.data, meta)
            # And *now* update the message state.
            state.addFragment(h, meta)
            say("Stored fragment %s of message %s",
                fragmentPacket.index+1, disp64(fragmentPacket.msgID,12))
            return fragmentPacket.msgID
        except MismatchedFragment, s:
            # Remove the other fragments, mark msgid as bad.
            LOG.warn("Found inconsistent fragment %s in message %s: %s",
                     fragmentPacket.index+1, disp64(fragmentPacket.msgID,12),
                     s)
            self._deleteMessageIDs({ meta.messageid : 1}, "REJECTED", now)
            return None
Пример #18
0
 def computeHash(self):
     """Update the hash field of this payload to correspond to the hash
        of the data."""
     self.hash = sha1(self.data)
Пример #19
0
 def _encodeKey(self, surb):
     return binascii.b2a_hex(sha1(surb.pack()))
Пример #20
0
 def computeHash(self):
     """Update the hash field of this payload to correspond to the hash
        of the data."""
     self.hash = "X" * DIGEST_LEN
     p = self.pack()
     self.hash = sha1(p[23:])
Пример #21
0
 def getHexDigest(self):
     """DOCDOC"""
     return binascii.b2a_hex(sha1(self.pack()))
Пример #22
0
 def _encodeKey(self, surb):
     return binascii.b2a_hex(sha1(surb.pack()))
Пример #23
0
    def addFragment(self, fragmentPacket, nym=None, now=None, verbose=0):
        """Given an instance of mixminion.Packet.FragmentPayload, record
           the fragment if appropriate and update the state of the
           fragment pool if necessary.  Returns the message ID that was
           updated, or None if the fragment was redundant or misformed.

              fragmentPacket -- the new fragment to add.
              nym -- a string representing the identity that received this
                  fragment.  [Tracking nyms is important, to prevent an
                  attack where we send 2 fragments to 'MarkTwain' and 2
                  fragments to 'SClemens', and see that the message is
                  reconstructed.]
              verbose -- if true, log information at the INFO level;
                  otherwise, log at DEBUG.
        """
        if verbose:
            say = LOG.info
        else:
            say = LOG.debug
        if now is None:
            now = time.time()
        today = previousMidnight(now)

        # If the message has already been rejected or completed, we can
        # drop this packet.
        s = self.db.getStatusAndTime(fragmentPacket.msgID)
        if s:
            say("Dropping fragment of %s message %r", s[0].lower(),
                disp64(fragmentPacket.msgID, 12))
            return None

        # Otherwise, create a new metadata object for this fragment...
        meta = FragmentMetadata(messageid=fragmentPacket.msgID,
                                idx=fragmentPacket.index,
                                size=fragmentPacket.msgLen,
                                isChunk=0,
                                chunkNum=None,
                                overhead=fragmentPacket.getOverhead(),
                                insertedDate=today,
                                nym=nym,
                                digest=sha1(fragmentPacket.data))
        # ... and allocate or find the MessageState for this message.
        state = self._getState(meta)
        try:
            # Check whether we can/should add this message, but do not
            # add it.
            state.addFragment(None, meta, noop=1)
            # No exception was thrown; queue the message.
            h = self.store.queueMessageAndMetadata(fragmentPacket.data, meta)
            # And *now* update the message state.
            state.addFragment(h, meta)
            say("Stored fragment %s of message %s", fragmentPacket.index + 1,
                disp64(fragmentPacket.msgID, 12))
            return fragmentPacket.msgID
        except MismatchedFragment, s:
            # Remove the other fragments, mark msgid as bad.
            LOG.warn("Found inconsistent fragment %s in message %s: %s",
                     fragmentPacket.index + 1, disp64(fragmentPacket.msgID,
                                                      12), s)
            self._deleteMessageIDs({meta.messageid: 1}, "REJECTED", now)
            return None
Пример #24
0
 def getKeyDigest(self):
     """Returns a hash of this server's identity key."""
     return sha1(pk_encode_public_key(self['Server']['Identity']))
Пример #25
0
 def computeHash(self):
     """Update the hash field of this payload to correspond to the hash
        of the data."""
     self.hash = "X"*DIGEST_LEN
     p = self.pack()
     self.hash = sha1(p[23:])
Пример #26
0
 def getIdentityDigest(self):
     """Return the digest of this server's public identity key.
        (SHA-1 digest of ASN.1-encoded key).
     """
     return sha1(pk_encode_public_key(self.getIdentity()))
Пример #27
0
 def getHexDigest(self):
     """DOCDOC"""
     return binascii.b2a_hex(sha1(self.pack()))
Пример #28
0
                return  # All is well.
            else:
                # We recognize the key, but some other identity signed it.
                raise MixProtocolBadAuth(
                    "Mismatch between expected and actual key ID")
        except KeyError:
            pass

        # We haven't found an identity for this pk yet.  Try to check the
        # signature on it.
        try:
            identity = tls.verify_cert_and_get_identity_pk()
        except _ml.TLSError, e:
            raise MixProtocolBadAuth("Invalid KeyID (allegedly) from %s: %s" %
                                     serverName)

        # Okay, remember who has signed this certificate.
        hashed_identity = sha1(identity.encode_key(public=1))
        LOG.trace("Remembering valid certificate for %s", serverName)
        self.cache[hashed_peer_pk] = hashed_identity

        # Note: we don't need to worry about two identities signing the
        # same certificate.  While this *is* possible to do, it's useless:
        # You could get someone else's certificate and sign it, but you
        # couldn't start up a TLS connection with that certificate without
        # stealing their private key too.

        # Was the signer the right person?
        if hashed_identity != targetKeyID:
            raise MixProtocolBadAuth("Invalid KeyID for %s" % serverName)
Пример #29
0
                return # All is well.
            else:
                # We recognize the key, but some other identity signed it.
                raise MixProtocolBadAuth(
                    "Mismatch between expected and actual key ID")
        except KeyError:
            pass

        # We haven't found an identity for this pk yet.  Try to check the
        # signature on it.
        try:
            identity = tls.verify_cert_and_get_identity_pk()
        except _ml.TLSError, e:
            raise MixProtocolBadAuth("Invalid KeyID (allegedly) from %s: %s"
                                   %serverName)

        # Okay, remember who has signed this certificate.
        hashed_identity = sha1(identity.encode_key(public=1))
        log.trace("Remembering valid certificate for %s", serverName)
        self.cache[hashed_peer_pk] = hashed_identity

        # Note: we don't need to worry about two identities signing the
        # same certificate.  While this *is* possible to do, it's useless:
        # You could get someone else's certificate and sign it, but you
        # couldn't start up a TLS connection with that certificate without
        # stealing their private key too.

        # Was the signer the right person?
        if hashed_identity != targetKeyID:
            raise MixProtocolBadAuth("Invalid KeyID for %s" % serverName)
Пример #30
0
class PeerCertificateCache:
    """A PeerCertificateCache validates certificate chains from MMTP servers,
       and remembers which chains we've already seen and validated."""

    ## Fields
    # cache: A map from peer (temporary) KeyID's to a (signing) KeyID.
    def __init__(self):
        self.cache = {}

    def check(self, tls, targetKeyID, serverName):
        """Check whether the certificate chain on the TLS connection 'tls'
           is valid, current, and matches the keyID 'targetKeyID'.  If so,
           return.  If not, raise MixProtocolBadAuth.  Display all messages
           using the server 'serverName'.
        """

        # First, make sure the certificate is neither premature nor expired.
        try:
            tls.check_cert_alive()
        except _ml.TLSError, e:
            s = str(e)
            skewed = 0
            notBefore, notAfter = tls.get_cert_lifetime()
            # XXXX 'stringContains' is not the best possible check here...
            if stringContains(s, "expired"):
                s += " [expired at %s]" % notAfter
                skewed = 1
            elif stringContains(s, "not yet valid"):
                s += " [not valid until %s]" % notBefore
                skewed = 1
            if skewed:
                s += " (One of you may have a skewed clock or wrong time zone)"
            raise MixProtocolBadAuth("Invalid certificate from %s: %s " %
                                     (serverName, s))

        # If we don't care whom we're talking to, we don't need to check
        # them out.
        if targetKeyID is None:
            return

        # Get the KeyID for the peer (temporary) key.
        hashed_peer_pk = sha1(tls.get_peer_cert_pk().encode_key(public=1))

        # Before 0.0.4alpha, a server's keyID was a hash of its current
        # TLS public key.  In 0.0.4alpha, we allowed this for backward
        # compatibility.  As of 0.0.4alpha2, since we've dropped backward
        # compatibility with earlier packet formats, we drop certificate
        # compatibility as well.
        if targetKeyID == hashed_peer_pk:
            raise MixProtocolBadAuth(
                "Pre-0.0.4 (non-rotatable) certificate from %s" % serverName)

        try:
            if targetKeyID == self.cache[hashed_peer_pk]:
                # We recognize the key, and have already seen it to be
                # signed by the target identity.
                LOG.trace("Got a cached certificate from %s", serverName)
                return  # All is well.
            else:
                # We recognize the key, but some other identity signed it.
                raise MixProtocolBadAuth(
                    "Mismatch between expected and actual key ID")
        except KeyError:
            pass

        # We haven't found an identity for this pk yet.  Try to check the
        # signature on it.
        try:
            identity = tls.verify_cert_and_get_identity_pk()
        except _ml.TLSError, e:
            raise MixProtocolBadAuth("Invalid KeyID (allegedly) from %s: %s" %
                                     serverName)
Пример #31
0
 def computeHash(self):
     """Update the hash field of this payload to correspond to the hash
        of the data."""
     self.hash = sha1(self.data)