def generateCertChain(filename, mmtpKey, identityKey, nickname, certStarts, certEnds): """Create a two-certificate chain for use in MMTP. filename -- location to store certificate chain. mmtpKey -- a short-term RSA key to use for connection encryption (1024 bits). identityKey -- our long-term signing key (2048-4096 bits). nickname -- nickname to use in our certificates. certStarts, certEnds -- certificate lifetimes. """ fname = filename+"_tmp" mixminion.Crypto.generate_cert(fname, mmtpKey, identityKey, "%s<MMTP>" %nickname, nickname, certStarts, certEnds) certText = readFile(fname) os.unlink(fname) mixminion.Crypto.generate_cert(fname, identityKey, identityKey, nickname, nickname, certStarts, certEnds) identityCertText = readFile(fname) os.unlink(fname) writeFile(filename, certText+identityCertText, 0600)
def _writeEncryptedFile(fname, password, magic, data): """Write 'data' into an encrypted file named 'fname', replacing it if necessary. Encrypts the data with the password 'password', and uses the filetype 'magic'.""" assert len(magic) == MAGIC_LEN prng = getCommonPRNG() length = struct.pack("!L", len(data)) paddingLen = ceilDiv(len(data), 1024)*1024 - len(data) padding = prng.getBytes(paddingLen) data = "".join([length,data,padding]) salt = prng.getBytes(SALT_LEN) key = sha1(salt+password+salt)[:AES_KEY_LEN] digest = sha1("".join([data,salt,magic])) encrypted = ctr_crypt(data+digest, key) contents = "".join([magic,"\x00",salt,encrypted]) writeFile(fname, armorText(contents, "TYPE III KEYRING", [("Version","0.1")]))
def _writeEncryptedFile(fname, password, magic, data): """Write 'data' into an encrypted file named 'fname', replacing it if necessary. Encrypts the data with the password 'password', and uses the filetype 'magic'.""" assert len(magic) == MAGIC_LEN prng = getCommonPRNG() length = struct.pack("!L", len(data)) paddingLen = ceilDiv(len(data), 1024) * 1024 - len(data) padding = prng.getBytes(paddingLen) data = "".join([length, data, padding]) salt = prng.getBytes(SALT_LEN) key = sha1(salt + password + salt)[:AES_KEY_LEN] digest = sha1("".join([data, salt, magic])) encrypted = ctr_crypt(data + digest, key) contents = "".join([magic, "\x00", salt, encrypted]) writeFile(fname, armorText(contents, "TYPE III KEYRING", [("Version", "0.1")]))
def getPingerSeed(self): """DOCDOC""" if self.pingerSeed is not None: return self.pingerSeed fn = os.path.join(self.keyDir, "pinger.seed") if os.path.exists(fn): checkPrivateFile(fn) r = readFile(fn) if len(r) == mixminion.Crypto.DIGEST_LEN: self.pingerSeed = r return r self.pingerSeed = r = mixminion.Crypto.trng(mixminion.Crypto.DIGEST_LEN) createPrivateDir(self.keyDir) writeFile(fn, r, 0600) return r
def updateKeys(self, packetHandler, statusFile=None,when=None): """Update the keys stored in a PacketHandler, MMTPServer object, so that they contain the currently correct keys. Also removes any dead keys. This function is idempotent. """ self.checkKeys() deadKeys = self.getDeadKeys(when) self.currentKeys = keys = self.getServerKeysets(when) keyNames = [k.keyname for k in keys] deadKeyNames = [k.keyname for msg, k in deadKeys] LOG.info("Updating keys: %s currently valid (%s); %s expired (%s)", len(keys), " ".join(keyNames), len(deadKeys), " ".join(deadKeyNames)) if packetHandler is not None: packetKeys = [] hashLogs = [] for k in keys: packetKeys.append(k.getPacketKey()) hashLogs.append(k.getHashLog()) packetHandler.setKeys(packetKeys, hashLogs) if statusFile: writeFile(statusFile, "".join(["%s\n"%k.getDescriptorFileName() for k in keys]), 0644) for msg, ks in deadKeys: LOG.info(msg) ks.delete() if deadKeys: self.checkKeys() self.nextUpdate = None self.getNextKeyRotation(keys)
def markAsPublished(self): """Mark this keyset as published.""" contents = "%s\n" % formatTime(time.time(), 1) writeFile(self.publishedFile, contents, mode=0600) self.published = 1
[Testing] Platform: %s Configuration: %s """ % (getPlatformSummary(), config.getConfigurationSummary()) # Remove extra (leading or trailing) whitespace from the lines. lines = [line.strip() for line in info.split("\n")] # Remove empty lines lines = filter(None, lines) # Force a newline at the end of the file, rejoin, and sign. lines.append("") info = "\n".join(lines) info = signServerInfo(info, identityKey) # Write the desciptor writeFile(serverKeys.getDescriptorFileName(), info, mode=0644) # This is for debugging: we try to parse and validate the descriptor # we just made. # FFFF Remove this once we're more confident. inf = ServerInfo(string=info) ok = checkDescriptorConsistency(inf, config, log=0, isPublished=0) if ok not in ('good', 'so-so'): print "========" print info print "======" checkDescriptorConsistency(inf, config, log=1, isPublished=0) assert ok in ('good', 'so-so') return info
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 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 markAsPublished(self): """Mark this keyset as published.""" contents = "%s\n"%formatTime(time.time(),1) writeFile(self.publishedFile, contents, mode=0600) self.published = 1
Platform: %s Configuration: %s """ %(getPlatformSummary(), config.getConfigurationSummary()) # Remove extra (leading or trailing) whitespace from the lines. lines = [ line.strip() for line in info.split("\n") ] # Remove empty lines lines = filter(None, lines) # Force a newline at the end of the file, rejoin, and sign. lines.append("") info = "\n".join(lines) info = signServerInfo(info, identityKey) # Write the desciptor writeFile(serverKeys.getDescriptorFileName(), info, mode=0644) # This is for debugging: we try to parse and validate the descriptor # we just made. # FFFF Remove this once we're more confident. inf = ServerInfo(string=info) ok = checkDescriptorConsistency(inf, config, log=0, isPublished=0) if ok not in ('good', 'so-so'): print "========" print info print "======" checkDescriptorConsistency(inf, config, log=1, isPublished=0) assert ok in ('good', 'so-so') return info