class RLPxSession(object): ephemeral_ecc = None nonce = None token = None aes_secret = None aes_enc = None aes_dec = None egress_mac = None ingress_mac = None remote_node = None _authentication_sent = False is_ready = False def __init__(self, peer=None): # persisted peer data. keys are the nodeid # session data self.peer = peer if peer: self.node = peer.local_node.ecc else: self.node = None def __repr__(self): return '<RLPxSession (%s)>' % self.address.encode('hex') def encrypt(self, header, frame): """ header-mac: right128 of egress-mac.update(aes(mac-secret,egress-mac)^header-ciphertext) """ assert self.is_ready is True def aes(data): return self.aes_enc.update(data) def mac(data): return self.egress_mac.update(data) # header assert len(header) == 16 # zero padded to 16 bytes header_ciphertext = aes(header) assert len(header_ciphertext) <= 32 # must not be larger than mac # FIXME mac-secret!? header_mac = mac(sxor(aes(mac('')), header_ciphertext))[-16:] # frame frame_ciphertext = aes(frame) frame_mac = self.egress_mac.update(frame_ciphertext) return header_ciphertext + header_mac + frame_ciphertext + frame_mac def decrypt(self, data): assert self.is_ready is True def aes(data): return self.aes_dec.update(data) def mac(data): return self.egress_mac.update(data) header_ciphertext = data[:16] header_mac = data[16:32] header = aes(header_ciphertext) expected_header_mac = mac(sxor(aes(mac(''), header_ciphertext)))[-16:] assert expected_header_mac == header_mac # FIXME check frame length in header # assume datalen == framelen for now frame_mac = self.egress_mac.update(frame_ciphertext) data = aes(data[32:]) def create_auth_message(self, remote_pubkey, token=None, ephemeral_privkey=None, nonce=None): """ 1. initiator generates ecdhe-random and nonce and creates auth 2. initiator connects to remote and sends auth New: E(remote-pubk, S(ephemeral-privk, ecdh-shared-secret ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x0 ) Known: E(remote-pubk, S(ephemeral-privk, token ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x1) """ if not token: # new ecdh_shared_secret = self.node.get_ecdh_key(remote_pubkey) token = ecdh_shared_secret flag = 0x0 else: flag = 0x1 nonce = nonce or ienc(random.randint(0, 2**256 - 1)) assert len(nonce) == 32 token_xor_nonce = sxor(token, nonce) assert len(token_xor_nonce) == 32 # generate session ephemeral key if not ephemeral_privkey: ephemeral_privkey = sha3(ienc(random.randint(0, 2**256 - 1))) self.ephemeral_ecc = ECCx(raw_privkey=ephemeral_privkey) ephemeral_pubkey = self.ephemeral_ecc.raw_pubkey assert len(ephemeral_pubkey) == 512 / 8 # S(ephemeral-privk, ecdh-shared-secret ^ nonce) S = self.ephemeral_ecc.sign(token_xor_nonce) assert len(S) == 65 # S || H(ephemeral-pubk) || pubk || nonce || 0x0 auth_message = S + sha3( ephemeral_pubkey) + self.node.raw_pubkey + nonce + chr(flag) assert len(auth_message) == 65 + 32 + 64 + 32 + 1 == 194 return auth_message def encrypt_auth_message(self, auth_message, remote_pubkey): return self.node.ecies_encrypt(auth_message, remote_pubkey) encrypt_auth_ack_message = encrypt_auth_message def send_authentication(self, remote_node, ephermal_privkey=None): auth_message = self.create_auth_message(remote_node, ephermal_privkey) self.peer.send(auth_message) self._authentication_sent = True def receive_authentication(self, ciphertext): """ 3. optionally, remote decrypts and verifies auth (checks that recovery of signature == H(ephemeral-pubk)) 4. remote generates authAck from remote-ephemeral-pubk and nonce (authAck = authRecipient handshake) optional: remote derives secrets and preemptively sends protocol-handshake (steps 9,11,8,10) """ auth_message = self.node.ecies_decrypt(ciphertext) # S || H(ephemeral-pubk) || pubk || nonce || 0x[0|1] assert len(auth_message) == 65 + 32 + 64 + 32 + 1 == 194 signature = auth_message[:65] H_remote_ephemeral_pubkey = auth_message[65:65 + 32] remote_pubkey = auth_message[65 + 32:65 + 32 + 64] nonce = auth_message[65 + 32 + 64:65 + 32 + 64 + 32] known_flag = auth_message[65 + 32 + 64 + 32:] # token or new ecdh_shared_secret token_database = dict() # FIXME token_found = False if known_flag == 1: token = token_database.get(remote_pubkey) if token: token_found = True else: token = ecdh_shared_secret = self.node.get_ecdh_key(remote_pubkey) # verify auth # S(ephemeral-privk, ecdh-shared-secret ^ nonce) ecdh_shared_secret = self.node.get_ecdh_key(remote_pubkey) signed = sxor(ecdh_shared_secret, nonce) # recover remote ephemeral pubkey remote_ephemeral_pubkey = ecdsa_recover(signed, signature) assert ecdsa_verify(remote_ephemeral_pubkey, signature, signed) # checks that recovery of signature == H(ephemeral-pubk) assert H_remote_ephemeral_pubkey == sha3(remote_ephemeral_pubkey) return dict(remote_ephemeral_pubkey=remote_ephemeral_pubkey, token=token, token_found=token_found, ecdh_shared_secret=ecdh_shared_secret, remote_pubkey=remote_pubkey, nonce=nonce, known_flag=known_flag) def create_auth_ack_message(self, ephemeral_pubkey, nonce, token_found=False): """ authRecipient = E(remote-pubk, remote-ephemeral-pubk || nonce || 0x1) // token found authRecipient = E(remote-pubk, remote-ephemeral-pubk || nonce || 0x0) // token not found nonce and empehemeral-pubk are local! """ flag = chr(1 if token_found else 0) msg = ephemeral_pubkey + nonce + flag assert len(msg) == 64 + 32 + 1 == 97 return msg def something(): ################## # send authentication if not yet if not self._authentication_sent: remote_node = RemoteNode(remote_pubkey) # FIXME LOOKUP self.send_authentication(remote_node) # - success -> AcknowledgeAuthentication self.acknowledge_authentication(other, remote_pubkey, remote_ecdhe_pubkey) # ecdhe_shared_secret = ecdh.agree(ecdhe-random, ecdhe-random-public) # Compute public key with the local private key and return a 512bits shared key ecdhe_shared_secret = self.ephemeral_ecc.get_ecdh_key(remote_pubkey) ecdhe_pubkey = self.ephemeral_ecc.get_pubkey() # shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || remote-nonce)) shared_secret = sha3(ecdhe_shared_secret + sha3(ienc(self.nonce) + ienc(remote_nonce))) self.aes_secret = sha3(ecdhe_shared_secret + shared_secret) self.mac_secret = sha3(ecdhe_shared_secret + self.aes_secret) # egress-mac = sha3(mac-secret^nonce || auth) self.egress_mac = sha3(sxor(self.mac_secret, self.nonce) + ciphertext) # ingress-mac = sha3(mac-secret^remote-nonce || auth) self.ingress_mac = sha3( sxor(self.mac_secret, remote_nonce) + ciphertext) self.token = sha3(shared_secret) iv = pyelliptic.Cipher.gen_IV('aes-256-ctr') self.aes_enc = pyelliptic.Cipher(self.aes_secret, iv, 1, ciphername='aes-256-ctr') self.aes_dec = pyelliptic.Cipher(self.aes_secret, iv, 0, ciphername='aes-256-ctr') self.is_ready = True