def getDirectorySignature(directory, pkey): digest = mixminion.ServerInfo._getMultisignedDirectoryDigest(directory) signature = pk_sign(digest, pkey) encKey = formatBase64(pk_encode_public_key(pkey)) encSig = formatBase64(signature) encDigest = formatBase64(digest) return ("[Signed-Directory]\nDirectory-Identity: %s\n" "Directory-Digest: %s\nDirectory-Signature: %s\n")%( encKey,encDigest,encSig)
def buildReplyBlock(path, exitType, exitInfo, userKey, expiryTime=None, secretRNG=None): """Construct a 'state-carrying' reply block that does not require the reply-message recipient to remember a list of secrets. Instead, all secrets are generated from an AES counter-mode stream, and the seed for the stream is stored in the 'tag' field of the final block's routing info. (See the spec for more info). path: a list of ServerInfo objects exitType,exitInfo: The address to deliver the final message. userKey: a string used to encrypt the seed. NOTE: We used to allow another kind of 'non-state-carrying' reply block that stored its secrets on disk, and used an arbitrary tag to determine which set of secrets to use. """ if secretRNG is None: secretRNG = Crypto.getCommonPRNG() # We need to pick the seed to generate our keys. To make the decoding # step a little faster, we find a seed such that H(seed|userKey|"Validate") # ends with 0. This way, we can detect whether we really have a reply # message with 99.6% probability. (Otherwise, we'd need to repeatedly # lioness-decrypt the payload in order to see whether the message was # a reply.) while 1: seed = _getRandomTag(secretRNG) if Crypto.sha1(seed + userKey + "Validate")[-1] == "\x00": break prng = Crypto.AESCounterPRNG(Crypto.sha1(seed + userKey + "Generate")[:16]) replyBlock, secrets, tag = _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime, prng, seed) STATUS.log("GENERATED_SURB", formatBase64(tag)) return replyBlock
def _getDigestImpl(info, regex, digestField=None, sigField=None, rsa=None): """Helper method. Calculates the correct digest of a server descriptor or directory (as provided in a string). If rsa is provided, signs the digest and creates a new descriptor. Otherwise just returns the digest. info -- the string to digest or sign. regex -- a compiled regex that matches the line containing the digest and the line containing the signature. digestField -- If not signing, None. Otherwise, the name of the digest field. sigField -- If not signing, None. Otherwise, the name of the signature field. rsa -- our public key """ info = _cleanForDigest(info) def replaceFn(m): s = m.group(0) return s[:s.index(':') + 1] info = regex.sub(replaceFn, info, 2) digest = sha1(info) if rsa is None: return digest signature = pk_sign(digest, rsa) digest = formatBase64(digest) signature = formatBase64(signature) def replaceFn2(s, digest=digest, signature=signature, digestField=digestField, sigField=sigField): if s.group(0).startswith(digestField): return "%s: %s" % (digestField, digest) else: assert s.group(0).startswith(sigField) return "%s: %s" % (sigField, signature) info = regex.sub(replaceFn2, info, 2) return info
def _getDigestImpl(info, regex, digestField=None, sigField=None, rsa=None): """Helper method. Calculates the correct digest of a server descriptor or directory (as provided in a string). If rsa is provided, signs the digest and creates a new descriptor. Otherwise just returns the digest. info -- the string to digest or sign. regex -- a compiled regex that matches the line containing the digest and the line containing the signature. digestField -- If not signing, None. Otherwise, the name of the digest field. sigField -- If not signing, None. Otherwise, the name of the signature field. rsa -- our public key """ info = _cleanForDigest(info) def replaceFn(m): s = m.group(0) return s[:s.index(':')+1] info = regex.sub(replaceFn, info, 2) digest = sha1(info) if rsa is None: return digest signature = pk_sign(digest,rsa) digest = formatBase64(digest) signature = formatBase64(signature) def replaceFn2(s, digest=digest, signature=signature, digestField=digestField, sigField=sigField): if s.group(0).startswith(digestField): return "%s: %s" % (digestField, digest) else: assert s.group(0).startswith(sigField) return "%s: %s" % (sigField, signature) info = regex.sub(replaceFn2, info, 2) return info
def readPendingServers(self): """Scan all of the servers waiting in the incoming directory. If any are bad, remove them. Return a list of (filename, ServerInfo, server descriptor, ID Fingerprint) tuples for all the servers in the directory. """ res = [] for fname in os.listdir(self.incomingDir): path = os.path.join(self.incomingDir,fname) try: text, server = _readServer(path) except MixError, e: os.unlink(path) LOG.warn( "Removed a bad server descriptor %s from incoming dir: %s", fname, e) continue fp = formatBase64(getIDFingerprint(server)) res.append((fname, server, text, fp))
def buildReplyBlock(path, exitType, exitInfo, userKey, expiryTime=None, secretRNG=None): """Construct a 'state-carrying' reply block that does not require the reply-message recipient to remember a list of secrets. Instead, all secrets are generated from an AES counter-mode stream, and the seed for the stream is stored in the 'tag' field of the final block's routing info. (See the spec for more info). path: a list of ServerInfo objects exitType,exitInfo: The address to deliver the final message. userKey: a string used to encrypt the seed. NOTE: We used to allow another kind of 'non-state-carrying' reply block that stored its secrets on disk, and used an arbitrary tag to determine which set of secrets to use. """ if secretRNG is None: secretRNG = Crypto.getCommonPRNG() # We need to pick the seed to generate our keys. To make the decoding # step a little faster, we find a seed such that H(seed|userKey|"Validate") # ends with 0. This way, we can detect whether we really have a reply # message with 99.6% probability. (Otherwise, we'd need to repeatedly # lioness-decrypt the payload in order to see whether the message was # a reply.) while 1: seed = _getRandomTag(secretRNG) if Crypto.sha1(seed+userKey+"Validate")[-1] == '\x00': break prng = Crypto.AESCounterPRNG(Crypto.sha1(seed+userKey+"Generate")[:16]) replyBlock, secrets, tag = _buildReplyBlockImpl(path, exitType, exitInfo, expiryTime, prng, seed) STATUS.log("GENERATED_SURB", formatBase64(tag)) return replyBlock
def _getKey(self, digest): k = formatBase64(digest).replace("/","-").replace("=","") assert len(k) == self.KEY_LENGTH return k
def __str__(self): return "%s:%s:%s" % (self.hostname, self.port, formatBase64(self.keyinfo))
# Identities go in if they have a consistant nickname, and most voters # include them. identNickname = {} badIdents = {} identsByVoter = [] digestsByIdent = {} for digestList in serversByDir.values(): idents = {} for digest in digestList: s = serverMap[digest] n = s.getNickname() ident = s.getIdentityDigest() try: if n != identNickname[ident]: log.warn("Multiple nicknames for %s",formatBase64(ident)) badIdents[ident] = 1 except KeyError: identNickname[ident]=n idents[ident] = 1 digestsByIdent.setdefault(ident,{})[digest]=1 identsByVoter.append(idents.keys()) includedIdentities = [ i for i in commonElements(identsByVoter, threshold) if not badIdents.has_key(i) ] # okay -- for each identity, what servers do we include? includedServers = [] for ident in includedIdentities: servers = [ serverMap[digest] for digest in digestsByIdent[ident].keys()]
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()
def _getKey(self, digest): k = formatBase64(digest).replace("/", "-").replace("=", "") assert len(k) == self.KEY_LENGTH return k
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))
def getAsciiTag(self): """Return a base64-representation of this message's decoding handle.""" return formatBase64(self.tag)
def __str__(self): return "IP:%s:%s:%s"%(self.ip,self.port,formatBase64(self.keyinfo))
def __str__(self): return "%s:%s:%s"%(self.hostname,self.port,formatBase64(self.keyinfo))
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()
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))
# Identities go in if they have a consistant nickname, and most voters # include them. identNickname = {} badIdents = {} identsByVoter = [] digestsByIdent = {} for digestList in serversByDir.values(): idents = {} for digest in digestList: s = serverMap[digest] n = s.getNickname() ident = s.getIdentityDigest() try: if n != identNickname[ident]: LOG.warn("Multiple nicknames for %s",formatBase64(ident)) badIdents[ident] = 1 except KeyError: identNickname[ident]=n idents[ident] = 1 digestsByIdent.setdefault(ident,{})[digest]=1 identsByVoter.append(idents.keys()) includedIdentities = [ i for i in commonElements(identsByVoter, threshold) if not badIdents.has_key(i) ] # okay -- for each identity, what servers do we include? includedServers = [] for ident in includedIdentities: servers = [ serverMap[digest] for digest in digestsByIdent[ident].keys()]
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")
def __str__(self): return "IP:%s:%s:%s" % (self.ip, self.port, formatBase64(self.keyinfo))