Example #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
Example #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
Example #3
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()
Example #4
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()
Example #5
0
    def getDeadKeys(self, now=None):
        """Helper function: return a list of (informative-message, keyset
           object) for each expired keyset in the keystore.  Does not rescan
           the keystore or remove dead keys.
        """
        if now is None:
            now = time.time()
            expiryStr = " expired"
        else:
            expiryStr = ""

        cutoff = now - self.keyOverlap

        result = []
        for va, vu, keyset in self.keySets:
            if vu >= cutoff:
                continue
            name = keyset.keyname
            message = "Removing%s key %s (valid from %s through %s)" % (
                expiryStr, name, formatDate(va), formatDate(vu))
            result.append((message, keyset))

        return result
Example #6
0
    def getDeadKeys(self,now=None):
        """Helper function: return a list of (informative-message, keyset
           object) for each expired keyset in the keystore.  Does not rescan
           the keystore or remove dead keys.
        """
        if now is None:
            now = time.time()
            expiryStr = " expired"
        else:
            expiryStr = ""

        cutoff = now - self.keyOverlap

        result = []
        for va, vu, keyset in self.keySets:
            if vu >= cutoff:
                continue
            name = keyset.keyname
            message ="Removing%s key %s (valid from %s through %s)"%(
                      expiryStr, name, formatDate(va), formatDate(vu))
            result.append((message, keyset))

        return result
Example #7
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")
Example #8
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")
Example #9
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))
Example #10
0
class ServerKeyring:
    """A ServerKeyring remembers current and future keys, descriptors, and
       hash logs for a mixminion server.  It keeps track of key rotation
       schedules, and generates new keys as needed.
       """

    ## Fields:
    # homeDir: server home directory
    # keyDir: server key directory
    # keyOverlap: How long after a new key begins do we accept the old one?
    # keySets: sorted list of (start, end, keyset)
    # nextUpdate: time_t when a new key should be added, or a current key
    #      should be removed, or "None" for uncalculated.
    # keyRange: tuple of (firstKey, lastKey) to represent which key names
    #      have keys on disk.
    # currentKeys: None, if we haven't checked for currently live keys, or
    #      a list of currently live ServerKeyset objects.
    # dhFile: pathname to file holding diffie-helman parameters.
    # _lock: A lock to prevent concurrent key generation or rotation.

    def __init__(self, config):
        "Create a ServerKeyring from a config object"
        self._lock = threading.RLock()
        self.configure(config)

    def configure(self, config):
        "Set up a ServerKeyring from a config object"
        self.config = config
        self.homeDir = config.getBaseDir()
        self.keyDir = config.getKeyDir()
        self.hashDir = os.path.join(config.getWorkDir(), 'hashlogs')
        self.dhFile = os.path.join(config.getWorkDir(), 'tls', 'dhparam')
        self.certFile = os.path.join(config.getWorkDir(), "cert_chain")
        self.keyOverlap = config['Server']['PublicKeyOverlap'].getSeconds()
        self.nickname = config['Server']['Nickname']  #DOCDOC
        self.nextUpdate = None
        self.currentKeys = None
        self._tlsContext = None  #DOCDOC
        self._tlsContextExpires = -1  #DOCDOC
        self.pingerSeed = None
        self.checkKeys()

    def checkKeys(self):
        """Internal method: read information about all this server's
           currently-prepared keys from disk.

           May raise ConfigError if any of the server descriptors on disk
           are invalid.
           """
        self.keySets = []
        badKeySets = []
        firstKey = sys.maxint
        lastKey = 0

        LOG.debug("Scanning server keystore at %s", self.keyDir)

        if not os.path.exists(self.keyDir):
            LOG.info("Creating server keystore at %s", self.keyDir)
            createPrivateDir(self.keyDir)

        # Iterate over the entires in HOME/keys
        for dirname in os.listdir(self.keyDir):
            # Skip any that aren't directories named "key_INT"
            if not os.path.isdir(os.path.join(self.keyDir, dirname)):
                continue
            if not dirname.startswith('key_'):
                LOG.warn("Unexpected directory %s under %s", dirname,
                         self.keyDir)
                continue
            keysetname = dirname[4:]
            try:
                setNum = int(keysetname)
                # keep trace of the first and last used key number
                if setNum < firstKey: firstKey = setNum
                if setNum > lastKey: lastKey = setNum
            except ValueError:
                LOG.warn("Unexpected directory %s under %s", dirname,
                         self.keyDir)
                continue

            # Find the server descriptor...
            keyset = ServerKeyset(self.keyDir, keysetname, self.hashDir)
            ok = 1
            try:
                keyset.checkKeys()
            except MixError, e:
                LOG.warn("Error checking private keys in keyset %s: %s",
                         keysetname, str(e))
                ok = 0

            try:
                if ok:
                    keyset.getServerDescriptor()
            except (ConfigError, IOError), e:
                LOG.warn("Key set %s has invalid/missing descriptor: %s",
                         keysetname, str(e))
                ok = 0

            if ok:
                t1, t2 = keyset.getLiveness()
                self.keySets.append((t1, t2, keyset))

                LOG.trace("Found key %s (valid from %s to %s)", dirname,
                          formatDate(t1), formatDate(t2))
            else:
                badKeySets.append(keyset)
Example #11
0
            LOG.warn("Removing %s invalid keysets", len(badKeySets))
        for b in badKeySets:
            b.delete()

        # Now, sort the key intervals by starting time.
        self.keySets.sort()
        self.keyRange = (firstKey, lastKey)

        # Now we try to see whether we have more or less than 1 key in effect
        # for a given time.
        for idx in xrange(len(self.keySets) - 1):
            end = self.keySets[idx][1]
            start = self.keySets[idx + 1][0]
            if start < end:
                LOG.warn("Multiple keys for %s.  That's unsupported.",
                         formatDate(end))
            elif start > end:
                LOG.warn("Gap in key schedule: no key from %s to %s",
                         formatDate(end), formatDate(start))

    def checkDescriptorConsistency(self, regen=1):
        """Check whether the server descriptors in this keyring are
           consistent with the server's configuration.  If 'regen' is
           true, inconsistent descriptors are regenerated."""
        identity = None
        state = []
        for _, _, ks in self.keySets:
            ok = ks.checkConsistency(self.config, 0)
            if ok == 'good':
                continue
            state.append((ok, ks))
Example #12
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()
Example #13
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()
Example #14
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))
Example #15
0
            LOG.warn("Removing %s invalid keysets", len(badKeySets))
        for b in badKeySets:
            b.delete()

        # Now, sort the key intervals by starting time.
        self.keySets.sort()
        self.keyRange = (firstKey, lastKey)

        # Now we try to see whether we have more or less than 1 key in effect
        # for a given time.
        for idx in xrange(len(self.keySets)-1):
            end = self.keySets[idx][1]
            start = self.keySets[idx+1][0]
            if start < end:
                LOG.warn("Multiple keys for %s.  That's unsupported.",
                              formatDate(end))
            elif start > end:
                LOG.warn("Gap in key schedule: no key from %s to %s",
                              formatDate(end), formatDate(start))

    def checkDescriptorConsistency(self, regen=1):
        """Check whether the server descriptors in this keyring are
           consistent with the server's configuration.  If 'regen' is
           true, inconsistent descriptors are regenerated."""
        identity = None
        state = []
        for _,_,ks in self.keySets:
            ok = ks.checkConsistency(self.config, 0)
            if ok == 'good':
                continue
            state.append((ok, ks))