Exemple #1
0
def _generateDirectory(identity, status,
                      servers, goodServerNames,
                      voters, validAfter,
                      clientVersions, serverVersions):

    assert status in ("vote", "consensus")
    va = formatDate(previousMidnight(validAfter))
    vu = formatDate(previousMidnight(validAfter)+24*60*60+5)
    rec = [ s.lower() for s in goodServerNames[:] ]
    rec.sort()
    rec = ", ".join(rec)
    v = []
    voters.sort()
    for keyid, urlbase in voters:
        v.append("Voting-Server: %s %s\n"
                 % (keyid, urlbase))
    servers = sortServerList(servers)

    cvers = ", ".join(sortVersionList(clientVersions))
    svers = ", ".join(sortVersionList(serverVersions))
    dirInfo = ("[Directory-Info]\n"
               "Version: 0.3\n"
               "Status: %s\n"
               "Valid-After: %s\n"
               "Valid-Until: %s\n"
               "Recommended-Servers: %s\n%s"
               "[Recommended-Software]\n"
               "MixminionClient: %s\n"
               "MixminionServer: %s\n")%(status, va, vu, rec, "".join(v),
                                         cvers, svers)

    unsigned = "".join([dirInfo]+[s._originalContents for s in servers])
    signature = getDirectorySignature(unsigned, identity)
    return signature+unsigned
Exemple #2
0
def _generateDirectory(identity, status,
                      servers, goodServerNames,
                      voters, validAfter,
                      clientVersions, serverVersions):

    assert status in ("vote", "consensus")
    va = formatDate(previousMidnight(validAfter))
    vu = formatDate(previousMidnight(validAfter)+24*60*60+5)
    rec = [ s.lower() for s in goodServerNames[:] ]
    rec.sort()
    rec = ", ".join(rec)
    v = []
    voters.sort()
    for keyid, urlbase in voters:
        v.append("Voting-Server: %s %s\n"
                 % (keyid, urlbase))
    servers = sortServerList(servers)

    cvers = ", ".join(sortVersionList(clientVersions))
    svers = ", ".join(sortVersionList(serverVersions))
    dirInfo = ("[Directory-Info]\n"
               "Version: 0.3\n"
               "Status: %s\n"
               "Valid-After: %s\n"
               "Valid-Until: %s\n"
               "Recommended-Servers: %s\n%s"
               "[Recommended-Software]\n"
               "MixminionClient: %s\n"
               "MixminionServer: %s\n")%(status, va, vu, rec, "".join(v),
                                         cvers, svers)

    unsigned = "".join([dirInfo]+[s._originalContents for s in servers])
    signature = getDirectorySignature(unsigned, identity)
    return signature+unsigned
Exemple #3
0
 def queuePacket(self, packet, routing, now=None):
     """Insert the 32K packet 'packet' (to be delivered to 'routing')
        into the queue.  Return the handle of the newly inserted packet."""
     if now is None:
         now = time.time()
     mixminion.ClientMain.clientLock()
     try:
         fmt = ("PACKET-0", packet, routing, previousMidnight(now))
         meta = ("V0", routing, previousMidnight(now))
         return self.store.queueObjectAndMetadata(fmt, meta)
     finally:
         mixminion.ClientMain.clientUnlock()
Exemple #4
0
 def queuePacket(self, packet, routing, now=None):
     """Insert the 32K packet 'packet' (to be delivered to 'routing')
        into the queue.  Return the handle of the newly inserted packet."""
     if now is None:
         now = time.time()
     mixminion.ClientMain.clientLock()
     try:
         fmt = ("PACKET-0", packet, routing, previousMidnight(now))
         meta = ("V0", routing, previousMidnight(now))
         return self.store.queueObjectAndMetadata(fmt,meta)
     finally:
         mixminion.ClientMain.clientUnlock()
Exemple #5
0
    def _setNextRotation(self, now=None):
        """Helper function: calculate the time when we next rotate the log."""
        # ???? Lock to 24-hour cycle

        # This is a little weird.  We won't save *until*:
        #       - .75 * rotateInterval seconds are accumulated.
        #  AND  - rotateInterval seconds have elapsed since the last
        #         rotation.
        #
        # IF the rotation interval is divisible by one hour, we also
        #  round to the hour, up to 5 minutes down and 55 up.
        if not now: now = time()

        accumulatedTime = self.accumulatedTime + (now - self.lastSave)
        secToGo = max(0, self.rotateInterval * 0.75 - accumulatedTime)
        self.nextRotation = max(self.lastRotation + self.rotateInterval,
                                now + secToGo)

        if self.nextRotation < now:
            self.nextRotation = now

        if (self.rotateInterval % 3600) == 0:
            mid = previousMidnight(self.nextRotation)
            rest = self.nextRotation - mid
            self.nextRotation = mid + 3600 * floorDiv(rest+55*60, 3600)
Exemple #6
0
    def _setNextRotation(self, now=None):
        """Helper function: calculate the time when we next rotate the log."""
        # ???? Lock to 24-hour cycle

        # This is a little weird.  We won't save *until*:
        #       - .75 * rotateInterval seconds are accumulated.
        #  AND  - rotateInterval seconds have elapsed since the last
        #         rotation.
        #
        # IF the rotation interval is divisible by one hour, we also
        #  round to the hour, up to 5 minutes down and 55 up.
        if not now: now = time()

        accumulatedTime = self.accumulatedTime + (now - self.lastSave)
        secToGo = max(0, self.rotateInterval * 0.75 - accumulatedTime)
        self.nextRotation = max(self.lastRotation + self.rotateInterval,
                                now + secToGo)

        if self.nextRotation < now:
            self.nextRotation = now

        if (self.rotateInterval % 3600) == 0:
            mid = previousMidnight(self.nextRotation)
            rest = self.nextRotation - mid
            self.nextRotation = mid + 3600 * floorDiv(rest + 55 * 60, 3600)
Exemple #7
0
 def markStatus(self, msgid, status, today=None):
     """Note fragments for a message with message ID 'msgid' should no
        longer be stored.  'status' is one of 'COMPLETED' or 'REJECTED',
        depending on whether the message was delivered or undeliverable."""
     assert status in ("COMPLETED", "REJECTED")
     if today is None:
         today = time.time()
     today = previousMidnight(today)
     self[msgid] = (status, today)
Exemple #8
0
 def markStatus(self, msgid, status, today=None):
     """Note fragments for a message with message ID 'msgid' should no
        longer be stored.  'status' is one of 'COMPLETED' or 'REJECTED',
        depending on whether the message was delivered or undeliverable."""
     assert status in ("COMPLETED", "REJECTED")
     if today is None:
         today = time.time()
     today = previousMidnight(today)
     self[msgid] = (status, today)
Exemple #9
0
    def createKeys(self, num=1, startAt=None):
        """Generate 'num' public keys for this server. If startAt is provided,
           make the first key become valid at 'startAt'.  Otherwise, make the
           first key become valid right after the last key we currently have
           expires.  If we have no keys now, make the first key start now."""
        # FFFF Use this.
        #password = None

        if startAt is None:
            if self.keySets:
                startAt = self.keySets[-1][1] + 60
                if startAt < time.time():
                    startAt = time.time() + 60
            else:
                startAt = time.time() + 60

        startAt = previousMidnight(startAt)

        firstKey, lastKey = self.keyRange

        for _ in xrange(num):
            if firstKey == sys.maxint:
                keynum = firstKey = lastKey = 1
            elif firstKey > 1:
                firstKey -= 1
                keynum = firstKey
            else:
                lastKey += 1
                keynum = lastKey

            keyname = "%04d" % keynum

            lifetime = self.config['Server']['PublicKeyLifetime'].getSeconds()
            nextStart = startAt + lifetime

            LOG.info("Generating key %s to run from %s through %s (GMT)",
                     keyname, formatDate(startAt),
                     formatDate(nextStart - 3600))
            generateServerDescriptorAndKeys(config=self.config,
                                            identityKey=self.getIdentityKey(),
                                            keyname=keyname,
                                            keydir=self.keyDir,
                                            hashdir=self.hashDir,
                                            validAt=startAt)
            startAt = nextStart

        self.checkKeys()
Exemple #10
0
    def createKeys(self, num=1, startAt=None):
        """Generate 'num' public keys for this server. If startAt is provided,
           make the first key become valid at 'startAt'.  Otherwise, make the
           first key become valid right after the last key we currently have
           expires.  If we have no keys now, make the first key start now."""
        # FFFF Use this.
        #password = None

        if startAt is None:
            if self.keySets:
                startAt = self.keySets[-1][1]+60
                if startAt < time.time():
                    startAt = time.time()+60
            else:
                startAt = time.time()+60

        startAt = previousMidnight(startAt)

        firstKey, lastKey = self.keyRange

        for _ in xrange(num):
            if firstKey == sys.maxint:
                keynum = firstKey = lastKey = 1
            elif firstKey > 1:
                firstKey -= 1
                keynum = firstKey
            else:
                lastKey += 1
                keynum = lastKey

            keyname = "%04d" % keynum

            lifetime = self.config['Server']['PublicKeyLifetime'].getSeconds()
            nextStart = startAt + lifetime

            LOG.info("Generating key %s to run from %s through %s (GMT)",
                     keyname, formatDate(startAt),
                     formatDate(nextStart-3600))
            generateServerDescriptorAndKeys(config=self.config,
                                            identityKey=self.getIdentityKey(),
                                            keyname=keyname,
                                            keydir=self.keyDir,
                                            hashdir=self.hashDir,
                                            validAt=startAt)
            startAt = nextStart

        self.checkKeys()
Exemple #11
0
    def _deleteMessageIDs(self, messageIDSet, why, today=None):
        """Helper function. Remove all the fragments and chunks associated
           with a given message, and mark the message as delivered or
           undeliverable.

              messageIDSet -- a map from 20-byte messageID to 1.
              why -- 'REJECTED' or 'COMPLETED' or '?'
        """
        assert why in ("REJECTED", "COMPLETED", "?")
        if not messageIDSet:
            return
        if today is None:
            today = time.time()
        today = previousMidnight(today)
        if why == 'REJECTED':
            LOG.debug("Removing bogus messages by IDs: %s",
                      messageIDSet.keys())
        elif why == "COMPLETED":
            LOG.debug("Removing completed messages by IDs: %s",
                      messageIDSet.keys())
        else:
            LOG.debug("Removing messages by IDs: %s",
                      messageIDSet.keys())

        for mid in messageIDSet.keys():
            if why == "?":
                state = self.states[mid]
                if state.isDone:
                    whythis = "COMPLETED"
                else:
                    whythis = "REJECTED"
            else:
                whythis = why
            self.db.markStatus(mid, whythis, today)
            try:
                del self.states[mid]
            except KeyError:
                pass
        for h, fm in self.store._metadata_cache.items():
            if messageIDSet.has_key(fm.messageid):
                self.store.removeMessage(h)
Exemple #12
0
    def _deleteMessageIDs(self, messageIDSet, why, today=None):
        """Helper function. Remove all the fragments and chunks associated
           with a given message, and mark the message as delivered or
           undeliverable.

              messageIDSet -- a map from 20-byte messageID to 1.
              why -- 'REJECTED' or 'COMPLETED' or '?'
        """
        assert why in ("REJECTED", "COMPLETED", "?")
        if not messageIDSet:
            return
        if today is None:
            today = time.time()
        today = previousMidnight(today)
        if why == 'REJECTED':
            LOG.debug("Removing bogus messages by IDs: %s",
                      messageIDSet.keys())
        elif why == "COMPLETED":
            LOG.debug("Removing completed messages by IDs: %s",
                      messageIDSet.keys())
        else:
            LOG.debug("Removing messages by IDs: %s", messageIDSet.keys())

        for mid in messageIDSet.keys():
            if why == "?":
                state = self.states[mid]
                if state.isDone:
                    whythis = "COMPLETED"
                else:
                    whythis = "REJECTED"
            else:
                whythis = why
            self.db.markStatus(mid, whythis, today)
            try:
                del self.states[mid]
            except KeyError:
                pass
        for h, fm in self.store._metadata_cache.items():
            if messageIDSet.has_key(fm.messageid):
                self.store.removeMessage(h)
Exemple #13
0
def generateServerDescriptorAndKeys(config, identityKey, keydir, keyname,
                                    hashdir, validAt=None, now=None,
                                    useServerKeys=0, validUntil=None):
    """Generate and sign a new server descriptor, and generate all the keys to
       go with it.

          config -- Our ServerConfig object.
          identityKey -- This server's private identity key
          keydir -- The root directory for storing key sets.
          keyname -- The name of this new key set within keydir
          hashdir -- The root directory for storing hash logs.
          validAt -- The starting time (in seconds) for this key's lifetime.
          useServerKeys -- If true, try to read an existing keyset from
               (keydir,keyname,hashdir) rather than generating a fresh one.
          validUntil -- Time at which the generated descriptor should
               expire.
    """
    if useServerKeys:
        serverKeys = ServerKeyset(keydir, keyname, hashdir)
        serverKeys.load()
        packetKey = serverKeys.packetKey
    else:
        # First, we generate both of our short-term keys...
        packetKey = mixminion.Crypto.pk_generate(PACKET_KEY_BYTES*8)

        # ...and save them to disk, setting up our directory structure while
        # we're at it.
        serverKeys = ServerKeyset(keydir, keyname, hashdir)
        serverKeys.packetKey = packetKey
        serverKeys.save()

    # FFFF unused
    # allowIncoming = config['Incoming/MMTP'].get('Enabled', 0)

    # Now, we pull all the information we need from our configuration.
    nickname = config['Server']['Nickname']
    contact = config['Server']['Contact-Email']
    fingerprint = config['Server']['Contact-Fingerprint']
    comments = config['Server']['Comments']
    if not now:
        now = time.time()
    if not validAt:
        validAt = now

    insecurities = config.getInsecurities()
    if insecurities:
        secure = "no"
    else:
        secure = "yes"

    # Calculate descriptor and X509 certificate lifetimes.
    # (Round validAt to previous midnight.)
    validAt = mixminion.Common.previousMidnight(validAt+30)
    if not validUntil:
        keyLifetime = config['Server']['PublicKeyLifetime'].getSeconds()
        validUntil = previousMidnight(validAt + keyLifetime + 30)

    mmtpProtocolsIn = mixminion.server.MMTPServer.MMTPServerConnection \
                      .PROTOCOL_VERSIONS[:]
    mmtpProtocolsOut = mixminion.server.MMTPServer.MMTPClientConnection \
                       .PROTOCOL_VERSIONS[:]
    mmtpProtocolsIn.sort()
    mmtpProtocolsOut.sort()
    mmtpProtocolsIn = ",".join(mmtpProtocolsIn)
    mmtpProtocolsOut = ",".join(mmtpProtocolsOut)


    #XXXX009 remove: hasn't been checked since 007 or used since 005.
    identityKeyID = formatBase64(
                      mixminion.Crypto.sha1(
                          mixminion.Crypto.pk_encode_public_key(identityKey)))

    fields = {
        # XXXX009 remove: hasn't been checked since 007.
        "IP": config['Incoming/MMTP'].get('IP', "0.0.0.0"),
        "Hostname": config['Incoming/MMTP'].get('Hostname', None),
        "Port": config['Incoming/MMTP'].get('Port', 0),
        "Nickname": nickname,
        "Identity":
           formatBase64(mixminion.Crypto.pk_encode_public_key(identityKey)),
        "Published": formatTime(now),
        "ValidAfter": formatDate(validAt),
        "ValidUntil": formatDate(validUntil),
        "PacketKey":
           formatBase64(mixminion.Crypto.pk_encode_public_key(packetKey)),
        "KeyID": identityKeyID,
        "MMTPProtocolsIn" : mmtpProtocolsIn,
        "MMTPProtocolsOut" : mmtpProtocolsOut,
        "PacketVersion" : mixminion.Packet.PACKET_VERSION,
        "mm_version" : mixminion.__version__,
        "Secure" : secure,
        "Contact" : contact,
        }

    # If we don't know our IP address, try to guess
    if fields['IP'] == '0.0.0.0': #XXXX008 remove; not needed since 005.
        try:
            fields['IP'] = _guessLocalIP()
            LOG.warn("No IP configured; guessing %s",fields['IP'])
        except IPGuessError, e:
            LOG.error("Can't guess IP: %s", str(e))
            raise UIError("Can't guess IP: %s" % str(e))
Exemple #14
0
def checkVoteDirectory(voters, validAfter, directory):
    # my (sorted, uniqd) list of voters, SignedDirectory instance, URL

    # Is there a single signature?
    sigs = directory.getSignatures()
    if len(sigs) == 0:
        raise BadVote("No signatures")
    elif len(sigs) > 1:
        raise BadVote("Too many signatures")
    sig = sigs[0]

    ident = sig['Signed-Directory']['Directory-Identity']
    keyid = mixminion.Crypto.pk_fingerprint(ident)

    # Do we recognize the signing key?
    for k,_ in voters:
        if k == keyid:
            break
    else:
        raise BadVote("Unknown identity key (%s)"%keyid)

    # Is the signature valid?
    if not sig.checkSignature():
        raise BadVote("Invalid signature")

    # Is the version valid?
    if (directory['Directory-Info']['Version'] !=
        mixminion.ServerInfo._DirectoryInfo.VERSION):
        raise BadVote("Unrecognized version (%s)")

    # Is the directory marked as a vote?
    if directory['Directory-Info']['Status'] != 'vote':
        raise BadVote("Not marked as vote")

    # Do we agree about the voters?
    if not _listIsSorted(directory.dirInfo.voters):
        raise BadVote("Voters not sorted")

    vkeys = {}
    for k,u in directory.dirInfo.voters:
        vkeys[k]=u
    mykeys = {}
    for k,u in voters: mykeys[k]=u

    for k,u in directory.dirInfo.voters:
        try:
            if mykeys[k] != u:
                raise BadVote("Mismatched URL for voter %s (%s vs %s)"%(
                    formatBase64(k), u, mykeys[k]))
        except KeyError:
            raise BadVote("Unkown voter %s at %s"%(k,u))
    for k, u in voters:
        if not vkeys.has_key(k):
            raise BadVote("Missing voter %s at %s"%(k,u))

    assert directory.dirInfo.voters == voters

    # Are the dates right?
    va = directory['Directory-Info']['Valid-After']
    vu = directory['Directory-Info']['Valid-Until']
    if va != validAfter:
        raise BadVote("Validity date is wrong (%s)"%formatDate(va))
    elif vu != previousMidnight(va+24*60*60+60):
        raise BadVote("Validity span is not 1 day long (ends at %s)"%
                      formatDate(vu))

    # Is everything sorted right?
    for vs in ['MixminionClient', 'MixminionServer']:
        versions = directory['Recommended-Software'][vs]
        if not versionListIsSorted(versions):
            raise BadVote("%s:%s is not in correct sorted order"%(vs,versions))
    if not serverListIsSorted(directory.getAllServers()):
        raise BadVote("Server descriptors are not in correct sorted order")
Exemple #15
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
Exemple #16
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
Exemple #17
0
def generateServerDescriptorAndKeys(config,
                                    identityKey,
                                    keydir,
                                    keyname,
                                    hashdir,
                                    validAt=None,
                                    now=None,
                                    useServerKeys=0,
                                    validUntil=None):
    """Generate and sign a new server descriptor, and generate all the keys to
       go with it.

          config -- Our ServerConfig object.
          identityKey -- This server's private identity key
          keydir -- The root directory for storing key sets.
          keyname -- The name of this new key set within keydir
          hashdir -- The root directory for storing hash logs.
          validAt -- The starting time (in seconds) for this key's lifetime.
          useServerKeys -- If true, try to read an existing keyset from
               (keydir,keyname,hashdir) rather than generating a fresh one.
          validUntil -- Time at which the generated descriptor should
               expire.
    """
    if useServerKeys:
        serverKeys = ServerKeyset(keydir, keyname, hashdir)
        serverKeys.load()
        packetKey = serverKeys.packetKey
    else:
        # First, we generate both of our short-term keys...
        packetKey = mixminion.Crypto.pk_generate(PACKET_KEY_BYTES * 8)

        # ...and save them to disk, setting up our directory structure while
        # we're at it.
        serverKeys = ServerKeyset(keydir, keyname, hashdir)
        serverKeys.packetKey = packetKey
        serverKeys.save()

    # FFFF unused
    # allowIncoming = config['Incoming/MMTP'].get('Enabled', 0)

    # Now, we pull all the information we need from our configuration.
    nickname = config['Server']['Nickname']
    contact = config['Server']['Contact-Email']
    fingerprint = config['Server']['Contact-Fingerprint']
    comments = config['Server']['Comments']
    if not now:
        now = time.time()
    if not validAt:
        validAt = now

    insecurities = config.getInsecurities()
    if insecurities:
        secure = "no"
    else:
        secure = "yes"

    # Calculate descriptor and X509 certificate lifetimes.
    # (Round validAt to previous midnight.)
    validAt = mixminion.Common.previousMidnight(validAt + 30)
    if not validUntil:
        keyLifetime = config['Server']['PublicKeyLifetime'].getSeconds()
        validUntil = previousMidnight(validAt + keyLifetime + 30)

    mmtpProtocolsIn = mixminion.server.MMTPServer.MMTPServerConnection \
                      .PROTOCOL_VERSIONS[:]
    mmtpProtocolsOut = mixminion.server.MMTPServer.MMTPClientConnection \
                       .PROTOCOL_VERSIONS[:]
    mmtpProtocolsIn.sort()
    mmtpProtocolsOut.sort()
    mmtpProtocolsIn = ",".join(mmtpProtocolsIn)
    mmtpProtocolsOut = ",".join(mmtpProtocolsOut)

    #XXXX009 remove: hasn't been checked since 007 or used since 005.
    identityKeyID = formatBase64(
        mixminion.Crypto.sha1(
            mixminion.Crypto.pk_encode_public_key(identityKey)))

    fields = {
        # XXXX009 remove: hasn't been checked since 007.
        "IP":
        config['Incoming/MMTP'].get('IP', "0.0.0.0"),
        "Hostname":
        config['Incoming/MMTP'].get('Hostname', None),
        "Port":
        config['Incoming/MMTP'].get('Port', 0),
        "Nickname":
        nickname,
        "Identity":
        formatBase64(mixminion.Crypto.pk_encode_public_key(identityKey)),
        "Published":
        formatTime(now),
        "ValidAfter":
        formatDate(validAt),
        "ValidUntil":
        formatDate(validUntil),
        "PacketKey":
        formatBase64(mixminion.Crypto.pk_encode_public_key(packetKey)),
        "KeyID":
        identityKeyID,
        "MMTPProtocolsIn":
        mmtpProtocolsIn,
        "MMTPProtocolsOut":
        mmtpProtocolsOut,
        "PacketVersion":
        mixminion.Packet.PACKET_VERSION,
        "mm_version":
        mixminion.__version__,
        "Secure":
        secure,
        "Contact":
        contact,
    }

    # If we don't know our IP address, try to guess
    if fields['IP'] == '0.0.0.0':  #XXXX008 remove; not needed since 005.
        try:
            fields['IP'] = _guessLocalIP()
            LOG.warn("No IP configured; guessing %s", fields['IP'])
        except IPGuessError, e:
            LOG.error("Can't guess IP: %s", str(e))
            raise UIError("Can't guess IP: %s" % str(e))
Exemple #18
0
def checkDescriptorConsistency(info, config, log=1, isPublished=1):
    """Given a ServerInfo and a ServerConfig, compare them for consistency.

       Returns 'good' iff info may have come from 'config'.

       If the server is inconsistent with the configuration file and should
       be regenerated, returns 'bad'.  Otherwise, returns 'so-so'.

       If 'log' is true, warn as well.  Does not check keys.
    """
    #XXXX This needs unit tests.  For now, though, it seems to work.
    warn = _WarnWrapper(silence = not log, isPublished=isPublished)

    config_s = config['Server']
    info_s = info['Server']
    if info_s['Nickname'] != config_s['Nickname']:
        warn("Mismatched nicknames: %s in configuration; %s published.",
             config_s['Nickname'], info_s['Nickname'])

    idBits = info_s['Identity'].get_modulus_bytes()*8
    confIDBits = config_s['IdentityKeyBits']
    if idBits != confIDBits:
        warn("Mismatched identity bits: %s in configuration; %s published.",
             confIDBits, idBits)
        warn.errors -= 1 # We can't do anything about this!

    if config_s['Contact-Email'] != info_s['Contact']:
        warn("Mismatched contacts: %s in configuration; %s published.",
             config_s['Contact-Email'], info_s['Contact'])
    if config_s['Contact-Fingerprint'] != info_s['Contact-Fingerprint']:
        warn("Mismatched contact fingerprints.")

    if info_s['Software'] and info_s['Software'] != (
        "Mixminion %s" % mixminion.__version__):
        warn("Mismatched versions: running %s; %s published.",
             mixminion.__version__, info_s['Software'])

    if config_s['Comments'] != info_s['Comments']:
        warn("Mismatched comments field.")

    if (previousMidnight(info_s['Valid-Until']) !=
        previousMidnight(config_s['PublicKeyLifetime'].getSeconds() +
                         info_s['Valid-After'])):
        warn("Published lifetime does not match PublicKeyLifetime")
        warn("(Future keys will be generated with the correct lifetime")
        warn.errors -= 2 # We can't do anything about this!

    insecurities = config.getInsecurities()
    if insecurities:
        if (info_s['Secure-Configuration'] or
            info_s.get('Why-Insecure',None)!=", ".join(insecurities)):
            warn("Mismatched Secure-Configuration: %r %r %r",
                 info_s['Secure-Configuration'],
                 info_s.get("Why-Insecure",None),
                 ", ".join(insecurities))
    else:
        if not info_s['Secure-Configuration'] or info_s.get('Why-Insecure'):
            warn("Mismatched Secure-Configuration")

    info_im = info['Incoming/MMTP']
    config_im = config['Incoming/MMTP']
    if info_im['Port'] != config_im['Port']:
        warn("Mismatched ports: %s configured; %s published.",
             config_im['Port'], info_im['Port'])

##     info_ip = info_im.get('IP',None)
##     if config_im['IP'] == '0.0.0.0':
##         guessed = _guessLocalIP()
##         if guessed != info_ip:
##             warn("Mismatched IPs: Guessed IP (%s); %s published.",
##                  guessed, info_ip)
##     elif config_im['IP'] != info_ip:
##         warn("Mismatched IPs: %s configured; %s published.",
##              config_im['IP'], info_ip)

    info_host = info_im.get('Hostname',None)
    config_host = config_im['Hostname']
    if config_host is None:
        guessed = socket.getfqdn()
        if guessed != info_host:
            warn("Mismatched hostnames: %s guessed; %s published",
                 guessed, info_host)
    elif config_host != info_host:
        warn("Mismatched hostnames: %s configured, %s published",
             config_host, info_host)

    if config_im['Enabled'] and not info_im.get('Version'):
        warn("Incoming MMTP enabled but not published.")
    elif not config_im['Enabled'] and info_im.get('Version'):
        warn("Incoming MMTP published but not enabled.")

    for section in ('Outgoing/MMTP', 'Delivery/MBOX', 'Delivery/SMTP'):
        info_out = info[section].get('Version')
        config_out = (config[section].get('Enabled') and
                      config[section].get('Advertise',1))
        if not config_out and section == 'Delivery/SMTP':
            config_out = (config['Delivery/SMTP-Via-Mixmaster'].get("Enabled")
                 and config['Delivery/SMTP-Via-Mixmaster'].get("Advertise", 1))
        if info_out and not config_out:
            warn("%s published, but not enabled.", section)
        if config_out and not info_out:
            warn("%s enabled, but not published.", section)

    info_testing = info.get("Testing",{})
    if info_testing.get("Platform", "") != getPlatformSummary():
        warn("Mismatched platform: running %r, but %r published",
             getPlatformSummary(), info_testing.get("Platform",""))
    if not warn.errors and info_testing.get("Configuration", "") != config.getConfigurationSummary():
        warn("Configuration has changed since last publication")

    if warn.errors:
        return "bad"
    elif warn.called:
        return "so-so"
    else:
        return "good"
Exemple #19
0
    def generateDirectory(self,
                          startAt,
                          endAt,
                          extraTime,
                          identityKey,
                          publicationTime=None,
                          badServers=(),
                          excludeServers=()):
        """Generate and sign a new directory, to be effective from <startAt>
           through <endAt>.  It includes all servers that are valid at
           any time between <startAt> and <endAt>+<extraTime>.  The directory
           is signed with <identityKey>.

           Any servers whose nicknames appear in 'badServers' are marked as
           not recommended; any servers whose nicknames appear in
           'excludeServers' are left off the directory entirely.
        """
        try:
            self._lock()
            self.clean()
            if publicationTime is None:
                publicationTime = time.time()
            if previousMidnight(startAt) >= previousMidnight(endAt):
                raise MixError("Validity range does not contain a full day.")

            excludeServers = [nickname.lower() for nickname in excludeServers]

            # First, sort all servers by nickname.
            includedByNickname = {}
            for fn, s in self.servers.items():
                nickname = s.getNickname().lower()
                if nickname in excludeServers: continue
                includedByNickname.setdefault(nickname, []).append((s, fn))

            # Second, find all servers that are valid for part of the period,
            # and that aren't superseded for the whole period.
            timeRange = IntervalSet([(previousMidnight(startAt),
                                      endAt + extraTime)])

            for nickname, ss in includedByNickname.items():
                # We prefer the most-recently-published descriptor.  If two
                # are published at the same time, we prefer the one that
                # expires last.
                ss = [(s['Server']['Published'], s['Server']['Valid-Until'], s,
                       fn) for s, fn in ss]
                ss.sort()
                ss.reverse()
                uncovered = timeRange.copy()
                included = []
                for _, _, s, fn in ss:
                    valid = s.getIntervalSet()
                    if (uncovered * valid):
                        included.append((s, fn))
                        uncovered -= valid
                includedByNickname[nickname] = included

            # Now sort the remaining servers by nickname, then by valid-after.
            included = []
            for ss in includedByNickname.values():
                for s, fn in ss:
                    nickname = s.getNickname()
                    validAfter = s['Server']['Valid-After']
                    included.append((nickname, validAfter, fn))
            included.sort()

            # FFFF We should probably not do all of this in RAM, but
            # FFFF what the hey.  It will only matter if we have many, many
            # FFFF servers in the system.
            contents = []
            for _, _, fn in included:
                txt = readFile(os.path.join(self.serverDir, fn))
                contents.append(txt)

            goodServers = [n for n, _, _ in included if n not in badServers]
            g = {}
            for n in goodServers:
                g[n] = 1
            goodServers = g.keys()
            goodServers.sort()
            goodServers = ", ".join(goodServers)

            clientVersions = self.config['Directory']['ClientVersions']
            serverVersions = self.config['Directory']['ServerVersions']

            #FFFF Support for multiple signatures
            header = """\
            [Directory]
            Version: 0.2
            Published: %s
            Valid-After: %s
            Valid-Until: %s
            Recommended-Servers: %s
            [Signature]
            DirectoryIdentity: %s
            DirectoryDigest:
            DirectorySignature:
            [Recommended-Software]
            MixminionClient: %s
            MixminionServer: %s
            """ % (formatTime(publicationTime), formatDate(startAt),
                   formatDate(endAt), goodServers,
                   formatBase64(pk_encode_public_key(identityKey)),
                   ", ".join(clientVersions), ", ".join(serverVersions))

            directory = header + "".join(contents)
            directory = _getDirectoryDigestImpl(directory, identityKey)

            # Make sure that the directory checks out
            # FFFF remove this once we are _very_ confident.
            if 1:
                parsed = ServerDirectory(string=directory)
                includedDigests = {}
                for _, _, fn in included:
                    includedDigests[self.servers[fn]['Server']['Digest']] = 1
                foundDigests = {}
                for s in parsed.getAllServers():
                    foundDigests[s['Server']['Digest']] = 1
                assert foundDigests == includedDigests

            writeFile(os.path.join(self.baseDir, "directory"),
                      directory,
                      mode=0644)

            f, _ = openUnique(
                os.path.join(self.dirArchiveDir, "dir-" + formatFnameTime()))
            f.write(directory)
            f.close()
        finally:
            self._unlock()
Exemple #20
0
    def generateDirectory(self,
                          startAt, endAt, extraTime,
                          identityKey,
                          publicationTime=None,
                          badServers=(),
                          excludeServers=()):
        """Generate and sign a new directory, to be effective from <startAt>
           through <endAt>.  It includes all servers that are valid at
           any time between <startAt> and <endAt>+<extraTime>.  The directory
           is signed with <identityKey>.

           Any servers whose nicknames appear in 'badServers' are marked as
           not recommended; any servers whose nicknames appear in
           'excludeServers' are left off the directory entirely.
        """
        try:
            self._lock()
            self.clean()
            if publicationTime is None:
                publicationTime = time.time()
            if previousMidnight(startAt) >= previousMidnight(endAt):
                raise MixError("Validity range does not contain a full day.")

            excludeServers = [ nickname.lower() for nickname in excludeServers]

            # First, sort all servers by nickname.
            includedByNickname =  {}
            for fn, s in self.servers.items():
                nickname = s.getNickname().lower()
                if nickname in excludeServers: continue
                includedByNickname.setdefault(nickname, []).append((s, fn))

            # Second, find all servers that are valid for part of the period,
            # and that aren't superseded for the whole period.
            timeRange = IntervalSet([(previousMidnight(startAt),
                                      endAt+extraTime)])

            for nickname, ss in includedByNickname.items():
                # We prefer the most-recently-published descriptor.  If two
                # are published at the same time, we prefer the one that
                # expires last.
                ss = [ (s['Server']['Published'],
                        s['Server']['Valid-Until'],
                        s, fn) for s,fn in ss]
                ss.sort()
                ss.reverse()
                uncovered = timeRange.copy()
                included = []
                for _, _, s, fn in ss:
                    valid = s.getIntervalSet()
                    if (uncovered * valid):
                        included.append((s, fn))
                        uncovered -= valid
                includedByNickname[nickname] = included

            # Now sort the remaining servers by nickname, then by valid-after.
            included = []
            for ss in includedByNickname.values():
                for s,fn in ss:
                    nickname = s.getNickname()
                    validAfter = s['Server']['Valid-After']
                    included.append((nickname, validAfter, fn))
            included.sort()

            # FFFF We should probably not do all of this in RAM, but
            # FFFF what the hey.  It will only matter if we have many, many
            # FFFF servers in the system.
            contents = [ ]
            for _, _, fn in included:
                txt = readFile(os.path.join(self.serverDir, fn))
                contents.append(txt)

            goodServers = [n for n,_,_ in included if n not in badServers]
            g = {}
            for n in goodServers: g[n]=1
            goodServers = g.keys()
            goodServers.sort()
            goodServers = ", ".join(goodServers)

            clientVersions = self.config['Directory']['ClientVersions']
            serverVersions = self.config['Directory']['ServerVersions']

            #FFFF Support for multiple signatures
            header = """\
            [Directory]
            Version: 0.2
            Published: %s
            Valid-After: %s
            Valid-Until: %s
            Recommended-Servers: %s
            [Signature]
            DirectoryIdentity: %s
            DirectoryDigest:
            DirectorySignature:
            [Recommended-Software]
            MixminionClient: %s
            MixminionServer: %s
            """ % (formatTime(publicationTime),
                   formatDate(startAt),
                   formatDate(endAt),
                   goodServers,
                   formatBase64(pk_encode_public_key(identityKey)),
                   ", ".join(clientVersions),
                   ", ".join(serverVersions))

            directory = header+"".join(contents)
            directory = _getDirectoryDigestImpl(directory, identityKey)

            # Make sure that the directory checks out
            # FFFF remove this once we are _very_ confident.
            if 1:
                parsed = ServerDirectory(string=directory)
                includedDigests = {}
                for _, _, fn in included:
                    includedDigests[self.servers[fn]['Server']['Digest']] = 1
                foundDigests = {}
                for s in parsed.getAllServers():
                    foundDigests[s['Server']['Digest']] = 1
                assert foundDigests == includedDigests

            writeFile(os.path.join(self.baseDir, "directory"),
                      directory,
                      mode=0644)

            f, _ = openUnique(os.path.join(self.dirArchiveDir,
                                            "dir-"+formatFnameTime()))
            f.write(directory)
            f.close()
        finally:
            self._unlock()
Exemple #21
0
def checkVoteDirectory(voters, validAfter, directory):
    # my (sorted, uniqd) list of voters, SignedDirectory instance, URL

    # Is there a single signature?
    sigs = directory.getSignatures()
    if len(sigs) == 0:
        raise BadVote("No signatures")
    elif len(sigs) > 1:
        raise BadVote("Too many signatures")
    sig = sigs[0]

    ident = sig['Signed-Directory']['Directory-Identity']
    keyid = mixminion.Crypto.pk_fingerprint(ident)

    # Do we recognize the signing key?
    for k,_ in voters:
        if k == keyid:
            break
    else:
        raise BadVote("Unknown identity key (%s)"%keyid)

    # Is the signature valid?
    if not sig.checkSignature():
        raise BadVote("Invalid signature")

    # Is the version valid?
    if (directory['Directory-Info']['Version'] !=
        mixminion.ServerInfo._DirectoryInfo.VERSION):
        raise BadVote("Unrecognized version (%s)")

    # Is the directory marked as a vote?
    if directory['Directory-Info']['Status'] != 'vote':
        raise BadVote("Not marked as vote")

    # Do we agree about the voters?
    if not _listIsSorted(directory.dirInfo.voters):
        raise BadVote("Voters not sorted")

    vkeys = {}
    for k,u in directory.dirInfo.voters:
        vkeys[k]=u
    mykeys = {}
    for k,u in voters: mykeys[k]=u

    for k,u in directory.dirInfo.voters:
        try:
            if mykeys[k] != u:
                raise BadVote("Mismatched URL for voter %s (%s vs %s)"%(
                    formatBase64(k), u, mykeys[k]))
        except KeyError:
            raise BadVote("Unkown voter %s at %s"%(k,u))
    for k, u in voters:
        if not vkeys.has_key(k):
            raise BadVote("Missing voter %s at %s"%(k,u))

    assert directory.dirInfo.voters == voters

    # Are the dates right?
    va = directory['Directory-Info']['Valid-After']
    vu = directory['Directory-Info']['Valid-Until']
    if va != validAfter:
        raise BadVote("Validity date is wrong (%s)"%formatDate(va))
    elif vu != previousMidnight(va+24*60*60+60):
        raise BadVote("Validity span is not 1 day long (ends at %s)"%
                      formatDate(vu))

    # Is everything sorted right?
    for vs in ['MixminionClient', 'MixminionServer']:
        versions = directory['Recommended-Software'][vs]
        if not versionListIsSorted(versions):
            raise BadVote("%s:%s is not in correct sorted order"%(vs,versions))
    if not serverListIsSorted(directory.getAllServers()):
        raise BadVote("Server descriptors are not in correct sorted order")
Exemple #22
0
def checkDescriptorConsistency(info, config, log=1, isPublished=1):
    """Given a ServerInfo and a ServerConfig, compare them for consistency.

       Returns 'good' iff info may have come from 'config'.

       If the server is inconsistent with the configuration file and should
       be regenerated, returns 'bad'.  Otherwise, returns 'so-so'.

       If 'log' is true, warn as well.  Does not check keys.
    """
    #XXXX This needs unit tests.  For now, though, it seems to work.
    warn = _WarnWrapper(silence=not log, isPublished=isPublished)

    config_s = config['Server']
    info_s = info['Server']
    if info_s['Nickname'] != config_s['Nickname']:
        warn("Mismatched nicknames: %s in configuration; %s published.",
             config_s['Nickname'], info_s['Nickname'])

    idBits = info_s['Identity'].get_modulus_bytes() * 8
    confIDBits = config_s['IdentityKeyBits']
    if idBits != confIDBits:
        warn("Mismatched identity bits: %s in configuration; %s published.",
             confIDBits, idBits)
        warn.errors -= 1  # We can't do anything about this!

    if config_s['Contact-Email'] != info_s['Contact']:
        warn("Mismatched contacts: %s in configuration; %s published.",
             config_s['Contact-Email'], info_s['Contact'])
    if config_s['Contact-Fingerprint'] != info_s['Contact-Fingerprint']:
        warn("Mismatched contact fingerprints.")

    if info_s['Software'] and info_s['Software'] != ("Mixminion %s" %
                                                     mixminion.__version__):
        warn("Mismatched versions: running %s; %s published.",
             mixminion.__version__, info_s['Software'])

    if config_s['Comments'] != info_s['Comments']:
        warn("Mismatched comments field.")

    if (previousMidnight(info_s['Valid-Until']) !=
            previousMidnight(config_s['PublicKeyLifetime'].getSeconds() +
                             info_s['Valid-After'])):
        warn("Published lifetime does not match PublicKeyLifetime")
        warn("(Future keys will be generated with the correct lifetime")
        warn.errors -= 2  # We can't do anything about this!

    insecurities = config.getInsecurities()
    if insecurities:
        if (info_s['Secure-Configuration'] or
                info_s.get('Why-Insecure', None) != ", ".join(insecurities)):
            warn("Mismatched Secure-Configuration: %r %r %r",
                 info_s['Secure-Configuration'],
                 info_s.get("Why-Insecure", None), ", ".join(insecurities))
    else:
        if not info_s['Secure-Configuration'] or info_s.get('Why-Insecure'):
            warn("Mismatched Secure-Configuration")

    info_im = info['Incoming/MMTP']
    config_im = config['Incoming/MMTP']
    if info_im['Port'] != config_im['Port']:
        warn("Mismatched ports: %s configured; %s published.",
             config_im['Port'], info_im['Port'])

##     info_ip = info_im.get('IP',None)
##     if config_im['IP'] == '0.0.0.0':
##         guessed = _guessLocalIP()
##         if guessed != info_ip:
##             warn("Mismatched IPs: Guessed IP (%s); %s published.",
##                  guessed, info_ip)
##     elif config_im['IP'] != info_ip:
##         warn("Mismatched IPs: %s configured; %s published.",
##              config_im['IP'], info_ip)

    info_host = info_im.get('Hostname', None)
    config_host = config_im['Hostname']
    if config_host is None:
        guessed = socket.getfqdn()
        if guessed != info_host:
            warn("Mismatched hostnames: %s guessed; %s published", guessed,
                 info_host)
    elif config_host != info_host:
        warn("Mismatched hostnames: %s configured, %s published", config_host,
             info_host)

    if config_im['Enabled'] and not info_im.get('Version'):
        warn("Incoming MMTP enabled but not published.")
    elif not config_im['Enabled'] and info_im.get('Version'):
        warn("Incoming MMTP published but not enabled.")

    for section in ('Outgoing/MMTP', 'Delivery/MBOX', 'Delivery/SMTP'):
        info_out = info[section].get('Version')
        config_out = (config[section].get('Enabled')
                      and config[section].get('Advertise', 1))
        if not config_out and section == 'Delivery/SMTP':
            config_out = (config['Delivery/SMTP-Via-Mixmaster'].get("Enabled")
                          and config['Delivery/SMTP-Via-Mixmaster'].get(
                              "Advertise", 1))
        if info_out and not config_out:
            warn("%s published, but not enabled.", section)
        if config_out and not info_out:
            warn("%s enabled, but not published.", section)

    info_testing = info.get("Testing", {})
    if info_testing.get("Platform", "") != getPlatformSummary():
        warn("Mismatched platform: running %r, but %r published",
             getPlatformSummary(), info_testing.get("Platform", ""))
    if not warn.errors and info_testing.get(
            "Configuration", "") != config.getConfigurationSummary():
        warn("Configuration has changed since last publication")

    if warn.errors:
        return "bad"
    elif warn.called:
        return "so-so"
    else:
        return "good"