commit c9ba8cffb49e063690c456f97978aaaf279f92df Author: Isis Lovecruft isis@torproject.org Date: Mon Mar 24 03:27:30 2014 +0000
Remove old scripts/gen_bridge_descriptors; use leekspin. --- scripts/gen_bridge_descriptors | 870 ---------------------------------------- 1 file changed, 870 deletions(-)
diff --git a/scripts/gen_bridge_descriptors b/scripts/gen_bridge_descriptors deleted file mode 100644 index 362ed3d..0000000 --- a/scripts/gen_bridge_descriptors +++ /dev/null @@ -1,870 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Generate valid and signed descriptors for mocked Tor relays or bridges. - -.. todo:: Finish enough CFFI_ bindings for the newer PyNaCl_ (or enough of the - SWIG_ bindings for the older pynacl_) to be able to emulate the following - curvecp_ command (the ``curvecp*`` commands require libchloride_): - - $ curvecpmakekey ntor-key - $ curvecpprintkey ntor-key > ntor-key.hex - $ python -c 'import binascii, sys; \ - key_hex=open('./ntor-key.hex','rb').read();\ - key_b64=binascii.b2a_base64(binascii.unhexlify(key_hex));\ - sys.stdout.write(key_b64);' - - .. _CFFI: https://cffi.readthedocs.org - .. _PyNaCl: https://github.com/seanlynch/pynacl - .. _SWIG: https://github.com/swig/swig - .. _pynacl: https://github.com/seanlynch/pynacl - .. _curvecp: http://curvecp.org/ - .. _libchloride: https://github.com/jedisct1/libchloride - -.. authors:: Isis Lovecruft isis@torproject.org 0xA3ADB67A2CDB8B35 - Matthew Finkel sysrqb@torproject.org -.. licence:: distributed with BridgeDB, see included LICENSE file -.. copyright:: (c) 2013 Matthew Finkel, Isis Lovecruft, The Tor Project, Inc. -""" - -from __future__ import print_function -from __future__ import absolute_import -from __future__ import unicode_literals - -import argparse -import binascii -import hashlib -import ipaddr -import math -import os -import sys -import random -import re -import time -import traceback - -from datetime import datetime -from codecs import open as open - -try: - import OpenSSL.crypto -except (ImportError, NameError) as error: - print("This script requires pyOpenSSL>=0.13.0") - raise SystemExit(error.message) -try: - from bridgedb.parse import versions -except (ImportError, NameError) as error: - print(error.message) - print("WARNING: Cannot import bridgedb package!", - "Generated descriptor content won't accurately reflect descriptor", - "information created by different Tor versions.", sep='\n\t') -try: - import nacl - import nacl.secret -except (ImportError, NameError, IOError): - nacl = secret = None - - -#: The version of this script -__version__ = '0.2.0' - -#: The <major>.<minor>.<micro>.<rev> version numbers for tor, taken from the -#: 'server-versions' line of a consensus file -SERVER_VERSIONS = """0.2.2.39,0.2.3.24-rc,0.2.3.25, -0.2.4.5-alpha,0.2.4.6-alpha,0.2.4.7-alpha,0.2.4.8-alpha,0.2.4.9-alpha, -0.2.4.10-alpha,0.2.4.11-alpha,0.2.4.12-alpha,0.2.4.14-alpha,0.2.4.15-rc, -0.2.4.16-rc,0.2.4.17-rc,0.2.5.1-alpha""".replace('\n', '').split(',') - -#: Strings found in PEM-encoded objects created by Tor -TOR_BEGIN_KEY = "-----BEGIN RSA PUBLIC KEY-----" -TOR_END_KEY = "-----END RSA PUBLIC KEY-----" -TOR_BEGIN_SK = "-----BEGIN RSA PRIVATE KEY-----" -TOR_END_SK = "-----END RSA PRIVATE KEY-----" -TOR_BEGIN_SIG = "-----BEGIN SIGNATURE-----" -TOR_END_SIG = "-----END SIGNATURE-----" - -#: Strings found in PEM-encoded objects created by OpenSSL -OPENSSL_BEGIN_KEY = "-----BEGIN PRIVATE KEY-----" -OPENSSL_END_KEY = "-----END PRIVATE KEY-----" -OPENSSL_BEGIN_CERT = "-----BEGIN CERTIFICATE-----" -OPENSSL_END_CERT = "-----END CERTIFICATE-----" - -PEM = OpenSSL.crypto.FILETYPE_PEM -ASN1 = OpenSSL.crypto.FILETYPE_ASN1 - - -class OpenSSLKeyGenError(Exception): - """Raised when there is a problem generating a new key.""" - - -def getArgParser(): - """Get our :class:`~argparse.ArgumentParser`.""" - parser = argparse.ArgumentParser(add_help=True) - parser.version = __version__ - parser.description = "Generate a signed set of network-status, " - parser.description += "extra-info, and server descriptor documents " - parser.description += "for mock Tor relays or bridges." - infoargs = parser.add_mutually_exclusive_group() - verbargs = parser.add_mutually_exclusive_group() - infoargs.add_argument("-v", "--verbose", action="store_true", - help="print information to stdout") - infoargs.add_argument("-q", "--quiet", action="store_true", - help="don't print anything") - verbargs.add_argument("--version", action="store_true", - help="print the %s version and exit".format( - parser.prog)) - group = parser.add_argument_group() - group.title = "required arguments" - group.add_argument("-n", "--descriptors", default=0, - help="generate <n> descriptor sets", type=int) - return parser - -def randomIP(): - """Create a random IPv4 or IPv6 address.""" - maybe = int(random.getrandbits(1)) - ip = randomIPv4() if maybe else randomIPv6() - return ip - -def randomIPv4(): - """Create a random IPv4 address.""" - return ipaddr.IPv4Address(random.getrandbits(32)) - -def randomIPv6(): - """Create a random IPv6 address.""" - return ipaddr.IPv6Address(random.getrandbits(128)) - -def randomPort(): - """Get a random integer in the range [1024, 65535].""" - return random.randint(1025, 65535) - -def getHexString(size): - """Get a capitalised hexidecimal string ``size`` bytes long. - - :param integer size: The number of bytes in the returned string. - :rtype: string - :returns: A hex string. - """ - s = "" - for i in xrange(size): - s += random.choice("ABCDEF0123456789") - return s - -def makeTimeStamp(now=None, fmt=None, variation=False, period=None): - """Get a random timestamp suitable for a bridge server descriptor. - - :param int now: The time, in seconds since the Epoch, to generate the - timestamp for (and to consider as the maximum time, if other options - are enabled). - :param string fmt: A strftime(3) format string for the timestamp. If not - given, defaults to ISO-8601 format without the 'T' separator. - :param bool variation: If True, enable timestamp variation. Otherwise, - make all timestamps be set to the current time. - :type period: int or None - :param period: If given, vary the generated timestamps to be a random time - between ``period`` hours ago and the current time. If None, generate - completely random timestamps which are anywhere between the Unix Epoch - and the current time. This parameter only has an effect if - ``variation`` is enabled. - """ - now = int(now) if now is not None else int(time.time()) - fmt = fmt if fmt else "%Y-%m-%d %H:%M:%S" - - if variation: - then = 1 - if period is not None: - secs = int(period) * 3600 - then = now - secs - # Get a random number between one epochseconds number and another - diff = random.randint(then, now) - # Then rewind the clock - now = diff - - return time.strftime(fmt, time.localtime(now)) - -def shouldHaveOptPrefix(version): - """Returns true if a tor ``version`` should have the 'opt ' prefix. - - In tor, up to and including, version 0.2.3.25, server-descriptors (bridge - or regular) prefixed several lines with 'opt '. For the 0.2.3.x series, - these lines were: - - 'protocols' - - 'fingerprint' - - 'hidden-service-dir' - - 'extra-info-digest' - - :param string version: One of ``SERVER_VERSIONS``. - :rtype: bool - :returns: True if we should include the 'opt ' prefix. - """ - changed_in = versions.Version('0.2.4.1-alpha', package='tor') - our_version = versions.Version(version, package='tor') - if our_version < changed_in: - return True - return False - -def makeProtocolsLine(version=None): - """Generate an appropriate [bridge-]server-descriptor 'protocols' line. - - :param string version: One of ``SERVER_VERSIONS``. - :rtype: string - :returns: An '@type [bridge-]server-descriptor' 'protocols' line. - """ - line = '' - if (version is not None) and shouldHaveOptPrefix(version): - line += 'opt ' - line += 'protocols Link 1 2 Circuit 1' - return line - -def convertToSpaceyFingerprint(fingerprint): - """Convert to a space-delimited 40 character fingerprint - - Given a 40 character string, usually the the SHA-1 hash of the - DER encoding of an ASN.1 RSA public key, such as: - | - | 72C2F0AE1C14F40ED37ED5F5434B64711A658E46 - | - - convert it to the following format: - | - | 72C2 F0AE 1C14 F40E D37E D5F5 434B 6471 1A65 8E46 - | - - :param string fingerprint: A 40 character hex fingerprint. - :rtype: string - :returns: A 4-character space-delimited fingerprint. - """ - - assert len(fingerprint) == 40 - return " ".join([fingerprint[i:i+4] for i in xrange(0, 40, 4)]) - -def makeFingerprintLine(fingerprint, version=None): - """Generate an appropriate [bridge-]server-descriptor 'fingerprint' line. - - For example, for tor-0.2.3.25 and prior versions, this would look like: - | - | opt fingerprint D4BB C339 2560 1B7F 226E 133B A85F 72AF E734 0B29 - | - - :param string fingerprint: A public key fingerprint in groups of four, - separated by spaces. - :param string version: One of ``SERVER_VERSIONS``. - :rtype: string - :returns: An '@type [bridge-]server-descriptor' 'published' line. - """ - line = '' - if (version is not None) and shouldHaveOptPrefix(version): - line += 'opt ' - line += 'fingerprint %s' % convertToSpaceyFingerprint(fingerprint) - return line - -def makeBandwidthLine(variance=30): - """Create a random 'bandwidth' line with some plausible burst variance. - - From torspec.git/dir-spec.txt, §2.1 "Router descriptors": - | "bandwidth" bandwidth-avg bandwidth-burst bandwidth-observed NL - | - | [Exactly once] - | - | Estimated bandwidth for this router, in bytes per second. The - | "average" bandwidth is the volume per second that the OR is willing - | to sustain over long periods; the "burst" bandwidth is the volume - | that the OR is willing to sustain in very short intervals. The - | "observed" value is an estimate of the capacity this relay can - | handle. The relay remembers the max bandwidth sustained output over - | any ten second period in the past day, and another sustained input. - | The "observed" value is the lesser of these two numbers. - - The "observed" bandwidth, in this function, is taken as some random value, - bounded between 20KB/s and 2MB/s. For example, say: - - >>> import math - >>> variance = 25 - >>> observed = 180376 - >>> percentage = float(variance) / 100. - >>> percentage - 0.25 - - The ``variance`` in this context is the percentage of the "observed" - bandwidth, which will be added to the "observed" bandwidth, and becomes - the value for the "burst" bandwidth: - - >>> burst = observed + math.ceil(observed * percentage) - >>> assert burst > observed - - This doesn't do much, since the "burst" bandwidth in a real - [bridge-]server-descriptor is reported by the OR; this function mostly - serves to avoid generating completely-crazy, totally-implausible bandwidth - values. The "average" bandwidth value is then just the mean value of the - other two. - - :param integer variance: The percent of the fake "observed" bandwidth to - increase the "burst" bandwidth by. - :rtype: string - :returns: A "bandwidth" line for a [bridge-]server-descriptor. - """ - observed = random.randint(20 * 2**10, 2 * 2**30) - percentage = float(variance) / 100. - burst = int(observed + math.ceil(observed * percentage)) - bandwidths = [burst, observed] - nitems = len(bandwidths) if (len(bandwidths) > 0) else float('nan') - avg = int(math.ceil(float(sum(bandwidths)) / nitems)) - line = "bandwidth %s %s %s" % (avg, burst, observed) - return line - -def makeExtraInfoDigestLine(hexdigest, version): - """Create a line to embed the hex SHA-1 digest of the extrainfo. - - :param string hexdigest: Should be the hex-encoded (uppercase) output of - the SHA-1 digest of the generated extrainfo document (this is the - extra-info descriptor, just without the signature at the end). This is - the same exact digest which gets signed by the OR server identity key, - and that signature is appended to the extrainfo document to create the - extra-info descriptor. - :param string version: One of ``SERVER_VERSIONS``. - :rtype: string - :returns: An ``@type [bridge-]server-descriptor`` 'extra-info-digest' - line. - """ - line = '' - if (version is not None) and shouldHaveOptPrefix(version): - line += 'opt ' - line += 'extra-info-digest %s' % hexdigest - return line - -def makeHSDirLine(version): - """This line doesn't do much… all the cool kids are HSDirs these days. - - :param string version: One of ``SERVER_VERSIONS``. - :rtype: string - :returns: An ``@type [bridge-]server-descriptor`` 'hidden-service-dir' - line. - """ - line = '' - if (version is not None) and shouldHaveOptPrefix(version): - line += 'opt ' - line += 'hidden-service-dir' - return line - -def createRSAKey(bits=1024): - """Create a new RSA keypair. - - :param integer bits: The bitlength of the keypair to generate. - :rtype: :class:`OpenSSL.crypto.PKey` - :returns: An RSA keypair of bitlength ``bits``. - """ - key = OpenSSL.crypto.PKey() - key.generate_key(OpenSSL.crypto.TYPE_RSA, bits) - if not key.check(): - raise OpenSSLKeyGenError("Couldn't create new RSA 1024-bit key") - return key - -def createNTORKey(): - """Create a Curve25519 key.""" - if nacl is None: - raise NotImplementedError - -def createKey(selfsign=True, digest='sha1'): - """Create a set of public and private RSA keypairs and corresponding certs. - - :param boolean selfsign: If True, use the private key to sign the public - certificate (otherwise, the private key will only sign the private - certificate to which it is attached). - :param string digest: The digest to use. (default: 'sha1') - :rtype: 4-tuple - :returns: (private_key, private_cert, public_key, public_cert) - """ - privateKey = createRSAKey() - privateCert = attachKey(privateKey, createTLSCert()) - publicKey = privateCert.get_pubkey() - publicCert = attachKey(publicKey, createTLSCert(), selfsign=False) - - if selfsign: - # We already signed the publicCert with the publicKey, now we need to - # sign the publicCert with the privateKey - publicCert.sign(privateKey, digest) - - return (privateKey, privateCert, publicKey, publicCert) - -def attachKey(key, cert, selfsign=True, digest='sha1', pem=False): - """Attach a key to a cert and optionally self-sign the cert. - - :type key: :class:`OpenSSL.crypto.PKey` - :param key: A previously generated key, used to generate the other half of - the keypair. - :type cert: :class:`OpenSSL.crypto.X509` - :param cert: A TLS certificate without a public key attached to it, such - as one created with :func:`createTLSCert`. - :param boolean selfsign: If True, use the ``key`` to self-sign the - ``cert``. Note that this will result in several nasty OpenSSL errors - if you attempt to export the public key of a cert in order to create - another cert which *only* holds the public key. (Otherwise, if you - used the first cert in the following example, it contains both halves - of the RSA keypair.) Do this instead: - - >>> secret_key = createRSAKey() - >>> secret_cert = attachKey(secret_key, createTLSCert(selfsign=True)) - >>> public_key = secret_cert.get_pubkey() - >>> public_cert = attachKey(public_key, createTLSCert, selfsign=False) - - :param string digest: The digest to use. Check your OpenSSL installation - to see which are supported. We pretty much only care about 'sha1' and - 'sha256' here. - :param boolean pem: If True, return a 3-tuple of PEM-encoded strings, one - for each of (certificate, private_key, public_key), where - 'certificate' is the original ``cert`` with the ``key`` attached, - 'private_key' is the private RSA modulus, primes, and exponents - exported from the 'certificate', and 'public_key' is the public RSA - modulus exported from the cert. NOTE: Using this when passing in a key - with only the public RSA modulus (as described above) will result in - nasty OpenSSL errors. Trust me, you do *not* want to try to parse - OpenSSL's errors. - :raises: An infinite, labyrinthine mire of non-Euclidean OpenSSL errors - with non-deterministic messages and self-referential errorcodes, - tangled upon itself in contempt of sanity, hope, and decent software - engineering practices. - :returns: If ``pem`` is True, then the values described there are - returned. Otherwise, returns the ``cert`` with the ``key`` attached to - it. - """ - # Attach the key to the certificate - cert.set_pubkey(key) - - if selfsign: - # Self-sign the cert with the key, using the specified hash digest - cert.sign(key, digest) - - if pem: - certificate = OpenSSL.crypto.dump_certificate(PEM, cert) - private_key = OpenSSL.crypto.dump_privatekey(PEM, key) - public_key = OpenSSL.crypto.dump_privatekey(PEM, cert.get_pubkey()) - return certificate, private_key, public_key - return cert - -def createTLSCert(lifetime=None): - """Create a TLS certificate. - - :param integer lifetime: The time, in seconds, that the certificate should - remain valid for. - :rtype: :class:`OpenSSL.crypto.X509` - :returns: A certificate, unsigned, and without a key attached to it. - """ - if not lifetime: - # see `router_initialize_tls_context()` in src/or/router.c - lifetime = 5 + random.randint(0, 361) - lifetime = lifetime * 24 * 3600 - if int(random.getrandbits(1)): - lifetime -= 1 - - cert = OpenSSL.crypto.X509() - - timeFormat = lambda x: time.strftime("%Y%m%d%H%M%SZ", x) - now = time.time() - before = time.gmtime(now) - after = time.gmtime(now + lifetime) - cert.set_notBefore(timeFormat(before)) - cert.set_notAfter(timeFormat(after)) - - return cert - -def createTLSLinkCert(lifetime=7200): - """Create a certificate for the TLS link layer. - - The TLS certificate used for the link layer between Tor relays, and - between clients and their bridges/guards, has a shorter lifetime than the - other certificates. Currently, these certs expire after two hours. - - :param integer lifetime: The time, in seconds, that the certificate should - remain valid for. - :rtype: :class:`OpenSSL.crypto.X509` - :returns: A certificate, unsigned, and without a key attached to it. - """ - cert = createTLSCert(lifetime) - cert.get_subject().CN = 'www.' + getHexString(16) + '.net' - cert.get_issuer().CN = 'www.' + getHexString(10) + '.com' - return cert - -def getPEMPublicKey(cert): - publicKey = OpenSSL.crypto.dump_privatekey(PEM, cert.get_pubkey()) - # It says "PRIVATE KEY" just because the stupid pyOpenSSL wrapper is - # braindamaged. You can check that it doesn't include the RSA private - # exponents and primes by substituting ``OpenSSL.crypto.FILETYPE_TEXT`` - # for the above ``PEM``. - publicKey = re.sub(OPENSSL_BEGIN_KEY, TOR_BEGIN_KEY, publicKey) - publicKey = re.sub(OPENSSL_END_KEY, TOR_END_KEY, publicKey) - return publicKey - -def getPEMPrivateKey(key): - privateKey = OpenSSL.crypto.dump_privatekey(PEM, key) - privateKey = re.sub(OPENSSL_BEGIN_KEY, TOR_BEGIN_SK, privateKey) - privateKey = re.sub(OPENSSL_END_KEY, TOR_END_SK, privateKey) - return privateKey - -def makeOnionKeys(bridge=True, digest='sha1'): - """Make all the keys and certificates necessary to fake an OR. - - The encodings for the various key and descriptor digests needed are - described in dir-spec.txt and tor-spec.txt, the latter mostly for the - padding and encoding used in the creation of an OR's keys. - - For the "router" line in a networkstatus document, the following encodings - are specified: - - From dir-spec.txt, commit 36761c7d5, L1504-1512: - | - | […] "Identity" is a hash of its - | identity key, encoded in base64, with trailing equals sign(s) - | removed. "Digest" is a hash of its most recent descriptor as - | signed (that is, not including the signature), encoded in base64. - | - - Before the hash digest of an OR's identity key is base64-encoded for - inclusion in a networkstatus document, the hash digest is created in the - following manner: - - From tor-spec.txt, commit 36761c7d5, L109-110: - | - | When we refer to "the hash of a public key", we mean the SHA-1 hash of the - | DER encoding of an ASN.1 RSA public key (as specified in PKCS.1). - | - - From tor-spec.txt, commit 36761c7d5, L785-787: - | - | The "legacy identity" and "identity fingerprint" fields are the SHA1 - | hash of the PKCS#1 ASN1 encoding of the next onion router's identity - | (signing) key. (See 0.3 above.) - | - - :param boolean bridge: If False, generate a server OR ID key, a signing - key, and a TLS certificate/key pair. If True, generate a client ID key - as well. - :param string digest: The digest to use. (default: 'sha1') - :returns: The server ID key, and a tuple of strings (fingerprint, - onion-key, signing-key), where onion-key and secret key are the strings - which should directly go into a server-descriptor. There are a *ton* of - keys and certs in the this function. If you need more for some reason, - this is definitely the thing you want to modify. - """ - serverID = createKey(True) - SIDSKey, SIDSCert, SIDPKey, SIDPCert = serverID - serverLinkCert = createTLSLinkCert() - serverLinkCert.sign(SIDSKey, digest) - - if bridge: - # For a bridge, a "client" ID key is used to generate the fingerprint - clientID = createKey(True) - CIDSKey, CIDSCert, CIDPKey, CIDPCert = clientID - - # XXX I think we're missing some of the signatures - # see torspec.git/tor-spec.txt §4.2 on CERTS cells - clientLinkCert = createTLSLinkCert() - clientLinkCert.sign(CIDSKey, digest) - else: - CIDSKey, CIDSCert, CIDPKey, CIDPCert = serverID - - signing = createKey() - signSKey, signSCert, signPKey, signPCert = signing - onion = createKey() - onionSKey, onionSCert, onionPKey, onionPCert = onion - - onionKeyString = 'onion-key\n%s' % getPEMPublicKey(onionPCert) - signingKeyString = 'signing-key\n%s' % getPEMPublicKey(signPCert) - - return SIDSKey, SIDPCert, (onionKeyString, signingKeyString) - -def generateExtraInfo(fingerprint, ts, ipv4, port): - """Create an OR extra-info document. - - See §2.2 "Extra-info documents" in torspec.git/dir-spec.txt. - - :param string fingerprint: A space-separated, hex-encoded, SHA-1 digest of - the OR's private identity key. See :func:`convertToSpaceyFingerprint`. - :param string ts: An ISO-8601 timestamp. See :func:`makeTimeStamp`. - :param string ipv4: An IPv4 address. - :param string port: The OR's ORPort. - :rtype: string - :returns: An extra-info document (unsigned). - """ - extra = [] - extra.append("extra-info Unnamed %s" % fingerprint) - extra.append("published %s" % ts) - extra.append("write-history %s (900 s)\n3188736,2226176,2866176" % ts) - extra.append("read-history %s (900 s)\n3891200,2483200,2698240" % ts) - extra.append("dirreq-write-history %s (900 s)\n1024,0,2048" % ts) - extra.append("dirreq-read-history %s (900 s)\n0,0,0" % ts) - extra.append("geoip-db-digest %s\ngeoip6-db-digest %s" - % (getHexString(40), getHexString(40))) - extra.append("dirreq-stats-end %s (86400 s)\ndirreq-v3-ips" % ts) - extra.append("dirreq-v3-reqs\ndirreq-v3-resp") - extra.append( - "ok=16,not-enough-sigs=0,unavailable=0,not-found=0,not-modified=0,busy=0") - extra.append("dirreq-v3-direct-dl complete=0,timeout=0,running=0") - extra.append("dirreq-v3-tunneled-dl complete=12,timeout=0,running=0") - extra.append("transport obfs3 %s:%d" % (ipv4, port + 1)) - extra.append("transport obfs2 %s:%d" % (ipv4, port + 2)) - extra.append("bridge-stats-end %s (86400 s)\nbridge-ips ca=8" % ts) - extra.append("bridge-ip-versions v4=8,v6=0\nbridge-ip-transports <OR>=8") - extra.append("router-signature\n") - - return '\n'.join(extra) - -def generateNetstatus(idkey_digest, server_desc_digest, timestamp, - ipv4, orport, ipv6=None, dirport=None, - flags='Fast Guard Running Stable Valid', - bandwidth_line=None): - """Generate an ``@type networkwork-status`` document (unsigned). - - DOCDOC - - :param string idkey_digest: The SHA-1 digest of the router's public identity - key. - :param XXX server_desc_digest: The SHA-1 digest of the router's - ``@type [bridge-]server-descriptor``, before the descriptor is signed. - :param XXX timestamp: - """ - - idkey_b64 = binascii.b2a_base64(idkey_digest) - idb64 = str(idkey_b64).strip().rstrip('==') - server_b64 = binascii.b2a_base64(server_desc_digest) - srvb64 = str(server_b64).strip().rstrip('==') - - if bandwidth_line is not None: - bw = int(bandwidth_line.split()[-1]) / 1024 # The 'observed' value - dirport = dirport if dirport else 0 - - status = [] - status.append("r Unnamed %s %s %s %s %s %d" % (idb64, srvb64, timestamp, - ipv4, orport, dirport)) - if ipv6 is not None: - status.append("a [%s]:%s" % (ipv6, orport)) - status.append("s %s\nw Bandwidth=%s\np reject 1-65535\n" % (flags, bw)) - - return '\n'.join(status) - -def signDescriptorDigest(key, descriptorDigest, digest='sha1'): - """Ugh...I hate OpenSSL. - - The extra-info-digest is a SHA-1 hash digest of the extrainfo document, - that is, the entire extrainfo descriptor up until the end of the - 'router-signature' line and including the newline, but not the actual - signature. - - The signature at the end of the extra-info descriptor is a signature of - the above extra-info-digest. This signature is appended to the end of the - extrainfo document, and the extra-info-digest is added to the - 'extra-info-digest' line of the [bridge-]server-descriptor. - - The first one of these was created with a raw digest, the second with a - hexdigest. They both encode the the 'sha1' digest type if you check the - `-asnparse` output (instead of `-raw -hexdump`). - - .. command:: openssl rsautl -inkey eiprivkey -verify -in eisig1 -raw -hexdump - | - | 0000 - 00 01 ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................ - | 0010 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................ - | 0020 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................ - | 0030 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................ - | 0040 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................ - | 0050 - ff ff ff ff ff ff ff ff-ff ff ff ff 00 30 21 30 .............0!0 - | 0060 - 09 06 05 2b 0e 03 02 1a-05 00 04 14 42 25 41 fb ...+........B%A. - | 0070 - 82 ef 11 f4 5f 2c 95 53-67 2d bb fe 7f c2 34 7f ...._,.Sg-....4. - - .. command:: openssl rsautl -inkey eiprivkey -verify -in eisig2 -raw -hexdump - | - | 0000 - 00 01 ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................ - | 0010 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................ - | 0020 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................ - | 0030 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................ - | 0040 - ff ff ff ff ff ff ff ff-ff ff ff ff ff ff ff ff ................ - | 0050 - ff ff ff ff ff ff ff ff-ff ff ff ff 00 30 21 30 .............0!0 - | 0060 - 09 06 05 2b 0e 03 02 1a-05 00 04 14 44 30 ab 90 ...+........D0.. - | 0070 - 93 d1 08 21 df 87 c2 39-2a 04 1c a5 bb 34 44 cd ...!...9*....4D. - - .. todo:: See the RSA PKCS_ Standard v2.2 for why this function is totally - wrong. - - .. _PKCS: http://www.emc.com/collateral/white-papers/h11300-pkcs-1v2-2-rsa-cryptograph... - - :type key: :class:`OpenSSL.crypto.PKey` - :param key: An RSA private key. - :param string descriptorDigest: The raw SHA-1 digest of any descriptor - document. - :param string digest: The digest to use. (default: 'sha1') - """ - sig = binascii.b2a_base64(OpenSSL.crypto.sign(key, descriptorDigest, - digest)) - sigCopy = sig - originalLength = len(sigCopy.replace('\n', '')) - - # Only put 64 bytes of the base64 signature per line: - sigSplit = [] - while len(sig) > 0: - sigSplit.append(sig[:64]) - sig = sig[64:] - sigFormatted = '\n'.join(sigSplit) - - sigFormattedCopy = sigFormatted - formattedLength = len(sigFormattedCopy.replace('\n', '')) - - if originalLength != formattedLength: - print("WARNING: signDescriptorDocument(): %s" - % "possible bad reformatting for signature.") - print("DEBUG: signDescriptorDocument(): original=%d formatted=%d" - % (originalLength, formattedLength)) - print("DEBUG: original:\n%s\nformatted:\n%s" - % (sigCopy, sigFormatted)) - - sigWithHeaders = TOR_BEGIN_SIG + '\n' \ - + sigFormatted \ - + TOR_END_SIG + '\n' - return sigWithHeaders - -def generateDescriptors(): - """Create keys, certs, signatures, documents and descriptors for an OR. - - :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 = randomIPv4() - ipv6 = randomIPv6() - port = randomPort() - - vers = random.choice(SERVER_VERSIONS) - uptime = int(random.randint(1800, 63072000)) - bandwidth = makeBandwidthLine() - timestamp = makeTimeStamp(variation=True, period=36) - protocols = makeProtocolsLine(vers) - - SIDSKey, SIDPCert, (onionkey, signingkey) = makeOnionKeys() - idkey_private = getPEMPrivateKey(SIDSKey) - idkey_digest = hashlib.sha1(idkey_private).digest() - - idkey_public = OpenSSL.crypto.dump_privatekey(ASN1, - SIDPCert.get_pubkey()) - idkey_public = re.sub(OPENSSL_BEGIN_KEY, '', idkey_public) - idkey_public = re.sub(OPENSSL_END_KEY, '', idkey_public) - idkey_public = idkey_public.strip() - - ident_digest = hashlib.sha1(idkey_public).digest() - fingerprint = hashlib.sha1(idkey_public).hexdigest().upper() - fpr = convertToSpaceyFingerprint(fingerprint) - - extrainfo_document = generateExtraInfo(fingerprint, timestamp, ipv4, port) - extrainfo_digest = hashlib.sha1(extrainfo_document).digest() - extrainfo_hexdigest = hashlib.sha1(extrainfo_document).hexdigest().upper() - extrainfo_sig = signDescriptorDigest(SIDSKey, extrainfo_digest) - extrainfo_desc = extrainfo_document + extrainfo_sig - - server = [] - server.append("@purpose bridge") - server.append("router Unnamed %s %s 0 0" % (ipv4, port)) - server.append("or-address [%s]:%s" % (ipv6, port)) - server.append("platform Tor %s on Linux" % vers) - server.append("%s\npublished %s" % (protocols, timestamp)) - server.append("%s" % makeFingerprintLine(fingerprint, vers)) - server.append("uptime %s\n%s" % (uptime, bandwidth)) - server.append("%s" % makeExtraInfoDigestLine(extrainfo_hexdigest, vers)) - server.append("%s%s%s" % (onionkey, signingkey, makeHSDirLine(vers))) - server.append("contact Somebody somebody@example.com") - if nacl is not None: - server.append("ntor-onion-key %s" - % binascii.b2a_base64(createNTORKey())) - server.append("reject *:*\nrouter-signature\n") - server_desc = '\n'.join(server) - - server_desc_digest = hashlib.sha1(server_desc).digest() - netstatus_desc = generateNetstatus(ident_digest, server_desc_digest, - timestamp, ipv4, port, ipv6=ipv6, - bandwidth_line=bandwidth) - server_desc += signDescriptorDigest(SIDSKey, server_desc_digest) - return extrainfo_desc, server_desc, netstatus_desc - -def writeDescToFile(filename, descriptors): - """Open ``filename`` and write a string containing descriptors into it. - - :param string filename: The name of the file to write to. - :param string descriptors: A giant string containing descriptors, - newlines, formatting, whatever is necessary to make it look like a - file tor would generate. - """ - encoding = sys.getfilesystemencoding() - descript = descriptors.encode(encoding, 'replace') - try: - with open(filename, 'wb', encoding=encoding, errors='replace') as fh: - fh.write(descript) - fh.flush() - except (IOError, OSError) as err: - print("Failure while attempting to write descriptors to file '%s': %s" - % (filename, err.message)) - -def create(count): - """Generate all types of descriptors and write them to files. - - :param integer count: How many sets of descriptors to generate, i.e. how - many mock bridges/relays to create. - """ - if nacl is None: - print("WARNING: Can't import PyNaCl. NTOR key generation is disabled.") - print("Generating %d bridge descriptors..." % int(count)) - - server_descriptors = list() - netstatus_consensus = list() - extrainfo_descriptors = list() - try: - for i in xrange(int(count)): - print(".", end='') - try: - extrainfo, server, netstatus = generateDescriptors() - except Exception as error: - err, msg, tb = sys.exc_info() - print(traceback.print_tb(tb)) - print(error) - else: - server_descriptors.append(server) - netstatus_consensus.append(netstatus) - extrainfo_descriptors.append(extrainfo) - except KeyboardInterrupt as keyint: - print("Received keyboard interrupt.") - print("Stopping descriptor creation and exiting.") - code = 1515 - finally: - print("\nWriting descriptors to files...", end="") - - cached = "cached-extrainfo.new" - descriptor_files = { - "networkstatus-bridges": ''.join(netstatus_consensus), - "bridge-descriptors": ''.join(server_descriptors), - "cached-extrainfo.new": ''.join(extrainfo_descriptors)} - - if not os.path.isfile(cached): - with open(cached, 'wb') as fh: - fh.flush() - if os.path.isfile(cached): - os.rename(cached, "cached-extrainfo") - - for fn, giantstring in descriptor_files.items(): - writeDescToFile(fn, giantstring) - print("Done.") - code = 0 - sys.exit(code) - -if __name__ == "__main__": - try: - parser = getArgParser() - options = parser.parse_args() - - if options.quiet: - print = lambda x: True - if options.version: - print("gen_bridge_descriptors-%s" % __version__) - sys.exit(0) - if options.descriptors and (options.descriptors > 0): - create(options.descriptors) - else: - raise SystemExit(parser.format_help()) - - except Exception as error: - raise SystemExit(error)
tor-commits@lists.torproject.org