Beispiel #1
0
    def __init__(self, onion_router):
        # 5.1.4. The "ntor" handshake

        # This handshake uses a set of DH handshakes to compute a set of
        # shared keys which the client knows are shared only with a particular
        # server, and the server knows are shared with whomever sent the
        # original handshake (or with nobody at all).  Here we use the
        # "curve25519" group and representation as specified in "Curve25519:
        # new Diffie-Hellman speed records" by D. J. Bernstein.

        # [The ntor handshake was added in Tor 0.2.4.8-alpha.]

        # In this section, define:
        #   H(x,t) as HMAC_SHA256 with message x and key t.
        #   H_LENGTH  = 32.
        #   ID_LENGTH = 20.
        #   G_LENGTH  = 32
        #   PROTOID   = "ntor-curve25519-sha256-1"
        #   t_mac     = PROTOID | ":mac"
        #   t_key     = PROTOID | ":key_extract"
        #   t_verify  = PROTOID | ":verify"
        #   MULT(a,b) = the multiplication of the curve25519 point 'a' by the
        #               scalar 'b'.
        #   G         = The preferred base point for curve25519 ([9])
        #   KEYGEN()  = The curve25519 key generation algorithm, returning
        #               a private/public keypair.
        #   m_expand  = PROTOID | ":key_expand"

        # H is defined as hmac()
        # MULT is included in the curve25519 library as get_shared_key()
        # KEYGEN() is curve25519.Private()
        super().__init__(onion_router)

        self.protoid = b'ntor-curve25519-sha256-1'
        self.t_mac = self.protoid + b':mac'
        self.t_key = self.protoid + b':key_extract'
        self.t_verify = self.protoid + b':verify'
        self.m_expand = self.protoid + b':key_expand'

        # To perform the handshake, the client needs to know an identity key
        # digest for the server, and an ntor onion key (a curve25519 public
        # key) for that server. Call the ntor onion key "B".  The client
        # generates a temporary keypair:
        #     x,X = KEYGEN()
        self._x = curve25519_private()

        self._X = curve25519_public_from_private(self._x)

        self._fingerprint_bytes = onion_router.fingerprint

        self._B = curve25519_public_from_bytes(
            onion_router.descriptor.ntor_key)

        # and generates a client-side handshake with contents:
        #   NODEID      Server identity digest  [ID_LENGTH bytes]
        #   KEYID       KEYID(B)                [H_LENGTH bytes]
        #   CLIENT_PK   X                       [G_LENGTH bytes]
        self._handshake = self._fingerprint_bytes
        self._handshake += curve25519_to_bytes(self._B)
        self._handshake += curve25519_to_bytes(self._X)
Beispiel #2
0
 def __init__(self,
              onion_address,
              descriptor_cookie=None,
              auth_type=AuthType.No):
     self._onion_address, self._permanent_id, onion_identity_pk = self.parse_onion(
         onion_address)
     self._onion_identity_pk = curve25519_public_from_bytes(
         onion_identity_pk) if onion_identity_pk else None
     if self._onion_identity_pk:
         raise Exception('v3 onion hidden service not supported yet')
     self._descriptor_cookie = b64decode(
         descriptor_cookie) if descriptor_cookie else None
     self._auth_type = auth_type
     if descriptor_cookie and auth_type == AuthType.No:
         raise RuntimeError('You must specify auth type')
     if not descriptor_cookie and auth_type != AuthType.No:
         raise RuntimeError('You must specify descriptor cookie')
Beispiel #3
0
    def _parse_onion(self, onion_address):
        # TODO: only v2 onion
        if onion_address.endswith('.onion'):
            onion_address = onion_address[:-6]

        if len(onion_address) == self.REND_SERVICE_ID_LEN_BASE32:
            permanent_id = b32decode(onion_address.upper())
            assert len(
                permanent_id
            ) == self.REND_SERVICE_ID_LEN, 'You must specify valid V2 onion hostname'
            return onion_address, permanent_id, None
        elif len(onion_address) == self.HS_SERVICE_ADDR_LEN_BASE32:
            # tor ref: hs_parse_address
            decoded = b32decode(onion_address.upper())
            pubkey = decoded[:self.ED25519_PUBKEY_LEN]
            # checksum decoded[self.ED25519_PUBKEY_LEN:self.ED25519_PUBKEY_LEN + self.HS_SERVICE_ADDR_CHECKSUM_LEN_USED]
            # version decoded[self.ED25519_PUBKEY_LEN + self.HS_SERVICE_ADDR_CHECKSUM_LEN_USED:]
            return onion_address, None, curve25519_public_from_bytes(pubkey)
Beispiel #4
0
    def complete_handshake(self, handshake_data):
        # The server's handshake reply is:
        # SERVER_PK   Y                       [G_LENGTH bytes]
        # AUTH        H(auth_input, t_mac)    [H_LENGTH bytes]
        y = handshake_data[:
                           32]  # ntor data curve25519::public_key::key_size_in_bytes
        auth = handshake_data[32:]  # ntor auth is SHA1, 32 in bytes?
        assert len(auth) == 32  #

        # The client then checks Y is in G^* [see NOTE below], and computes

        # secret_input = EXP(Y,x) | EXP(B,x) | ID | B | X | Y | PROTOID
        si = curve25519_get_shared(self._x, curve25519_public_from_bytes(y))
        si += curve25519_get_shared(self._x, self._B)
        si += self._fingerprint_bytes
        si += curve25519_to_bytes(self._B)
        si += curve25519_to_bytes(self._X)
        si += y
        si += b'ntor-curve25519-sha256-1'

        # KEY_SEED = H(secret_input, t_key)
        # verify = H(secret_input, t_verify)
        key_seed = hmac(self.t_key, si)
        verify = hmac(self.t_verify, si)

        # auth_input = verify | ID | B | Y | X | PROTOID | "Server"
        ai = verify
        ai += self._fingerprint_bytes
        ai += curve25519_to_bytes(self._B)
        ai += y
        ai += curve25519_to_bytes(self._X)
        ai += self.protoid
        ai += b'Server'

        # The client verifies that AUTH == H(auth_input, t_mac).
        if auth != hmac(self.t_mac, ai):
            raise NtorError('auth input does not match.')

        # Both parties check that none of the EXP() operations produced the
        # point at infinity. [NOTE: This is an adequate replacement for
        # checking Y for group membership, if the group is curve25519.]

        # Both parties now have a shared value for KEY_SEED.  They expand this
        # into the keys needed for the Tor relay protocol, using the KDF
        # described in 5.2.2 and the tag m_expand.

        # 5.2.2. KDF-RFC5869

        # For newer KDF needs, Tor uses the key derivation function HKDF from
        # RFC5869, instantiated with SHA256.  (This is due to a construction
        # from Krawczyk.)  The generated key material is:

        #     K = K_1 | K_2 | K_3 | ...

        #     Where H(x,t) is HMAC_SHA256 with value x and key t
        #       and K_1     = H(m_expand | INT8(1) , KEY_SEED )
        #       and K_(i+1) = H(K_i | m_expand | INT8(i+1) , KEY_SEED )
        #       and m_expand is an arbitrarily chosen value,
        #       and INT8(i) is a octet with the value "i".

        # In RFC5869's vocabulary, this is HKDF-SHA256 with info == m_expand,
        # salt == t_key, and IKM == secret_input.
        # WARN: length must be 92
        # 72 + byte_type rend_nonce     [20]; << ignored now
        return hkdf_sha256(key_seed, length=72, info=self.m_expand)
Beispiel #5
0
 def _B(self):
     return curve25519_public_from_bytes(self._onion_router.descriptor.ntor_key)  # noqa: N802