def test_body_length(): initiator, responder = test_session() msg_frame = sha3('test') + 'notpadded' msg_frame_padded = rzpad16(msg_frame) frame_size = len(msg_frame) msg_header = struct.pack('>I', frame_size)[1:] + sha3('x')[:16 - 3] msg_ct = initiator.encrypt(msg_header, msg_frame_padded) r = responder.decrypt(msg_ct) assert r['header'] == msg_header assert r['frame'] == msg_frame # test excess data msg_ct2 = initiator.encrypt(msg_header, msg_frame_padded) r = responder.decrypt(msg_ct2 + 'excess data') assert r['header'] == msg_header assert r['frame'] == msg_frame assert r['bytes_read'] == len(msg_ct) # test data underflow data = initiator.encrypt(msg_header, msg_frame_padded) header = responder.decrypt_header(data[:32]) body_size = struct.unpack('>I', '\x00' + header[:3])[0] exception_raised = False try: responder.decrypt_body(data[32:-1], body_size) except FormatError: exception_raised = True assert exception_raised
def unpack(self, message): """ macSize = 256 / 8 = 32 sigSize = 520 / 8 = 65 headSize = macSize + sigSize = 97 hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:] shouldhash := crypto.Sha3(buf[macSize:]) """ mdc = message[:32] if mdc != crypto.sha3(message[32:]): log.warn('packet with wrong mcd') raise WrongMAC() signature = message[32:97] assert len(signature) == 65 signed_data = crypto.sha3(message[97:]) remote_pubkey = crypto.ecdsa_recover(signed_data, signature) assert len(remote_pubkey) == 512 / 8 if not crypto.verify(remote_pubkey, signature, signed_data): raise InvalidSignature() cmd_id = self.decoders['cmd_id'](message[97]) assert cmd_id in self.cmd_id_map.values() payload = rlp.decode(message[98:]) assert isinstance(payload, list) expiration = self.decoders['expiration'](payload.pop()) if time.time() > expiration: raise PacketExpired() return remote_pubkey, cmd_id, payload, mdc
def unpack(self, message): """ macSize = 256 / 8 = 32 sigSize = 520 / 8 = 65 headSize = macSize + sigSize = 97 hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:] shouldhash := crypto.Sha3(buf[macSize:]) """ mdc = message[:32] if mdc != crypto.sha3(message[32:]): log.warn('packet with wrong mcd') raise WrongMAC() signature = message[32:97] assert len(signature) == 65 signed_data = crypto.sha3(message[97:]) remote_pubkey = crypto.ecdsa_recover(signed_data, signature) assert len(remote_pubkey) == 512 / 8 # if not crypto.verify(remote_pubkey, signature, signed_data): # raise InvalidSignature() cmd_id = self.decoders['cmd_id'](message[97]) cmd = self.rev_cmd_id_map[cmd_id] payload = rlp.decode(message[98:], strict=False) assert isinstance(payload, list) # ignore excessive list elements as required by EIP-8. payload = payload[:self.cmd_elem_count_map.get(cmd, len(payload))] return remote_pubkey, cmd_id, payload, mdc
def get_connected_apps(): a_config = dict(p2p=dict(listen_host='127.0.0.1', listen_port=3005), node=dict(privkey_hex=encode_hex(crypto.sha3(b'a')))) b_config = copy.deepcopy(a_config) b_config['p2p']['listen_port'] = 3006 b_config['node']['privkey_hex'] = encode_hex(crypto.sha3(b'b')) a_app = BaseApp(a_config) peermanager.PeerManager.register_with_app(a_app) a_app.start() b_app = BaseApp(b_config) peermanager.PeerManager.register_with_app(b_app) b_app.start() a_peermgr = a_app.services.peermanager b_peermgr = b_app.services.peermanager # connect host = b_config['p2p']['listen_host'] port = b_config['p2p']['listen_port'] pubkey = crypto.privtopub(decode_hex(b_config['node']['privkey_hex'])) a_peermgr.connect((host, port), remote_pubkey=pubkey) return a_app, b_app
def unpack(self, message): """ macSize = 256 / 8 = 32 sigSize = 520 / 8 = 65 headSize = macSize + sigSize = 97 hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:] shouldhash := crypto.Sha3(buf[macSize:]) """ mdc = message[:32] assert mdc == crypto.sha3(message[32:]) signature = message[32:97] assert len(signature) == 65 signed_data = crypto.sha3(message[97:]) remote_pubkey = crypto.ecdsa_recover(signed_data, signature) assert len(remote_pubkey) == 512 / 8 if not crypto.verify(remote_pubkey, signature, signed_data): raise InvalidSignature() cmd_id = self.decoders['cmd_id'](message[97]) assert cmd_id in self.cmd_id_map.values() payload = rlp.decode(message[98:]) assert isinstance(payload, list) expiration = self.decoders['expiration'](payload.pop()) if time.time() > expiration: raise PacketExpired() return remote_pubkey, cmd_id, payload, mdc
def get_connected_apps(): a_config = dict(p2p=dict(listen_host='127.0.0.1', listen_port=3000), node=dict(privkey_hex=crypto.sha3('a').encode('hex'))) b_config = copy.deepcopy(a_config) b_config['p2p']['listen_port'] = 3001 b_config['node']['privkey_hex'] = crypto.sha3('b').encode('hex') a_app = BaseApp(a_config) peermanager.PeerManager.register_with_app(a_app) a_app.start() b_app = BaseApp(b_config) peermanager.PeerManager.register_with_app(b_app) b_app.start() a_peermgr = a_app.services.peermanager b_peermgr = b_app.services.peermanager # connect host = b_config['p2p']['listen_host'] port = b_config['p2p']['listen_port'] pubkey = crypto.privtopub(b_config['node']['privkey_hex'].decode('hex')) a_peermgr.connect((host, port), remote_pubkey=pubkey) return a_app, b_app
def setup_cipher(self): # https://github.com/ethereum/cpp-ethereum/blob/develop/libp2p/RLPxFrameIO.cpp#L34 assert self.responder_nonce assert self.initiator_nonce assert self.auth_init assert self.auth_ack assert self.remote_ephemeral_pubkey if not self.ecc.is_valid_key(self.remote_ephemeral_pubkey): raise InvalidKeyError('invalid remote ephemeral pubkey') # derive base secrets from ephemeral key agreement # ecdhe-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) ecdhe_shared_secret = self.ephemeral_ecc.get_ecdh_key( self.remote_ephemeral_pubkey) # shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)) shared_secret = sha3(ecdhe_shared_secret + sha3(self.responder_nonce + self.initiator_nonce)) self.ecdhe_shared_secret = ecdhe_shared_secret # FIXME DEBUG self.shared_secret = shared_secret # FIXME DEBUG # token = sha3(shared-secret) self.token = sha3(shared_secret) self.token_by_pubkey[self.remote_pubkey] = self.token # aes-secret = sha3(ecdhe-shared-secret || shared-secret) self.aes_secret = sha3(ecdhe_shared_secret + shared_secret) # mac-secret = sha3(ecdhe-shared-secret || aes-secret) self.mac_secret = sha3(ecdhe_shared_secret + self.aes_secret) # setup sha3 instances for the MACs # egress-mac = sha3.update(mac-secret ^ recipient-nonce || auth-sent-init) mac1 = sha3_256( sxor(self.mac_secret, self.responder_nonce) + self.auth_init) # ingress-mac = sha3.update(mac-secret ^ initiator-nonce || auth-recvd-ack) mac2 = sha3_256( sxor(self.mac_secret, self.initiator_nonce) + self.auth_ack) if self.is_initiator: self.egress_mac, self.ingress_mac = mac1, mac2 else: self.egress_mac, self.ingress_mac = mac2, mac1 ciphername = 'aes-256-ctr' iv = "\x00" * 16 assert len(iv) == 16 self.aes_enc = pyelliptic.Cipher(self.aes_secret, iv, 1, ciphername=ciphername) self.aes_dec = pyelliptic.Cipher(self.aes_secret, iv, 0, ciphername=ciphername) self.mac_enc = AES.AESCipher(self.mac_secret, AES.MODE_ECB).encrypt self.is_ready = True
def create_auth_message(self, remote_pubkey, 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) """ assert self.is_initiator if not self.ecc.is_valid_key(remote_pubkey): raise InvalidKeyError('invalid remote pubkey') self.remote_pubkey = remote_pubkey token = self.token_by_pubkey.get(remote_pubkey) if not token: # new ecdh_shared_secret = self.ecc.get_ecdh_key(remote_pubkey) token = ecdh_shared_secret flag = 0x0 else: flag = 0x1 self.initiator_nonce = nonce or sha3( ienc(random.randint(0, 2**256 - 1))) assert len(self.initiator_nonce) == 32 token_xor_nonce = sxor(token, self.initiator_nonce) assert len(token_xor_nonce) == 32 ephemeral_pubkey = self.ephemeral_ecc.raw_pubkey assert len(ephemeral_pubkey) == 512 / 8 if not self.ecc.is_valid_key(ephemeral_pubkey): raise InvalidKeyError('invalid ephemeral pubkey') # 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.ecc.raw_pubkey + \ self.initiator_nonce + chr(flag) assert len(auth_message) == 65 + 32 + 64 + 32 + 1 == 194 return auth_message
def setup_cipher(self): # https://github.com/ethereum/cpp-ethereum/blob/develop/libp2p/RLPxFrameIO.cpp#L34 assert self.responder_nonce assert self.initiator_nonce assert self.auth_init assert self.auth_ack assert self.remote_ephemeral_pubkey if not self.ecc.is_valid_key(self.remote_ephemeral_pubkey): raise InvalidKeyError('invalid remote ephemeral pubkey') # derive base secrets from ephemeral key agreement # ecdhe-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) ecdhe_shared_secret = self.ephemeral_ecc.get_ecdh_key(self.remote_ephemeral_pubkey) # shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)) shared_secret = sha3( ecdhe_shared_secret + sha3(self.responder_nonce + self.initiator_nonce)) self.ecdhe_shared_secret = ecdhe_shared_secret # FIXME DEBUG self.shared_secret = shared_secret # FIXME DEBUG # token = sha3(shared-secret) self.token = sha3(shared_secret) self.token_by_pubkey[self.remote_pubkey] = self.token # aes-secret = sha3(ecdhe-shared-secret || shared-secret) self.aes_secret = sha3(ecdhe_shared_secret + shared_secret) # mac-secret = sha3(ecdhe-shared-secret || aes-secret) self.mac_secret = sha3(ecdhe_shared_secret + self.aes_secret) # setup sha3 instances for the MACs # egress-mac = sha3.update(mac-secret ^ recipient-nonce || auth-sent-init) mac1 = sha3_256(sxor(self.mac_secret, self.responder_nonce) + self.auth_init) # ingress-mac = sha3.update(mac-secret ^ initiator-nonce || auth-recvd-ack) mac2 = sha3_256(sxor(self.mac_secret, self.initiator_nonce) + self.auth_ack) if self.is_initiator: self.egress_mac, self.ingress_mac = mac1, mac2 else: self.egress_mac, self.ingress_mac = mac2, mac1 ciphername = 'aes-256-ctr' iv = "\x00" * 16 assert len(iv) == 16 self.aes_enc = pyelliptic.Cipher(self.aes_secret, iv, 1, ciphername=ciphername) self.aes_dec = pyelliptic.Cipher(self.aes_secret, iv, 0, ciphername=ciphername) self.mac_enc = AES.AESCipher(self.mac_secret, AES.MODE_ECB).encrypt self.is_ready = True
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 test_signature(): bob = get_ecc(b'secret2') # sign message = crypto.sha3(b"Hello Alice") signature = bob.sign(message) # verify signature assert crypto.verify(bob.raw_pubkey, signature, message) is True assert crypto.ECCx(raw_pubkey=bob.raw_pubkey).verify(signature, message) is True # wrong signature message = crypto.sha3(b"Hello Alicf") assert crypto.ECCx(raw_pubkey=bob.raw_pubkey).verify(signature, message) is False assert crypto.verify(bob.raw_pubkey, signature, message) is False
def pack(self, cmd_id, payload): """ UDP packets are structured as follows: hash || signature || packet-type || packet-data packet-type: single byte < 2**7 // valid values are [1,4] packet-data: RLP encoded list. Packet properties are serialized in the order in which they're defined. See packet-data below. Offset | 0 | MDC | Ensures integrity of packet, 65 | signature | Ensures authenticity of sender, `SIGN(sender-privkey, MDC)` 97 | type | Single byte in range [1, 4] that determines the structure of Data 98 | data | RLP encoded, see section Packet Data The packets are signed and authenticated. The sender's Node ID is determined by recovering the public key from the signature. sender-pubkey = ECRECOVER(Signature) The integrity of the packet can then be verified by computing the expected MDC of the packet as: MDC = SHA3(sender-pubkey || type || data) As an optimization, implementations may look up the public key by the UDP sending address and compute MDC before recovering the sender ID. If the MDC values do not match, the packet can be dropped. """ assert cmd_id in self.cmd_id_map.values() assert isinstance(payload, list) cmd_id = self.encoders['cmd_id'](cmd_id) expiration = self.encoders['expiration'](int(time.time() + self.expiration)) encoded_data = rlp.encode(payload + [expiration]) # print rlp.decode(encoded_data) signed_data = crypto.sha3(cmd_id + encoded_data) signature = crypto.sign(signed_data, self.privkey) assert crypto.verify(self.pubkey, signature, signed_data) # assert self.pubkey == crypto.ecdsa_recover(signed_data, signature) # assert crypto.verify(self.pubkey, signature, signed_data) assert len(signature) == 65 mdc = crypto.sha3(signature + cmd_id + encoded_data) assert len(mdc) == 32 # print dict(mdc=mdc.encode('hex'), signature=signature.encode('hex'), # data=str(cmd_id + encoded_data).encode('hex')) return mdc + signature + cmd_id + encoded_data
def test_app_restart(): host, port = '127.0.0.1', 3020 a_config = dict(p2p=dict(listen_host=host, listen_port=port), node=dict(privkey_hex=crypto.sha3('a').encode('hex'))) a_app = BaseApp(a_config) peermanager.PeerManager.register_with_app(a_app) # Restart app 10-times: there should be no exception for i in xrange(0, 10): a_app.start() assert a_app.services.peermanager.server.started try_tcp_connect((host, port)) assert a_app.services.peermanager.num_peers() == 0 a_app.stop() assert a_app.services.peermanager.is_stopped # Start the app 10-times: there should be no exception like 'Bind error' for i in xrange(0, 10): a_app.start() assert a_app.services.peermanager.server.started try_tcp_connect((host, port)) a_app.stop() assert a_app.services.peermanager.is_stopped
def create_auth_message(self, remote_pubkey, 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) """ assert self.is_initiator if not self.ecc.is_valid_key(remote_pubkey): raise InvalidKeyError('invalid remote pubkey') self.remote_pubkey = remote_pubkey token = self.token_by_pubkey.get(remote_pubkey) if not token: # new ecdh_shared_secret = self.ecc.get_ecdh_key(remote_pubkey) token = ecdh_shared_secret flag = 0x0 else: flag = 0x1 self.initiator_nonce = nonce or sha3(ienc(random.randint(0, 2 ** 256 - 1))) assert len(self.initiator_nonce) == 32 token_xor_nonce = sxor(token, self.initiator_nonce) assert len(token_xor_nonce) == 32 ephemeral_pubkey = self.ephemeral_ecc.raw_pubkey assert len(ephemeral_pubkey) == 512 / 8 if not self.ecc.is_valid_key(ephemeral_pubkey): raise InvalidKeyError('invalid ephemeral pubkey') # 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.ecc.raw_pubkey + \ self.initiator_nonce + chr(flag) assert len(auth_message) == 65 + 32 + 64 + 32 + 1 == 194 return auth_message
def pack(self, cmd_id, payload): """ UDP packets are structured as follows: hash || signature || packet-type || packet-data packet-type: single byte < 2**7 // valid values are [1,4] packet-data: RLP encoded list. Packet properties are serialized in the order in which they're defined. See packet-data below. Offset | 0 | MDC | Ensures integrity of packet, 65 | signature | Ensures authenticity of sender, `SIGN(sender-privkey, MDC)` 97 | type | Single byte in range [1, 4] that determines the structure of Data 98 | data | RLP encoded, see section Packet Data The packets are signed and authenticated. The sender's Node ID is determined by recovering the public key from the signature. sender-pubkey = ECRECOVER(Signature) The integrity of the packet can then be verified by computing the expected MDC of the packet as: MDC = SHA3(sender-pubkey || type || data) As an optimization, implementations may look up the public key by the UDP sending address and compute MDC before recovering the sender ID. If the MDC values do not match, the packet can be dropped. """ assert cmd_id in self.cmd_id_map.values() assert isinstance(payload, list) cmd_id = self.encoders['cmd_id'](cmd_id) expiration = self.encoders['expiration'](int(time.time() + self.expiration)) encoded_data = rlp.encode(payload + [expiration]) # print rlp.decode(encoded_data) signed_data = crypto.sha3(cmd_id + encoded_data) signature = crypto.sign(signed_data, self.privkey) # assert crypto.verify(self.pubkey, signature, signed_data) # assert self.pubkey == crypto.ecdsa_recover(signed_data, signature) # assert crypto.verify(self.pubkey, signature, signed_data) assert len(signature) == 65 mdc = crypto.sha3(signature + cmd_id + encoded_data) assert len(mdc) == 32 # print dict(mdc=mdc.encode('hex'), signature=signature.encode('hex'), # data=str(cmd_id + encoded_data).encode('hex')) return mdc + signature + cmd_id + encoded_data
def recover_1kb(times=1000): alice = get_ecc(b'secret1') message = ''.join(chr(random.randrange(0, 256)) for i in range(1024)) message = crypto.sha3(message.encode('utf-8')) signature = alice.sign(message) for i in range(times): recovered_pubkey = crypto.ecdsa_recover(message, signature) assert recovered_pubkey == alice.raw_pubkey
def sign(self, msg): """ signature: sign(privkey, sha3(packet-type || packet-data)) signature: sign(privkey, sha3(pubkey || packet-type || packet-data)) // implementation w/MCD """ msg = crypto.sha3(msg) return crypto.sign(msg, self.privkey)
def test_recover(): alice = get_ecc(b'secret1') message = crypto.sha3(b'hello bob') signature = alice.sign(message) assert len(signature) == 65 assert crypto.verify(alice.raw_pubkey, signature, message) is True recovered_pubkey = crypto.ecdsa_recover(message, signature) assert len(recovered_pubkey) == 64 assert alice.raw_pubkey == recovered_pubkey
def test_encryption(): initiator, responder = test_session() for i in range(5): msg_frame = sha3(str(i) + 'f') * i + 'notpadded' msg_frame_padded = rzpad16(msg_frame) frame_size = len(msg_frame) msg_header = struct.pack('>I', frame_size)[1:] + sha3(str(i))[:16 - 3] msg_ct = initiator.encrypt(msg_header, msg_frame_padded) r = responder.decrypt(msg_ct) assert r['header'] == msg_header assert r['frame'] == msg_frame for i in range(5): msg_frame = sha3(str(i) + 'f') msg_header = struct.pack('>I', len(msg_frame))[1:] + sha3(str(i))[:16 - 3] msg_ct = responder.encrypt(msg_header, msg_frame) r = initiator.decrypt(msg_ct) assert r['header'] == msg_header assert r['frame'] == msg_frame
def get_app(port, seed): config = dict(discovery=dict(), node=dict(privkey_hex=crypto.sha3(seed).encode('hex'))) config_discovery = config['discovery'] config_discovery['listen_host'] = '127.0.0.1' config_discovery['listen_port'] = port config_discovery['bootstrap_nodes'] = [] # create app app = BaseApp(config) discovery.NodeDiscovery.register_with_app(app) return app
def get_app(port, seed): config = ConfigParser.ConfigParser() config.add_section('p2p') config.set('p2p', 'listen_host', '127.0.0.1') config.set('p2p', 'listen_port', str(port)) config.set('p2p', 'privkey_hex', crypto.sha3(seed).encode('hex')) # create app app = BaseApp(config) discovery.NodeDiscovery.register_with_app(app) return app
def __init__(self, host, port, seed): self.address = discovery.Address(host, port) config = dict(discovery=dict(), node=dict(privkey_hex=crypto.sha3(seed).encode('hex'))) config_discovery = config['discovery'] config_discovery['listen_host'] = host config_discovery['listen_port'] = port app = AppMock() app.config = config self.protocol = discovery.DiscoveryProtocol(app=app, transport=self)
def test_encryption(): initiator, responder = test_session() for i in range(5): msg_frame = sha3(str(i) + 'f') * i + 'notpadded' msg_frame_padded = rzpad16(msg_frame) frame_size = len(msg_frame) msg_header = struct.pack('>I', frame_size)[1:] + sha3(str(i))[:16 - 3] msg_ct = initiator.encrypt(msg_header, msg_frame_padded) r = responder.decrypt(msg_ct) assert r['header'] == msg_header assert r['frame'] == msg_frame for i in range(5): msg_frame = sha3(str(i) + 'f') msg_header = struct.pack('>I', len(msg_frame))[1:] + sha3( str(i))[:16 - 3] msg_ct = responder.encrypt(msg_header, msg_frame) r = initiator.decrypt(msg_ct) assert r['header'] == msg_header assert r['frame'] == msg_frame
def get_app(port, seed): config = dict(p2p=dict()) config_p2p = config['p2p'] config_p2p['listen_host'] = '127.0.0.1' config_p2p['listen_port'] = port config_p2p['privkey_hex'] = crypto.sha3(seed).encode('hex') # create app app = BaseApp(config) discovery.NodeDiscovery.register_with_app(app) return app
def decode_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) """ assert not self.is_initiator self.auth_init = ciphertext try: auth_message = self.ecc.ecies_decrypt(ciphertext) except RuntimeError as e: raise AuthenticationError(e) # S || H(ephemeral-pubk) || pubk || nonce || 0x[0|1] assert len( auth_message ) == 65 + 32 + 64 + 32 + 1 == 194 == self.auth_message_length signature = auth_message[:65] H_initiator_ephemeral_pubkey = auth_message[65:65 + 32] initiator_pubkey = auth_message[65 + 32:65 + 32 + 64] if not self.ecc.is_valid_key(initiator_pubkey): raise InvalidKeyError('invalid initiator pubkey') self.remote_pubkey = initiator_pubkey self.initiator_nonce = auth_message[65 + 32 + 64:65 + 32 + 64 + 32] known_flag = bool(ord(auth_message[65 + 32 + 64 + 32:])) # token or new ecdh_shared_secret if known_flag: self.remote_token_found = True # what todo if remote has token, but local forgot it. # reply with token not found. FIXME!!! token = self.token_by_pubkey.get(initiator_pubkey) assert token # FIXME continue session with ecdh_key and send flag in auth_ack else: token = self.ecc.get_ecdh_key(initiator_pubkey) # verify auth # S(ephemeral-privk, ecdh-shared-secret ^ nonce) signed = sxor(token, self.initiator_nonce) # recover initiator ephemeral pubkey self.remote_ephemeral_pubkey = ecdsa_recover(signed, signature) if not self.ecc.is_valid_key(self.remote_ephemeral_pubkey): raise InvalidKeyError('invalid remote ephemeral pubkey') if not ecdsa_verify(self.remote_ephemeral_pubkey, signature, signed): raise AuthenticationError('could not verify signature') # checks that recovery of signature == H(ephemeral-pubk) assert H_initiator_ephemeral_pubkey == sha3( self.remote_ephemeral_pubkey)
def __init__(self, host, port, seed): self.address = discovery.Address(host, port) config = ConfigParser.ConfigParser() config.add_section('p2p') config.set('p2p', 'listen_host', host) config.set('p2p', 'listen_port', str(port)) config.set('p2p', 'privkey_hex', crypto.sha3(seed).encode('hex')) app = AppMock() app.config = config self.protocol = discovery.DiscoveryProtocol(app=app, transport=self)
def parse_additional_bootstraps(bootstraps): retv = [] if not bootstraps: return retv for p in bootstraps.split(","): ip, port = p.split(":") seed = 0 privkey = sha3("{}:udp:{}:{}".format(seed, ip, port).encode("utf-8")) pubkey = privtopub_raw(privkey) enode = host_port_pubkey_to_uri(ip, port, pubkey) retv.append(enode) return retv
def decode_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) """ assert not self.is_initiator self.auth_init = ciphertext try: auth_message = self.ecc.ecies_decrypt(ciphertext) except RuntimeError as e: raise AuthenticationError(e) # S || H(ephemeral-pubk) || pubk || nonce || 0x[0|1] assert len(auth_message) == 65 + 32 + 64 + 32 + 1 == 194 == self.auth_message_length signature = auth_message[:65] H_initiator_ephemeral_pubkey = auth_message[65:65 + 32] initiator_pubkey = auth_message[65 + 32:65 + 32 + 64] if not self.ecc.is_valid_key(initiator_pubkey): raise InvalidKeyError('invalid initiator pubkey') self.remote_pubkey = initiator_pubkey self.initiator_nonce = auth_message[65 + 32 + 64:65 + 32 + 64 + 32] known_flag = bool(ord(auth_message[65 + 32 + 64 + 32:])) # token or new ecdh_shared_secret if known_flag: self.remote_token_found = True # what todo if remote has token, but local forgot it. # reply with token not found. FIXME!!! token = self.token_by_pubkey.get(initiator_pubkey) assert token # FIXME continue session with ecdh_key and send flag in auth_ack else: token = self.ecc.get_ecdh_key(initiator_pubkey) # verify auth # S(ephemeral-privk, ecdh-shared-secret ^ nonce) signed = sxor(token, self.initiator_nonce) # recover initiator ephemeral pubkey self.remote_ephemeral_pubkey = ecdsa_recover(signed, signature) if not self.ecc.is_valid_key(self.remote_ephemeral_pubkey): raise InvalidKeyError('invalid remote ephemeral pubkey') if not ecdsa_verify(self.remote_ephemeral_pubkey, signature, signed): raise AuthenticationError('could not verify signature') # checks that recovery of signature == H(ephemeral-pubk) assert H_initiator_ephemeral_pubkey == sha3(self.remote_ephemeral_pubkey)
def create_auth_ack_message(self, ephemeral_pubkey=None, nonce=None, 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! """ assert not self.is_initiator ephemeral_pubkey = ephemeral_pubkey or self.ephemeral_ecc.raw_pubkey self.responder_nonce = nonce or sha3(ienc(random.randint(0, 2 ** 256 - 1))) flag = chr(1 if token_found else 0) msg = ephemeral_pubkey + self.responder_nonce + flag assert len(msg) == 64 + 32 + 1 == 97 == self.auth_ack_message_length return msg
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 connect_go(): a_config = dict(p2p=dict(listen_host='127.0.0.1', listen_port=3000), node=dict(privkey_hex=crypto.sha3('a').encode('hex'))) a_app = BaseApp(a_config) peermanager.PeerManager.register_with_app(a_app) a_app.start() a_peermgr = a_app.services.peermanager # connect pubkey = "6ed2fecb28ff17dec8647f08aa4368b57790000e0e9b33a7b91f32c41b6ca9ba21600e9a8c44248ce63a71544388c6745fa291f88f8b81e109ba3da11f7b41b9".decode( 'hex') a_peermgr.connect(('127.0.0.1', 30303), remote_pubkey=pubkey) gevent.sleep(50) a_app.stop()
class BaseApp(object): client_name = 'pyethcrawler' client_version = '0.1/%s/%s' % (sys.platform, 'py%d.%d.%d' % sys.version_info[:3]) client_version_string = '%s/v%s' % (client_name, client_version) start_console = False default_config = {} default_config['client_version_string'] = client_version_string default_config['result_dir'] = '' default_config['prev_routing_table'] = '/results/routing-table_170109-084715.csv' default_config['prev_peers'] = '' # default_config['p2p'] = dict(bootstrap_nodes=[],listen_port=30304,listen_host='0.0.0.0') privkey_hex = 'b3b9736ba5e4b9d0f85231291316c7fd82bde4ae80bb7ca98175cf6d11c0c4eb' pubkey = crypto.privtopub(privkey_hex.decode('hex')) default_config['node'] = dict(privkey_hex=privkey_hex, id=crypto.sha3(pubkey)) def __init__(self, config=default_config): self.config = utils.update_config_with_defaults(config, self.default_config) self.services = IterableUserDict() def register_service(self, service): """ registeres protocol with app, which will be accessible as app.services.<protocol.name> (e.g. app.services.p2p or app.services.eth) """ assert isinstance(service, BaseService) assert service.name not in self.services log.info('registering service', service=service.name) self.services[service.name] = service setattr(self.services, service.name, service) def deregister_service(self, service): assert isinstance(service, BaseService) self.services.remove(service) delattr(self.services, service.name) def start(self): for service in self.services.values(): service.start() def stop(self): for service in self.services.values(): service.stop() def join(self): for service in self.services.values(): service.join()
class MockPeerManager(peermanager.PeerManager): privkey = crypto.sha3(b'a') pubkey = crypto.privtopub(privkey) wired_services = services config = { 'client_version_string': b'mock', 'p2p': { 'listen_port': 3006 }, 'node': { 'privkey_hex': encode_hex(privkey), 'id': encode_hex(pubkey), } } def __init__(self): pass
def create_auth_ack_message(self, ephemeral_pubkey=None, nonce=None, 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! """ assert not self.is_initiator ephemeral_pubkey = ephemeral_pubkey or self.ephemeral_ecc.raw_pubkey self.responder_nonce = nonce or sha3( ienc(random.randint(0, 2**256 - 1))) flag = chr(1 if token_found else 0) msg = ephemeral_pubkey + self.responder_nonce + flag assert len(msg) == 64 + 32 + 1 == 97 == self.auth_ack_message_length 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
def create_auth_ack_message(self, version=supported_rlpx_version, eip8=False, ephemeral_pubkey=None, nonce=None): """ authRecipient = E(remote-pubk, remote-ephemeral-pubk || nonce || 0x1) // token found authRecipient = E(remote-pubk, remote-ephemeral-pubk || nonce || 0x0) // token not found nonce, ephemeral_pubkey, version are local! """ assert not self.is_initiator ephemeral_pubkey = ephemeral_pubkey or self.ephemeral_ecc.raw_pubkey self.responder_nonce = nonce or sha3( ienc(random.randint(0, 2**256 - 1))) if eip8 or self.got_eip8_auth: msg = self.create_eip8_auth_ack_message(ephemeral_pubkey, self.responder_nonce, version) assert len(msg) > 97 else: msg = ephemeral_pubkey + self.responder_nonce + b'\x00' assert len(msg) == 97 return msg
def devp2p_app(env, network): seed = 0 gevent.get_hub().SYSTEM_ERROR = BaseException # get bootstrap node (node0) enode bootstrap_node_privkey = sha3("{}:udp:{}:{}".format( seed, env.cluster_config.P2P.BOOTSTRAP_HOST, env.cluster_config.P2P.BOOTSTRAP_PORT, ).encode("utf-8")) bootstrap_node_pubkey = privtopub_raw(bootstrap_node_privkey) enode = host_port_pubkey_to_uri( env.cluster_config.P2P.BOOTSTRAP_HOST, env.cluster_config.P2P.BOOTSTRAP_PORT, bootstrap_node_pubkey, ) services = [NodeDiscovery, peermanager.PeerManager, Devp2pService] # prepare config base_config = dict() for s in services: update_config_with_defaults(base_config, s.default_config) base_config["discovery"]["bootstrap_nodes"] = [ enode ] + parse_additional_bootstraps( env.cluster_config.P2P.ADDITIONAL_BOOTSTRAPS) base_config["seed"] = seed base_config["base_port"] = env.cluster_config.P2P.DISCOVERY_PORT base_config["min_peers"] = env.cluster_config.P2P.MIN_PEERS base_config["max_peers"] = env.cluster_config.P2P.MAX_PEERS min_peers = base_config["min_peers"] max_peers = base_config["max_peers"] assert min_peers <= max_peers config = copy.deepcopy(base_config) node_num = 0 config["node_num"] = env.cluster_config.P2P.DISCOVERY_PORT # create this node priv_key config["node"]["privkey_hex"] = encode_hex( sha3("{}:udp:{}:{}".format( seed, network.ip, env.cluster_config.P2P.DISCOVERY_PORT).encode("utf-8"))) # set ports based on node config["discovery"]["listen_port"] = env.cluster_config.P2P.DISCOVERY_PORT config["p2p"]["listen_port"] = env.cluster_config.P2P.DISCOVERY_PORT config["p2p"]["min_peers"] = min_peers config["p2p"]["max_peers"] = max_peers ip = network.ip config["client_version_string"] = "{}:{}".format(ip, network.port) app = Devp2pApp(config, network) Logger.info("create_app config={}".format(app.config)) # register services for service in services: assert issubclass(service, BaseService) if service.name not in app.config["deactivated_services"]: assert service.name not in app.services service.register_with_app(app) assert hasattr(app.services, service.name) serve_app(app)
def main(): parser = argparse.ArgumentParser() parser.add_argument("--bootstrap_host", default="0.0.0.0", type=str) parser.add_argument("--bootstrap_port", default=29000, type=int) # p2p port for this node parser.add_argument("--node_port", default=29000, type=int) parser.add_argument("--node_num", default=0, type=int) parser.add_argument("--min_peers", default=2, type=int) parser.add_argument("--max_peers", default=10, type=int) seed = 0 args = parser.parse_args() gevent.get_hub().SYSTEM_ERROR = BaseException # get bootstrap node (node0) enode bootstrap_node_privkey = sha3("{}:udp:{}".format(0, 0).encode("utf-8")) bootstrap_node_pubkey = privtopub_raw(bootstrap_node_privkey) enode = host_port_pubkey_to_uri(args.bootstrap_host, args.bootstrap_port, bootstrap_node_pubkey) services = [NodeDiscovery, peermanager.PeerManager, ExampleService] # prepare config base_config = dict() for s in services: update_config_with_defaults(base_config, s.default_config) base_config["discovery"]["bootstrap_nodes"] = [enode] base_config["seed"] = seed base_config["base_port"] = args.node_port base_config["min_peers"] = args.min_peers base_config["max_peers"] = args.max_peers log.info("run:", base_config=base_config) min_peers = base_config["min_peers"] max_peers = base_config["max_peers"] assert min_peers <= max_peers config = copy.deepcopy(base_config) config["node_num"] = args.node_num # create this node priv_key config["node"]["privkey_hex"] = encode_hex( sha3("{}:udp:{}".format(seed, args.node_num).encode("utf-8"))) # set ports based on node config["discovery"]["listen_port"] = args.node_port config["p2p"]["listen_port"] = args.node_port config["p2p"]["min_peers"] = min(10, min_peers) config["p2p"]["max_peers"] = max_peers config["client_version_string"] = "NODE{}".format( args.node_num).encode('utf-8') app = ExampleApp(config) log.info("create_app", config=app.config) # register services for service in services: assert issubclass(service, BaseService) if service.name not in app.config["deactivated_services"]: assert service.name not in app.services service.register_with_app(app) assert hasattr(app.services, service.name) app_helper.serve_until_stopped([app])