def _set_ciphers(self): """Generate out/inbound encryption keys and initialise respective ciphers.""" outgoing_key = hap_hkdf(self.shared_key, self.CIPHER_SALT, self.OUT_CIPHER_INFO) self.out_cipher = CHACHA20_POLY1305(outgoing_key, "python") incoming_key = hap_hkdf(self.shared_key, self.CIPHER_SALT, self.IN_CIPHER_INFO) self.in_cipher = CHACHA20_POLY1305(incoming_key, "python")
def _set_ciphers(self): outgoing_key = hap_hkdf(self.shared_key, self.CIPHER_SALT, self.OUT_CIPHER_INFO) self.out_cipher = CHACHA20_POLY1305(outgoing_key, "python") incoming_key = hap_hkdf(self.shared_key, self.CIPHER_SALT, self.IN_CIPHER_INFO) self.in_cipher = CHACHA20_POLY1305(incoming_key, "python")
def _pairing_five(self, client_username, client_ltpk, encryption_key): """At that point we know the client has the accessory password and has a valid key pair. Add it as a pair and send a sever proof. Parameters are as for _pairing_four. """ logging.debug("Pairing [5/5]") session_key = self._accessory_driver.srp_verifier.get_session_key() output_key = hap_hkdf(long_to_bytes(session_key), PAIRING_5_SALT, PAIRING_5_INFO) server_public = self._state.public_key.to_bytes() mac = self._state.mac.encode() material = output_key + mac + server_public private_key = self._state.private_key server_proof = private_key.sign(material) message = tlv.encode(HAP_TLV_TAGS.USERNAME, mac, HAP_TLV_TAGS.PUBLIC_KEY, server_public, HAP_TLV_TAGS.PROOF, server_proof) cipher = CHACHA20_POLY1305(encryption_key, "python") aead_message = bytes( cipher.seal(PAIRING_5_NONCE, bytearray(message), b"")) client_uuid = uuid.UUID(str(client_username, "utf-8")) should_confirm = self._accessory_driver.pair(client_uuid, client_ltpk) if not should_confirm: self.send_response(500) return return tlv.encode(HAP_TLV_TAGS.SEQUENCE_NUM, b'\x06', HAP_TLV_TAGS.ENCRYPTED_DATA, aead_message)
def _pairing_three(self, tlv_objects): """Expand the SRP session key to obtain a new key. Use it to verify and decrypt the recieved data. Continue to step four. @param tlv_objects: The TLV data received from the client. @type tlv_object: dict """ logger.debug("Pairing [3/5]") encrypted_data = tlv_objects[HAP_TLV_TAGS.ENCRYPTED_DATA] session_key = self.accessory_handler.srp_verifier.get_session_key() hkdf_enc_key = hap_hkdf(long_to_bytes(session_key), self.PAIRING_3_SALT, self.PAIRING_3_INFO) cipher = CHACHA20_POLY1305(hkdf_enc_key, "python") decrypted_data = cipher.open(self.PAIRING_3_NONCE, bytearray(encrypted_data), b"") assert decrypted_data is not None dec_tlv_objects = tlv.decode(bytes(decrypted_data)) client_username = dec_tlv_objects[HAP_TLV_TAGS.USERNAME] client_ltpk = dec_tlv_objects[HAP_TLV_TAGS.PUBLIC_KEY] client_proof = dec_tlv_objects[HAP_TLV_TAGS.PROOF] self._pairing_four(client_username, client_ltpk, client_proof, hkdf_enc_key)
def test_seal(self): aead = CHACHA20_POLY1305( bytearray( b'\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f' b'\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f' ), "python") plaintext = bytearray( b'Ladies and Gentlemen of the class of \'99: If I could offer you o' b'nly one tip for the future, sunscreen would be it.') aad = bytearray(b'\x50\x51\x52\x53\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7') nonce = bytearray(b'\x07\x00\x00\x00\x40\x41\x42\x43\x44\x45\x46\x47') ciphertext = aead.seal(nonce, plaintext, aad) self.assertEqual( ciphertext, bytearray( b'\xd3\x1a\x8d\x34\x64\x8e\x60\xdb\x7b\x86\xaf\xbc\x53\xef\x7e\xc2' b'\xa4\xad\xed\x51\x29\x6e\x08\xfe\xa9\xe2\xb5\xa7\x36\xee\x62\xd6' b'\x3d\xbe\xa4\x5e\x8c\xa9\x67\x12\x82\xfa\xfb\x69\xda\x92\x72\x8b' b'\x1a\x71\xde\x0a\x9e\x06\x0b\x29\x05\xd6\xa5\xb6\x7e\xcd\x3b\x36' b'\x92\xdd\xbd\x7f\x2d\x77\x8b\x8c\x98\x03\xae\xe3\x28\x09\x1b\x58' b'\xfa\xb3\x24\xe4\xfa\xd6\x75\x94\x55\x85\x80\x8b\x48\x31\xd7\xbc' b'\x3f\xf4\xde\xf0\x8e\x4b\x7a\x9d\xe5\x76\xd2\x65\x86\xce\xc6\x4b' b'\x61\x16' b'\x1a\xe1\x0b\x59\x4f\x09\xe2\x6a\x7e\x90\x2e\xcb\xd0\x60\x06\x91' ))
def _pair_verify_two(self, tlv_objects): """Verify the client proof and upgrade to encrypted transport. @param tlv_objects: The TLV data received from the client. @type tlv_object: dict """ logger.debug("Pair verify [2/2]") encrypted_data = tlv_objects[HAP_TLV_TAGS.ENCRYPTED_DATA] cipher = CHACHA20_POLY1305(self.enc_context["pre_session_key"], "python") decrypted_data = cipher.open(self.PVERIFY_2_NONCE, bytearray(encrypted_data), b"") assert decrypted_data is not None # TODO: dec_tlv_objects = tlv.decode(bytes(decrypted_data)) client_username = dec_tlv_objects[HAP_TLV_TAGS.USERNAME] material = self.enc_context["client_public"] \ + client_username \ + self.enc_context["public_key"].serialize() client_uuid = uuid.UUID(str(client_username, "ascii")) perm_client_public = self.state.paired_clients.get(client_uuid) if perm_client_public is None: logger.debug( "Client %s attempted pair verify without being paired first.", client_uuid) self.send_response(200) self.send_header("Content-Type", self.PAIRING_RESPONSE_TYPE) data = tlv.encode(HAP_TLV_TAGS.ERROR_CODE, HAP_OPERATION_CODE.INVALID_REQUEST) self.end_response(data) return verifying_key = ed25519.VerifyingKey(perm_client_public) try: verifying_key.verify(dec_tlv_objects[HAP_TLV_TAGS.PROOF], material) except ed25519.BadSignatureError: logger.error("Bad signature, abort.") self.send_response(200) self.send_header("Content-Type", self.PAIRING_RESPONSE_TYPE) data = tlv.encode(HAP_TLV_TAGS.ERROR_CODE, HAP_OPERATION_CODE.INVALID_REQUEST) self.end_response(data) return logger.debug( "Pair verify with client '%s' completed. Switching to " "encrypted transport.", self.client_address) data = tlv.encode(HAP_TLV_TAGS.SEQUENCE_NUM, b'\x04') self.send_response(200) self.send_header("Content-Type", self.PAIRING_RESPONSE_TYPE) self.end_response(data) self._upgrade_to_encrypted() del self.enc_context
def test_open(self): #RFC 7539 Appendix A.5 key = bytearray( b'\x1c\x92\x40\xa5\xeb\x55\xd3\x8a\xf3\x33\x88\x86\x04\xf6\xb5\xf0' b'\x47\x39\x17\xc1\x40\x2b\x80\x09\x9d\xca\x5c\xbc\x20\x70\x75\xc0' ) ciphertext = bytearray( b'\x64\xa0\x86\x15\x75\x86\x1a\xf4\x60\xf0\x62\xc7\x9b\xe6\x43\xbd' b'\x5e\x80\x5c\xfd\x34\x5c\xf3\x89\xf1\x08\x67\x0a\xc7\x6c\x8c\xb2' b'\x4c\x6c\xfc\x18\x75\x5d\x43\xee\xa0\x9e\xe9\x4e\x38\x2d\x26\xb0' b'\xbd\xb7\xb7\x3c\x32\x1b\x01\x00\xd4\xf0\x3b\x7f\x35\x58\x94\xcf' b'\x33\x2f\x83\x0e\x71\x0b\x97\xce\x98\xc8\xa8\x4a\xbd\x0b\x94\x81' b'\x14\xad\x17\x6e\x00\x8d\x33\xbd\x60\xf9\x82\xb1\xff\x37\xc8\x55' b'\x97\x97\xa0\x6e\xf4\xf0\xef\x61\xc1\x86\x32\x4e\x2b\x35\x06\x38' b'\x36\x06\x90\x7b\x6a\x7c\x02\xb0\xf9\xf6\x15\x7b\x53\xc8\x67\xe4' b'\xb9\x16\x6c\x76\x7b\x80\x4d\x46\xa5\x9b\x52\x16\xcd\xe7\xa4\xe9' b'\x90\x40\xc5\xa4\x04\x33\x22\x5e\xe2\x82\xa1\xb0\xa0\x6c\x52\x3e' b'\xaf\x45\x34\xd7\xf8\x3f\xa1\x15\x5b\x00\x47\x71\x8c\xbc\x54\x6a' b'\x0d\x07\x2b\x04\xb3\x56\x4e\xea\x1b\x42\x22\x73\xf5\x48\x27\x1a' b'\x0b\xb2\x31\x60\x53\xfa\x76\x99\x19\x55\xeb\xd6\x31\x59\x43\x4e' b'\xce\xbb\x4e\x46\x6d\xae\x5a\x10\x73\xa6\x72\x76\x27\x09\x7a\x10' b'\x49\xe6\x17\xd9\x1d\x36\x10\x94\xfa\x68\xf0\xff\x77\x98\x71\x30' b'\x30\x5b\xea\xba\x2e\xda\x04\xdf\x99\x7b\x71\x4d\x6c\x6f\x2c\x29' b'\xa6\xad\x5c\xb4\x02\x2b\x02\x70\x9b') nonce = bytearray(b'\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08') aad = bytearray(b'\xf3\x33\x88\x86\x00\x00\x00\x00\x00\x00\x4e\x91') tag = bytearray( b'\xee\xad\x9d\x67\x89\x0c\xbb\x22\x39\x23\x36\xfe\xa1\x85\x1f\x38' ) aead = CHACHA20_POLY1305(key, "python") plaintext = aead.open(nonce, ciphertext + tag, aad) self.assertEqual( plaintext, bytearray( b'Internet-Drafts are draft documents valid for a maximum of six m' b'onths and may be updated, replaced, or obsoleted by other docume' b'nts at any time. It is inappropriate to use Internet-Drafts as r' b'eference material or to cite them other than as /' b'\xe2\x80\x9cwork in progress\x2e\x2f\xe2\x80\x9d'))
def _pair_verify_one(self, tlv_objects): """Generate new session key pair and send a proof to the client. @param tlv_objects: The TLV data received from the client. @type tlv_object: dict """ logging.debug("Pair verify [1/2].") client_public = tlv_objects[HAP_TLV_TAGS.PUBLIC_KEY] private_key = curve25519.Private() public_key = private_key.get_public() shared_key = private_key.get_shared_key( curve25519.Public(client_public), # Key is hashed before being returned, we don't want it; This fixes that. lambda x: x) mac = self._state.mac.encode() material = public_key.serialize() + mac + client_public server_proof = self._state.private_key.sign(material) output_key = hap_hkdf(shared_key, PVERIFY_1_SALT, PVERIFY_1_INFO) enc_context = { "client_public": client_public, "private_key": private_key, "public_key": public_key, "shared_key": shared_key, "pre_session_key": output_key } message = tlv.encode(HAP_TLV_TAGS.USERNAME, mac, HAP_TLV_TAGS.PROOF, server_proof) cipher = CHACHA20_POLY1305(output_key, "python") aead_message = bytes( cipher.seal(PVERIFY_1_NONCE, bytearray(message), b"")) data = tlv.encode(HAP_TLV_TAGS.SEQUENCE_NUM, b'\x02', HAP_TLV_TAGS.ENCRYPTED_DATA, aead_message, HAP_TLV_TAGS.PUBLIC_KEY, public_key.serialize()) return data, enc_context
def _pair_verify_one(self, tlv_objects): """Generate new session key pair and send a proof to the client. @param tlv_objects: The TLV data received from the client. @type tlv_object: dict """ logger.debug("Pair verify [1/2].") client_public = tlv_objects[HAP_TLV_TAGS.PUBLIC_KEY] private_key = curve25519.Private() public_key = private_key.get_public() shared_key = private_key.get_shared_key( curve25519.Public(client_public), # Why not randomly hash the key before returning it # so that it is incompatible with another # implementation and force clients to use hacks? lambda x: x) mac = self.accessory.mac.encode() material = public_key.serialize() + mac + client_public server_proof = self.accessory.private_key.sign(material) output_key = hap_hkdf(shared_key, self.PVERIFY_1_SALT, self.PVERIFY_1_INFO) self._set_encryption_ctx(client_public, private_key, public_key, shared_key, output_key) message = tlv.encode(HAP_TLV_TAGS.USERNAME, mac, HAP_TLV_TAGS.PROOF, server_proof) cipher = CHACHA20_POLY1305(output_key, "python") aead_message = bytes( cipher.seal(self.PVERIFY_1_NONCE, bytearray(message), b"")) data = tlv.encode(HAP_TLV_TAGS.SEQUENCE_NUM, b'\x02', HAP_TLV_TAGS.ENCRYPTED_DATA, aead_message, HAP_TLV_TAGS.PUBLIC_KEY, public_key.serialize()) self.send_response(200) self.send_header("Content-Type", self.PAIRING_RESPONSE_TYPE) self.end_response(data)
def _pair_verify_two(self, tlv_objects, enc_context): """Verify the client proof and upgrade to encrypted transport. @param tlv_objects: The TLV data received from the client. @type tlv_object: dict """ logging.debug('Pair verify [2/2]') encrypted_data = tlv_objects[HAP_TLV_TAGS.ENCRYPTED_DATA] cipher = CHACHA20_POLY1305(enc_context['pre_session_key'], 'python') decrypted_data = cipher.open(PVERIFY_2_NONCE, bytearray(encrypted_data), b'') assert decrypted_data is not None # TODO: dec_tlv_objects = tlv.decode(bytes(decrypted_data)) client_username = dec_tlv_objects[HAP_TLV_TAGS.USERNAME] material = enc_context['client_public'] \ + client_username \ + enc_context['public_key'].serialize() client_uuid = uuid.UUID(str(client_username, 'ascii')) perm_client_public = self._state.paired_clients.get(client_uuid) if perm_client_public is None: logging.debug( 'Client %s attempted pair verify without being paired first.', client_uuid) return tlv.encode(HAP_TLV_TAGS.ERROR_CODE, HAP_OPERATION_CODE.INVALID_REQUEST) verifying_key = ed25519.VerifyingKey(perm_client_public) try: verifying_key.verify(dec_tlv_objects[HAP_TLV_TAGS.PROOF], material) except ed25519.BadSignatureError: logging.error('Bad signature, abort.') return tlv.encode(HAP_TLV_TAGS.ERROR_CODE, HAP_OPERATION_CODE.INVALID_REQUEST) return tlv.encode(HAP_TLV_TAGS.SEQUENCE_NUM, b'\x04'), None
def __init__(self, out_key, in_key): """Initialize a new Chacha20Cipher.""" self._enc_out = CHACHA20_POLY1305(out_key, 'python') self._enc_in = CHACHA20_POLY1305(in_key, 'python') self._out_counter = 0 self._in_counter = 0
def unpack_addr_payload(ciphertext, hdpass): plaintext = CHACHA20_POLY1305(hdpass, 'python').open(b'serokellfore', ciphertext, b'') if plaintext: return cbor.loads(bytes(plaintext))
def pack_addr_payload(path, hdpass): 'packHDAddressAttr' plaintext = cbor.dumps(cbor.VarList(path)) return bytes( CHACHA20_POLY1305(hdpass, 'python').seal(b'serokellfore', plaintext, b''))
def test_open_with_too_short_ciphertext(self): aead = CHACHA20_POLY1305(bytearray(256 // 8), "python") plaintext = aead.open(bytearray(96 // 8), bytearray(15), bytearray(0)) self.assertIsNone(plaintext)
def test___init___with_unsupported_implementation(self): with self.assertRaises(ValueError): CHACHA20_POLY1305(bytearray(256 // 8), "pycrypto")
def test___init___with_invalid_key_size(self): with self.assertRaises(ValueError): CHACHA20_POLY1305(bytearray(128 // 8), "python")
def test___init__(self): aead = CHACHA20_POLY1305(bytearray(256 // 8), "python") self.assertIsNotNone(aead)
def test_open_with_invalid_tag(self): aead = CHACHA20_POLY1305(bytearray(256 // 8), "python") plaintext = aead.open(bytearray(96 // 8), bytearray(32), bytearray(0)) self.assertIsNone(plaintext)
def test_seal_with_invalid_nonce(self): aead = CHACHA20_POLY1305(bytearray(256 // 8), "python") with self.assertRaises(ValueError): aead.seal(bytearray(16), bytearray(10), bytearray(10))
def test_open_with_invalid_size_nonce(self): aead = CHACHA20_POLY1305(bytearray(256 // 8), "python") with self.assertRaises(ValueError): aead.open(bytearray(128 // 8), bytearray(64), bytearray(0))