Open Table Of Contents

Source code for leekspin.generator

# -*- coding: utf-8 -*-

"""Main leekspin module for generating mocked Onion Relay (OR) and Hidden
Service (HS) descriptors and writing them to disk.
"""

from __future__ import absolute_import
from __future__ import print_function

from codecs import open as open

import base64
import hashlib
import logging
import random
import re
import sys
import os
import traceback

try:
    import OpenSSL
    import OpenSSL.crypto
except (ImportError, NameError) as error:
    print("This script requires pyOpenSSL>=0.14.0")
    raise SystemExit(error.message)

import OpenSSL
import OpenSSL.crypto

from leekspin import const
from leekspin import crypto
from leekspin import extrainfo
from leekspin import netstatus
from leekspin import nicknames
from leekspin import ntor
from leekspin import rendezvous
from leekspin import server
from leekspin import tls
from leekspin import torversions
from leekspin import util

#: If pynacl was found by :attr:`leekspin.ntor.nacl`.
nacl = True if ntor.nacl else False


[docs]def generateDescriptors(bridge=True, withoutTAP=False, withoutNTOR=False): """Create keys, certs, signatures, documents and descriptors for an OR. :param bool bridge: If ``True``, generate Bridge descriptors; otherwise, generate Relay descriptors. :param bool withoutTAP: If ``True``, generate descriptors without support for the TAP handshake, e.g. without RSA keys. :param bool withoutTAP: If ``True``, generate descriptors without support for the ntor handshake, e.g. without Ed25519 keys. :returns: A 3-tuple of strings: - a ``@type [bridge-]extra-info`` descriptor, - a ``@type [bridge-]server-descriptor``, and - a ``@type network-status`` document for a mock Tor relay/bridge. """ ipv4 = util.randomIPv4() ipv6 = util.randomIPv6() port = util.randomPort() nick = nicknames.generateNickname() vers = torversions.getRandomVersion() uptime = int(random.randint(1800, 63072000)) bandwidth = server.makeBandwidthLine() timestamp = util.makeTimeStamp(variation=True, period=36) protocols = server.makeProtocolsLine(vers) if withoutTAP: (secretOnionKey, publicOnionKey, onionKeyLine) = (None, None, None) else: (secretOnionKey, publicOnionKey, onionKeyLine) = crypto.generateOnionKey() (secretSigningKey, publicSigningKey, signingKeyLine) = crypto.generateSigningKey() secretNTORKey = None publicNTORKey = None if not withoutNTOR and nacl: try: secretNTORKey = ntor.createNTORSecretKey() publicNTORKey = ntor.getNTORPublicKey(secretNTORKey) except ntor.NTORKeyCreationError as error: secretNTORKey = None publicNTORKey = None (fingerprintSpacey, fingerprintBinary) = crypto.getFingerprint(publicSigningKey) fingerprintSmooshed = crypto.convertToSmooshedFingerprint(fingerprintSpacey) extrainfoDoc = extrainfo.generateExtraInfo(nick, fingerprintSmooshed, timestamp, ipv4, port, bridge=bridge) (extrainfoDigestBinary, extrainfoDigest, extrainfoDigestPKCS1) = crypto.digestDescriptorContent(extrainfoDoc) extrainfoDesc = crypto.signDescriptorContent(extrainfoDoc, secretSigningKey, digest=extrainfoDigestPKCS1) serverDoc = server.generateServerDescriptor(nick, fingerprintSpacey, timestamp, ipv4, ipv6, port, vers, protocols, uptime, bandwidth, extrainfoDigest, onionKeyLine, signingKeyLine, publicNTORKey, bridge=bridge) (serverDigestBinary, serverDigest, serverDigestPKCS1) = crypto.digestDescriptorContent(serverDoc) if bridge: serverDoc = b'@purpose bridge\n' + serverDoc serverDesc = crypto.signDescriptorContent(serverDoc, secretSigningKey, digest=serverDigestPKCS1) netstatusDesc = netstatus.generateBridgeNetstatus(nick, fingerprintBinary, serverDigestBinary, timestamp, ipv4, port, ipv6=ipv6, bandwidth_line=bandwidth) return (extrainfoDesc, serverDesc, netstatusDesc)
[docs]def generateHSDesc(replica): """Generate a ``@type rendezvous-service-descriptor`` for an Hidden Service. .. todo:: Make generation of ``permanent_ids`` deal with HS "stealth" authorisation. .. todo:: Implement per-client ``session_keys`` and ``descriptor_cookies``, see rend-spec.txt §2.1. :param int replica: The ``replica`` number for this particular descriptor. This influences the ``secret-id-part`` of the descriptor (see :func:`~leekspin.rendezvous.calculateSecretIDPart`). :rtype: str :returns: A ``@type rendezvous-service-descriptor`` as a string. """ import time vers = torversions.getRandomVersion() versionsLine = rendezvous.generateVersionLine(vers) protocolVersionsLine = rendezvous.generateProtocolVersionsLine(vers) (secretPermanentKey, publicPermanentKey, permanentKeyLine) = rendezvous.generatePermanentKey() (secretSigningKey, publicSigningKey, signingKeyLine) = crypto.generateSigningKey() # TODO: Make generation of permanent_ids deal with HS "stealth" authorisation. permanentID = rendezvous.generatePermanentID(publicPermanentKey) # TODO: Implement per-client session-keys / descriptor cookies, see # rend-spec.txt §2.1. descCookie = rendezvous.createDescriptorCookie() descCookieB64 = base64.b64encode(descCookie)#.strip("=") # see rendclient.c rend_parse_service_authorization()↑↑↑ logging.info(("# Generated HS .onion address: %s\n" "# Generated HS descriptor cookie: %s") % (permanentID.encode("hex") + ".onion", descCookieB64)) currentTime = int(time.time()) publicationTimeLine = rendezvous.generatePublicationTimeLine(currentTime) (secretIDPart, secretIDLine) = rendezvous.calculateSecretIDPart(permanentID, currentTime, descCookieB64, replica) introductionPoints = rendezvous.generateIntroPoints(descCookieB64) rendServiceLine = rendezvous.generateRendServiceLine(permanentID, secretIDPart, replica) d = [] d.append(rendServiceLine) d.append(versionsLine) d.append(permanentKeyLine) d.append(secretIDLine) d.append(publicationTimeLine) d.append(protocolVersionsLine) d.append(introductionPoints) d.append(const.TOKEN_HS_SIGNATURE) document = "\r\n".join(d) + "\r\n" (_, _, documentDigestPKCS1) = crypto.digestDescriptorContent(document) descriptor = crypto.signDescriptorContent(document, secretSigningKey, token=const.TOKEN_HS_SIGNATURE) # "router-signature" is for relay/bridge descriptor signatures; we have to # replace it with just "signature": descriptor = descriptor.replace("router-signature", "signature") logging.info("%s\n" % descriptor) return descriptor
[docs]def createHiddenServiceDescriptors(count, replicas=2): """Generate hidden service descriptors. :param int count: How many sets of descriptors to generate. This essentially corresponds to the number of fake ``.onion`` s which should be mocked. :param int replicas: The number of times which one particular mocked Hidden Service should "replicate" its descriptor in the Tor Network's ``HSDir`` hashring. The default for real Hidden Services created with Tor is ``2``. """ logging.info("Generating %d hidden service descriptors..." % count) rendDescriptors = list() try: for i in range(int(count)): # Create replicas from [1, **replicas**], inclusive: for j in range(1, int(replicas)): desc = generateHSDesc(j) rendDescriptors.append(desc) except KeyboardInterrupt as keyint: logging.warn("Received keyboard interrupt.") logging.warn("Stopping descriptor creation and exiting.") code = 1515 except Exception as error: logging.exception(error) finally: logging.info("Writing descriptors to files...") descriptorFiles = { "rendezvous-service-descriptors": '\n'.join(rendDescriptors)} for fn, giantstring in descriptorFiles.items(): util.writeDescToFile(fn, giantstring) logging.info("Done.") code = 0 sys.exit(code)
[docs]def createRelayOrBridgeDescriptors(count, bridge=True, **kwargs): """Generate all types of descriptors and write them to files. :param int count: How many sets of descriptors to generate, i.e. how many mock bridges/relays to create. :param bool bridge: If ``True``, generate Bridge descriptors; otherwise, generate Relay descriptors. """ logging.info("Generating %d %s descriptors..." % (int(count), 'bridge' if bridge else 'relay')) logging.info("Generated router nicknames:") withoutTAP = False withoutNTOR = False if kwargs: if "withoutTAP" in kwargs: withoutTAP = kwargs.get("withoutTAP") if "withoutNTOR" in kwargs: withoutNTOR = kwargs.get("withoutNTOR") server_descriptors = list() netstatus_consensus = list() extrainfo_descriptors = list() try: # Add headers: netstatus_consensus.append( (b'flag-thresholds stable-uptime=613624 stable-mtbf=2488616 ' 'fast-speed=15000 guard-wfu=98.000% guard-tk=691200 ' 'guard-bw-inc-exits=55000 guard-bw-exc-exits=55000 enough-mtbf=1 ' 'ignoring-advertised-bws=0\n')) for i in xrange(int(count)): try: (extrainfo, server, netstatus) = generateDescriptors(bridge=bridge, withoutTAP=withoutTAP, withoutNTOR=withoutNTOR) except Exception as error: err, msg, tb = sys.exc_info() try: logging.debug(tb) logging.error(error) except: print(traceback.print_tb(tb)) print(error) else: server_descriptors.append(server) netstatus_consensus.append(netstatus) extrainfo_descriptors.append(extrainfo) except KeyboardInterrupt as keyint: logging.warn("Received keyboard interrupt.") logging.warn("Stopping descriptor creation and exiting.") code = 1515 finally: logging.info("Writing descriptors to files...") cached = "cached-extrainfo" cachedNew = "cached-extrainfo.new" # TODO: the `networkstatus-bridges` file and the `cached-consensus` # file should be sorted by fingerprint. if bridge: descriptorFiles = { "networkstatus-bridges": ''.join(netstatus_consensus), "bridge-descriptors": ''.join(server_descriptors)} else: # TODO: make the `cached-consensus` file have the appropriate # consensus headers. descriptorFiles = { "cached-consensus": ''.join(netstatus_consensus), "cached-descriptors": ''.join(server_descriptors)} # Both bridges and relay extrainfos are stored in the same filenames descriptorFiles[cachedNew] = ''.join(extrainfo_descriptors) if not os.path.isfile(cachedNew): with open(cachedNew, 'wb') as fh: fh.flush() if os.path.isfile(cachedNew): os.rename(cachedNew, cached) for fn, giantstring in descriptorFiles.items(): util.writeDescToFile(fn, giantstring) logging.info("Done.") code = 0 sys.exit(code)
[docs]def create(count, descriptorType=None, withoutTAP=False, withoutNTOR=False): """Create **count** descriptors of type **descriptor_type**. :param int count: The number of descriptors to generate. :type descriptorType: str or ``None`` :param descriptorType: One of ``"relay"``, ``"bridge"``, ``"hidden_service"``, or ``None``. :param bool withoutTAP: If ``True``, generate descriptors without support for the TAP handshake, e.g. without RSA keys. :param bool withoutTAP: If ``True``, generate descriptors without support for the ntor handshake, e.g. without Ed25519 keys. """ logging.info("Creating descriptor type %s" % descriptorType) if descriptorType in ('bridge', 'relay'): bridge = bool(descriptorType == 'bridge') createRelayOrBridgeDescriptors(count, bridge=bridge, withoutTAP=withoutTAP, withoutNTOR=withoutNTOR) elif descriptorType in ('hidden_service',): createHiddenServiceDescriptors(count)