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
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)
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
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