Example #1
0
def generateIntroPoints(descriptorCookie=None, introductionPoints=2):
    """Generate the ``introduction-points`` message for an HS descriptor.

    From `rend-spec.txt`_ §1.3::

        "introduction-points" NL encrypted-string

          [At most once]

           A list of introduction points. If the optional "descriptor-cookie" is
           used, this list is encrypted with AES in CTR mode with a random
           initialization vector of 128 bits that is written to
           the beginning of the encrypted string, and the "descriptor-cookie" as
           secret key of 128 bits length.

           The string containing the introduction point data (either encrypted
           or not) is encoded in base64, and surrounded with
           "-----BEGIN MESSAGE-----" and "-----END MESSAGE-----".

           The unencrypted string may begin with:

             "service-authentication" auth-type auth-data NL

               [Any number]

               The service-specific authentication data can be used to perform
               client authentication. This data is independent of the selected
               introduction point as opposed to "intro-authentication" below. The
               format of auth-data (base64-encoded or PEM format) depends on
               auth-type. See section 2 of this document for details on auth
               mechanisms.

           Subsequently, an arbitrary number of introduction point entries may
           follow, each containing the following data:

             "introduction-point" SP identifier NL

               [At start, exactly once]

               The identifier of this introduction point: the base32 encoded
               hash of this introduction point's identity key.

             "ip-address" SP ip4 NL

               [Exactly once]

               The IP address of this introduction point.

             "onion-port" SP port NL

               [Exactly once]

               The TCP port on which the introduction point is listening for
               incoming onion requests.

             "onion-key" NL a public key in PEM format

               [Exactly once]

               The public key that can be used to encrypt messages to this
               introduction point.

             "service-key" NL a public key in PEM format

               [Exactly once]

               The public key that can be used to encrypt messages to the hidden
               service.

             "intro-authentication" auth-type auth-data NL

               [Any number]

               The introduction-point-specific authentication data can be used
               to perform client authentication. This data depends on the
               selected introduction point as opposed to "service-authentication"
               above. The format of auth-data (base64-encoded or PEM format)
               depends on auth-type. See section 2 of this document for details
               on auth mechanisms.

            (This ends the fields in the encrypted portion of the descriptor.)

           [It's ok for Bob to advertise 0 introduction points. He might want
            to do that if he previously advertised some introduction points,
            and now he doesn't have any. -RD]

    .. _rend-spec.txt: https://gitweb.torproject.org/torspec.git/tree/rend-spec.txt
    """
    # Tor stores all generated authorization data for the authorization
    # protocols described in Sections 2.1 and 2.2 in a new file using the
    # following file format:
    #
    #   "client-name" human-readable client identifier NL
    #   "descriptor-cookie" 128-bit key ^= 22 base64 chars NL
    #
    # If the authorization protocol of Section 2.2 is used, Tor also generates
    # and stores the following data:
    #
    #   "client-key" NL a public key in PEM format

    # "service-authentication" auth-type auth-data NL

    # XXX We only currently support authTupe 0 (REND_NO_AUTH).
    #authType = random.randint(0, 2)
    authType = 0

    if authType == 0:
        # XXX Do we need authData for REND_NO_AUTH? It seems from tor's source
        # that the "session-key" for the AES-CTR rounds is ``"(none)"``?
        authData = 'XXX'
    elif authType == 1:
        authData = 'XXX'
    elif authType == 2:
        # XXX Need client-key?
        authData = 'XXX'

    unencrypted = []

    # TODO: This part is *super* wastful.  In the future, we might want to
    # make it save the necessary descriptor keys from the generation of some
    # relay router descriptors, then reuse those pre-generated routers to pick
    # intro points and generate their unencrypted data strings.
    for i in range(introductionPoints):
        # "introduction-point" SP identifier NL
        (_, publicSigningKey, _) = crypto.generateSigningKey()
        (_, fingerprintBinary) = crypto.getFingerprint(publicSigningKey)
        identifier = base64.b32encode(fingerprintBinary).lower()
        # "onion-key" NL a public key in PEM format
        (secretOnionKey, publicOnionKey,
         publicOnionKeyLine) = crypto.generateOnionKey()
        # "service-key" NL a public key in PEM format
        (secretServiceKey, publicServiceKey,
         publicServiceKeyLine) = crypto.generateOnionKey()

        introPoint = []
        introPoint.append(b"introduction-point %s" % identifier)
        introPoint.append(b"ip-address %s" % util.randomIPv4())
        introPoint.append(b"onion-port %s" % util.randomPort())
        introPoint.append(publicOnionKeyLine)
        introPoint.append(
            publicServiceKeyLine.replace("onion-key", "service-key"))
        # XXX Need to implement intro-authentication auth-data:
        # "intro-authentication" auth-type auth-data NL
        if authType == 2:
            introPoint.append(b"intro-authentication %s" %
                              (authType, authData))

        IP = "\n".join(introPoint)
        unencrypted.append(IP)

    unencrypted = "\n".join(unencrypted)

    # See §2.1 of rend-spec.txt
    if authType == 1 or authType == 2 or authType == 0:
        # When generating a hidden service descriptor, the service encrypts
        # the introduction-point part with a single randomly generated
        # symmetric 128-bit session key using AES-CTR as described for v2
        # hidden service descriptors in rend-spec. Afterwards, the service
        # encrypts the session key to all descriptor cookies using
        # AES. Authorized client should be able to efficiently find the
        # session key that is encrypted for him/her, so that 4 octet long
        # client ID are generated consisting of descriptor cookie and
        # initialization vector. Descriptors always contain a number of
        # encrypted session keys that is a multiple of 16 by adding fake
        # entries.  Encrypted session keys are ordered by client IDs in order
        # to conceal addition or removal of authorized clients by the service
        # provider.
        sessionKey = createDescriptorCookie()  # XXX last two bytes
        counter = Counter.new(128)
        ciphre = AES.new(sessionKey,
                         mode=AES.MODE_CTR,
                         IV=b"XXX",
                         counter=counter)
        encrypted = ciphre.encrypt(unencrypted)
        encryptedB64 = base64.b64encode(encrypted)
        encryptedNoHeaders = crypto.chunkInto64CharsPerLine(encryptedB64,
                                                            separator=b"\r\n")
    # See §2.2 of rend-spec.txt
    #elif authType == 2:

    intros = []
    if authType != 0:
        intros.append(b"service-authentication %s %s" % (authType, authData))
    intros.append(b"introduction-points")
    intros.append(TOR_BEGIN_MSG)
    intros.append(encryptedNoHeaders)
    intros.append(TOR_END_MSG)

    introPoints = "\r\n".join(intros)
    return introPoints
Example #2
0
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)
Example #3
0
def generateIntroPoints(descriptorCookie=None, introductionPoints=2):
    """Generate the ``introduction-points`` message for an HS descriptor.

    From `rend-spec.txt`_ §1.3::

        "introduction-points" NL encrypted-string

          [At most once]

           A list of introduction points. If the optional "descriptor-cookie" is
           used, this list is encrypted with AES in CTR mode with a random
           initialization vector of 128 bits that is written to
           the beginning of the encrypted string, and the "descriptor-cookie" as
           secret key of 128 bits length.

           The string containing the introduction point data (either encrypted
           or not) is encoded in base64, and surrounded with
           "-----BEGIN MESSAGE-----" and "-----END MESSAGE-----".

           The unencrypted string may begin with:

             "service-authentication" auth-type auth-data NL

               [Any number]

               The service-specific authentication data can be used to perform
               client authentication. This data is independent of the selected
               introduction point as opposed to "intro-authentication" below. The
               format of auth-data (base64-encoded or PEM format) depends on
               auth-type. See section 2 of this document for details on auth
               mechanisms.

           Subsequently, an arbitrary number of introduction point entries may
           follow, each containing the following data:

             "introduction-point" SP identifier NL

               [At start, exactly once]

               The identifier of this introduction point: the base32 encoded
               hash of this introduction point's identity key.

             "ip-address" SP ip4 NL

               [Exactly once]

               The IP address of this introduction point.

             "onion-port" SP port NL

               [Exactly once]

               The TCP port on which the introduction point is listening for
               incoming onion requests.

             "onion-key" NL a public key in PEM format

               [Exactly once]

               The public key that can be used to encrypt messages to this
               introduction point.

             "service-key" NL a public key in PEM format

               [Exactly once]

               The public key that can be used to encrypt messages to the hidden
               service.

             "intro-authentication" auth-type auth-data NL

               [Any number]

               The introduction-point-specific authentication data can be used
               to perform client authentication. This data depends on the
               selected introduction point as opposed to "service-authentication"
               above. The format of auth-data (base64-encoded or PEM format)
               depends on auth-type. See section 2 of this document for details
               on auth mechanisms.

            (This ends the fields in the encrypted portion of the descriptor.)

           [It's ok for Bob to advertise 0 introduction points. He might want
            to do that if he previously advertised some introduction points,
            and now he doesn't have any. -RD]

    .. _rend-spec.txt: https://gitweb.torproject.org/torspec.git/tree/rend-spec.txt
    """
    # Tor stores all generated authorization data for the authorization
    # protocols described in Sections 2.1 and 2.2 in a new file using the
    # following file format:
    #
    #   "client-name" human-readable client identifier NL
    #   "descriptor-cookie" 128-bit key ^= 22 base64 chars NL
    #
    # If the authorization protocol of Section 2.2 is used, Tor also generates
    # and stores the following data:
    #
    #   "client-key" NL a public key in PEM format

    # "service-authentication" auth-type auth-data NL

    # XXX We only currently support authTupe 0 (REND_NO_AUTH).
    #authType = random.randint(0, 2)
    authType = 0

    if authType == 0:
        # XXX Do we need authData for REND_NO_AUTH? It seems from tor's source
        # that the "session-key" for the AES-CTR rounds is ``"(none)"``?
        authData = 'XXX'
    elif authType == 1:
        authData = 'XXX'
    elif authType == 2:
        # XXX Need client-key?
        authData = 'XXX'

    unencrypted = []

    # TODO: This part is *super* wastful.  In the future, we might want to
    # make it save the necessary descriptor keys from the generation of some
    # relay router descriptors, then reuse those pre-generated routers to pick
    # intro points and generate their unencrypted data strings.
    for i in range(introductionPoints):
        # "introduction-point" SP identifier NL
        (_, publicSigningKey, _) = crypto.generateSigningKey()
        (_, fingerprintBinary) = crypto.getFingerprint(publicSigningKey)
        identifier = base64.b32encode(fingerprintBinary).lower()
        # "onion-key" NL a public key in PEM format
        (secretOnionKey,
         publicOnionKey,
         publicOnionKeyLine) = crypto.generateOnionKey()
        # "service-key" NL a public key in PEM format
        (secretServiceKey,
         publicServiceKey,
         publicServiceKeyLine) = crypto.generateOnionKey()

        introPoint = []
        introPoint.append(b"introduction-point %s" % identifier)
        introPoint.append(b"ip-address %s" % util.randomIPv4())
        introPoint.append(b"onion-port %s" % util.randomPort())
        introPoint.append(publicOnionKeyLine)
        introPoint.append(publicServiceKeyLine.replace("onion-key", "service-key"))
        # XXX Need to implement intro-authentication auth-data:
        # "intro-authentication" auth-type auth-data NL
        if authType == 2:
            introPoint.append(b"intro-authentication %s" % (authType, authData))

        IP = "\n".join(introPoint)
        unencrypted.append(IP)

    unencrypted = "\n".join(unencrypted)

    # See §2.1 of rend-spec.txt
    if authType == 1 or authType == 2 or authType == 0:
        # When generating a hidden service descriptor, the service encrypts
        # the introduction-point part with a single randomly generated
        # symmetric 128-bit session key using AES-CTR as described for v2
        # hidden service descriptors in rend-spec. Afterwards, the service
        # encrypts the session key to all descriptor cookies using
        # AES. Authorized client should be able to efficiently find the
        # session key that is encrypted for him/her, so that 4 octet long
        # client ID are generated consisting of descriptor cookie and
        # initialization vector. Descriptors always contain a number of
        # encrypted session keys that is a multiple of 16 by adding fake
        # entries.  Encrypted session keys are ordered by client IDs in order
        # to conceal addition or removal of authorized clients by the service
        # provider.
        sessionKey = createDescriptorCookie()  # XXX last two bytes
        counter = Counter.new(128)
        ciphre = AES.new(sessionKey, mode=AES.MODE_CTR, IV=b"XXX", counter=counter)
        encrypted = ciphre.encrypt(unencrypted)
        encryptedB64 = base64.b64encode(encrypted)
        encryptedNoHeaders = crypto.chunkInto64CharsPerLine(encryptedB64,
                                                            separator=b"\r\n")
    # See §2.2 of rend-spec.txt
    #elif authType == 2:

    intros = []
    if authType != 0:
        intros.append(b"service-authentication %s %s" % (authType, authData))
    intros.append(b"introduction-points")
    intros.append(TOR_BEGIN_MSG)
    intros.append(encryptedNoHeaders)
    intros.append(TOR_END_MSG)

    introPoints = "\r\n".join(intros)
    return introPoints
Example #4
0
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 = 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)

    SIDSKey, SIDPCert, (onionkey, signingkey) = crypto.makeOnionKeys()
    idkeyPrivate = tls.getPrivateKey(SIDSKey)
    idkeyDigest = hashlib.sha1(idkeyPrivate).digest()

    idkeyPublic = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_ASN1,
                                                 SIDPCert.get_pubkey())
    idkeyPublic = re.sub(const.OPENSSL_BEGIN_KEY, '', idkeyPublic)
    idkeyPublic = re.sub(const.OPENSSL_END_KEY, '', idkeyPublic)
    idkeyPublic = idkeyPublic.strip()

    identDigest = hashlib.sha1(idkeyPublic).digest()
    fingerprint = hashlib.sha1(idkeyPublic).hexdigest().upper()
    fpr = crypto.convertToSpaceyFingerprint(fingerprint)

    extrainfoDoc = extrainfo.generateExtraInfo(nick, fingerprint,
                                               timestamp, ipv4, port)
    extrainfoDigest = hashlib.sha1(extrainfoDoc).digest()
    extrainfoHexdigest = hashlib.sha1(extrainfoDoc).hexdigest().upper()
    extrainfoSig = crypto.signDescriptorDigest(SIDSKey, extrainfoDigest)
    extrainfoDesc = extrainfoDoc + extrainfoSig

    serverDoc = []
    serverDoc.append("@purpose bridge")
    serverDoc.append("router %s %s %s 0 0" % (nick, ipv4, port))
    serverDoc.append("or-address [%s]:%s" % (ipv6, port))
    serverDoc.append("platform Tor %s on Linux" % vers)
    serverDoc.append("%s\npublished %s" % (protocols, timestamp))
    serverDoc.append("%s" % server.makeFingerprintLine(fingerprint, vers))
    serverDoc.append("uptime %s\n%s" % (uptime, bandwidth))
    serverDoc.append("%s" % server.makeExtraInfoDigestLine(extrainfoHexdigest,
                                                           vers))
    serverDoc.append("%s%s%s" % (onionkey, signingkey,
                                 server.makeHSDirLine(vers)))
    serverDoc.append("contact Somebody <*****@*****.**>")
    if nacl is not None:
        ntorkey = ntor.getNTORPublicKey()
        if ntorkey is not None:
            serverDoc.append("ntor-onion-key %s" % ntorkey)
    serverDoc.append("reject *:*\nrouter-signature\n")

    serverDesc = '\n'.join(serverDoc)
    serverDescDigest = hashlib.sha1(serverDesc).digest()

    netstatusDesc = netstatus.generateNetstatus(nick, identDigest,
                                                serverDescDigest, timestamp,
                                                ipv4, port, ipv6=ipv6,
                                                bandwidth_line=bandwidth)
    serverDesc += crypto.signDescriptorDigest(SIDSKey, serverDescDigest)
    return extrainfoDesc, serverDesc, netstatusDesc