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)
def handshake(self): # 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] handshake = self._fingerprint_bytes handshake += curve25519_to_bytes(self._B) handshake += curve25519_to_bytes(self._X) return handshake
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)