def verify_client_auth(self, client_auth): """ verify the client auth crypto envelope. this operation is performed by the server when it receives a client auth message. """ scalar = crypto_scalarmult(bytes(self.local_ephemeral_key.private_key), bytes(self._remote_ephemeral_pub_key)) scalar_remote_longterm = crypto_scalarmult( bytes(self.local_signing_key.private_key.to_curve25519_private_key( )), bytes(self._remote_ephemeral_pub_key)) hasher = hashlib.sha256() hasher.update(self.application_key + scalar + scalar_remote_longterm) box_secret = hasher.digest() nonce = b"\x00" * 24 message = crypto_box_open_afternm(client_auth, nonce, box_secret) self._client_vouch = message self.remote_longterm_pub_key = VerifyKey(message[:32]) signature = message[32:] hasher = hashlib.sha256() scalar = crypto_scalarmult(bytes(self.local_ephemeral_key.private_key), bytes(self._remote_ephemeral_pub_key)) hasher.update(scalar) hashed_value = hasher.digest() signed_message = self.application_key + bytes( self.local_signing_key.public_key) + hashed_value self.remote_longterm_pub_key.verify(signed_message, signature=signature)
def create_client_auth(self): """ this is the client authentication cryptographic envelope which the client sends to the server. it's mathematical representation is as follows: Box_{box_secret}(data_to_box) where: box_secret = hash([K | crypto_scalarmult(a_priv, b_pub) | crypto_scalarmult(a_priv, B_pub)]) data_to_box = A_pub | Sign_A_priv( K | B_pub | hash(crypto_scalarmult(a_priv, b_pub))) K = <<this is the application key>> """ hasher = hashlib.sha256() scalar = crypto_scalarmult(bytes(self.local_ephemeral_key.private_key), bytes(self._remote_ephemeral_pub_key)) hasher.update(scalar) hashed_value = hasher.digest() signed_message = self.local_signing_key.private_key.sign( self.application_key + bytes(self.remote_longterm_pub_key) + bytes(hashed_value)) message_to_box = bytes( self.local_signing_key.public_key) + signed_message.signature self._client_auth = message_to_box scalar_remote_longterm = crypto_scalarmult( bytes(self.local_ephemeral_key.private_key), bytes(self.remote_longterm_pub_key.to_curve25519_public_key())) hasher = hashlib.sha256() # XXX hasher.update(self.application_key + scalar + scalar_remote_longterm) box_secret = hasher.digest() nonce = b"\x00" * 24 return crypto_box_afternm(message_to_box, nonce, box_secret)
def create_server_accept(self): """ create a server accept crypto envelope. this envelope is sent from the server to the client. it's math representation is the following: Box_{box_secret}(data_to_box) where: data_to_box = Sign_B( K | H | hash(crypto_scalarmult(b_priv, a_pub)) box_secret = hash([ K | crypto_scalarmult(b_priv, a_pub) | crypto_scalarmult(B_priv, a_pub) | crypto_scalarmult(b_priv, A_pub) ]) H = A_pub | Sign_A_priv( K | B_pub | hash(crypto_scalarmult(b_priv, a_pub))) K = <<this is the application key>> """ message_to_sign = self.application_key + self._client_vouch + self._hashed_secret signed_message = self.local_signing_key.private_key.sign( message_to_sign) message_to_box = signed_message.signature local_longterm_sharedsecret = crypto_scalarmult( bytes(self.local_signing_key.private_key.to_curve25519_private_key( )), bytes(self._remote_ephemeral_pub_key)) remote_longterm_sharedsecret = crypto_scalarmult( bytes(self.local_ephemeral_key.private_key), bytes(self.remote_longterm_pub_key.to_curve25519_public_key())) to_hash = self.application_key + self._secret + local_longterm_sharedsecret + remote_longterm_sharedsecret hasher = hashlib.sha256() hasher.update(to_hash) box_secret = hasher.digest() self._shared_secret = box_secret nonce = b"\x00" * 24 return crypto_box_afternm(message_to_box, nonce, box_secret)
def init(prefs, the_ephemeral_key, has=False): encrypted_prefs = [] for pref in prefs: if not has: hashed_reciprocal_pref = sha256(reciprocal_map[pref]) want_pref = c.crypto_scalarmult(the_ephemeral_key, hashed_reciprocal_pref) encrypted_prefs.append(want_pref) else: hashed_pref = sha256(pref) have_pref = c.crypto_scalarmult(the_ephemeral_key, hashed_pref) encrypted_prefs.append(have_pref) return encrypted_prefs
def bob(network): # Bob sends his haves bobs_prefs = map(lambda x: x.strip(), file("bob.txt").readlines()) bobs_ephemeral_key = nacl.utils.random(32) bobs_encrypted_haves = init(bobs_prefs, bobs_ephemeral_key, True) # negotiate set size bobs_prefs_count = len(bobs_prefs) alices_prefs_count = network.recv() if bobs_prefs_count > alices_prefs_count: bobs_prefs_count = alices_prefs_count network.send(bobs_prefs_count) # compare prefs alices_encrypted_wants = [] for i in range(0, bobs_prefs_count): # receive Alice's point and encrypt it alices_encrypted_want = c.crypto_scalarmult(bobs_ephemeral_key, network.recv()) if alices_encrypted_want in alices_encrypted_wants: print "Bob feels somebody is cheating" return alices_encrypted_wants.append(alices_encrypted_want) # test if we are still interested if len(set.intersection(set(bobs_encrypted_haves), set(alices_encrypted_wants))) < i/10: print "Bob feels like this is going nowhere" # send encrypted point back network.send(alices_encrypted_wants[-1]) # send bobs point network.send(bobs_encrypted_haves[i]) # get it back encrypted bobs_encrypted_haves[i] = network.recv() for i in compare(bobs_encrypted_haves, alices_encrypted_wants, bobs_prefs): print "Bob learned:", i
def alice(network): # Alice sends her wants alices_prefs = map(lambda x: x.strip(), file("alice.txt").readlines()) alices_ephemeral_key = nacl.utils.random(32) alices_encrypted_wants = init(alices_prefs, alices_ephemeral_key, False) # negotiate set size alices_prefs_count = len(alices_prefs) network.send(alices_prefs_count) bobs_prefs_count = network.recv() if alices_prefs_count > bobs_prefs_count: alices_prefs_count = bobs_prefs_count # compare prefs bobs_encrypted_haves = [] for i in range(0, alices_prefs_count): # send Alice's point network.send(alices_encrypted_wants[i]) # get it back encrypted alices_encrypted_wants[i] = network.recv() # get Bob's point and encrypt it bobs_encrypted_have = c.crypto_scalarmult(alices_ephemeral_key, network.recv()) if bobs_encrypted_have in bobs_encrypted_haves: print "Alice feels somebody is cheating" return bobs_encrypted_haves.append(bobs_encrypted_have) # test if we are still interested if len(set.intersection(set(bobs_encrypted_haves), set(alices_encrypted_wants))) < i/10: print "Alice feels like this is going nowhere" network.send(bobs_encrypted_haves[-1]) for i in compare(alices_encrypted_wants, bobs_encrypted_haves, alices_prefs): print "Alice learned:", i
def verify_server_accept(self, data): """Verify that the server's accept message is sane""" curve_lkey = self.local_key.to_curve25519_private_key() # b_alice is (A * b) b_alice = crypto_scalarmult(bytes(curve_lkey), self.remote_ephemeral_key) self.b_alice = b_alice # this is hash(K | a * b | a * B | A * b) self.box_secret = hashlib.sha256(self.application_key + self.shared_secret + self.a_bob + b_alice).digest() nonce = b"\x00" * 24 try: # let's use the box secret to unbox our encrypted message signature = crypto_box_open_afternm(data, nonce, self.box_secret) except CryptoError: raise SHSError('Error decrypting server acceptance message') # we should have received sign(B)[K | H | hash(a * b)] # let's see if that signature can verify the reconstructed data on our side self.remote_pub_key.verify( self.application_key + self.hello + self.shared_hash, signature) return True
def alice(network): # Alice sends her wants alices_prefs = map(lambda x: x.strip(), file("alice.txt").readlines()) alices_ephemeral_key = nacl.utils.random(32) alices_encrypted_wants = init(alices_prefs, alices_ephemeral_key, False) # negotiate set size alices_prefs_count = len(alices_prefs) network.send(alices_prefs_count) bobs_prefs_count = network.recv() if alices_prefs_count > bobs_prefs_count: alices_prefs_count = bobs_prefs_count # compare prefs bobs_encrypted_haves = [] for i in range(0, alices_prefs_count): # send Alice's point network.send(alices_encrypted_wants[i]) # get it back encrypted alices_encrypted_wants[i] = network.recv() # get Bob's point and encrypt it bobs_encrypted_have = c.crypto_scalarmult(alices_ephemeral_key, network.recv()) if bobs_encrypted_have in bobs_encrypted_haves: print "Alice feels somebody is cheating" return bobs_encrypted_haves.append(bobs_encrypted_have) # test if we are still interested if len( set.intersection(set(bobs_encrypted_haves), set(alices_encrypted_wants))) < i / 10: print "Alice feels like this is going nowhere" network.send(bobs_encrypted_haves[-1]) for i in compare(alices_encrypted_wants, bobs_encrypted_haves, alices_prefs): print "Alice learned:", i
def encrypt_for_node(identity: Identity, public_key: str, payload: Union[str, bytes]) -> str: """ encrypt payload using a nacl.SecretBox with a shared key derived from the public key of the node and private key of the user Args: identity(Identity): the identity object that contains the key pair of the user public_key(str): public key of the node, hex-encoded payload(Union[str, bytes]): any data you want to encrypt Returns: str: hex-encoded encrypted data. you can use this safely into your reservation data """ user_private = identity.nacl.signing_key.to_curve25519_private_key( ).encode() node_verify_bin = binascii.unhexlify(public_key) node_public = VerifyKey(node_verify_bin).to_curve25519_public_key() node_public = node_public.encode() shared_secret = crypto_scalarmult(user_private, node_public) h = blake2b(shared_secret, digest_size=32) key = h.digest() if isinstance(payload, str): payload = payload.encode() box = SecretBox(key) encrypted = box.encrypt(payload) return binascii.hexlify(encrypted)
def bob(network): # Bob sends his haves bobs_prefs = map(lambda x: x.strip(), file("bob.txt").readlines()) bobs_ephemeral_key = nacl.utils.random(32) bobs_encrypted_haves = init(bobs_prefs, bobs_ephemeral_key, True) # negotiate set size bobs_prefs_count = len(bobs_prefs) alices_prefs_count = network.recv() if bobs_prefs_count > alices_prefs_count: bobs_prefs_count = alices_prefs_count network.send(bobs_prefs_count) # compare prefs alices_encrypted_wants = [] for i in range(0, bobs_prefs_count): # receive Alice's point and encrypt it alices_encrypted_want = c.crypto_scalarmult(bobs_ephemeral_key, network.recv()) if alices_encrypted_want in alices_encrypted_wants: print "Bob feels somebody is cheating" return alices_encrypted_wants.append(alices_encrypted_want) # test if we are still interested if len( set.intersection(set(bobs_encrypted_haves), set(alices_encrypted_wants))) < i / 10: print "Bob feels like this is going nowhere" # send encrypted point back network.send(alices_encrypted_wants[-1]) # send bobs point network.send(bobs_encrypted_haves[i]) # get it back encrypted bobs_encrypted_haves[i] = network.recv() for i in compare(bobs_encrypted_haves, alices_encrypted_wants, bobs_prefs): print "Bob learned:", i
def verify_client_auth(self, data): assert len(data) == 112 a_bob = crypto_scalarmult(bytes(self.local_key.to_curve25519_private_key()), self.remote_ephemeral_key) box_secret = hashlib.sha256(self.application_key + self.shared_secret + a_bob).digest() self.hello = crypto_box_open_afternm(data, b'\x00' * 24, box_secret) signature, public_key = self.hello[:64], self.hello[64:] signed = self.application_key + bytes(self.local_key.verify_key) + self.shared_hash pkey = VerifyKey(public_key) # will raise an exception if verification fails pkey.verify(signed, signature) self.remote_pub_key = pkey b_alice = crypto_scalarmult(bytes(self.local_ephemeral_key), bytes(self.remote_pub_key.to_curve25519_public_key())) self.box_secret = hashlib.sha256(self.application_key + self.shared_secret + a_bob + b_alice).digest()[:32] return True
def x25519_scalarmult(secret_scalar: ECScalar, point: ECPoint) -> ECPoint: """Scalar multiplication of ``point`` with (secret) ``scalar`` :param secret_scalar: Scalar (integer) `n`, in byte representation (:class:`ECScalar`) :param point: Point on curve `P`, in byte representation (:class:`ECPoint`) :returns: New point on curve: :math:`nP = P + P + P + P + P + \\text{...}` (`n` times) """ k = crypto_scalarmult(secret_scalar, point) assert any(k), "All-zeros-check failed (see RFC 7748, section 6.1)" return ECPoint(k)
def _EXP(n, p): ''' .. note:: See tor-spec Section 5.1.4 for why this is an adequate replacement for checking that none of the EXP() operations produced the point at infinity. :returns: **str** result ''' ret = bindings.crypto_scalarmult(bytes(n), bytes(PublicKey(p))) bad = util.constantStrAllZero(ret) return (ret, bad)
def verify_server_accept(self, server_accept_envelope): """ this is used by the client to verify the server accept envelope """ remote_longterm_sharedsecret = crypto_scalarmult( bytes(self.local_ephemeral_key.private_key), bytes(self.remote_longterm_pub_key.to_curve25519_public_key())) local_longterm_sharedsecret = crypto_scalarmult( bytes(self.local_signing_key.private_key.to_curve25519_private_key( )), bytes(self._remote_ephemeral_pub_key)) to_hash = self.application_key + self._secret + remote_longterm_sharedsecret + local_longterm_sharedsecret hasher = hashlib.sha256() hasher.update(to_hash) box_secret = hasher.digest() self._shared_secret = box_secret nonce = b"\x00" * 24 signature = crypto_box_open_afternm(server_accept_envelope, nonce, box_secret) message = self.application_key + self._client_auth + self._hashed_secret self.remote_longterm_pub_key.verify(message, signature)
def test_scalarmult(): x, xpub = secret_scalar() assert len(x) == 32 y, ypub = secret_scalar() # the Curve25519 base point (generator) base = unhexlify(b"09" + b"00" * 31) bx1 = c.crypto_scalarmult_base(x) bx2 = c.crypto_scalarmult(x, base) assert tohex(bx1) == tohex(bx2) assert tohex(bx1) == tohex(xpub) xby = c.crypto_scalarmult(x, c.crypto_scalarmult_base(y)) ybx = c.crypto_scalarmult(y, c.crypto_scalarmult_base(x)) assert tohex(xby) == tohex(ybx) z = unhexlify(b"10" * 32) bz1 = c.crypto_scalarmult_base(z) assert tohex(bz1) == ("781faab908430150daccdd6f9d6c5086" "e34f73a93ebbaa271765e5036edfc519") bz2 = c.crypto_scalarmult(z, base) assert tohex(bz1) == tohex(bz2)
def is_server_challenge_verified(self, challenge): """ this is used by the client side to verify a challenge from the server. if verified returns True, otherwise returns False. """ assert len(challenge) == 64 mac = challenge[:32] remote_ephemeral_pub_key = challenge[32:64] scalar_val = crypto_scalarmult( bytes(self.local_ephemeral_key.private_key), bytes(remote_ephemeral_pub_key)) hmac_key = self.application_key[:32] + scalar_val h = hmac.HMAC(hmac_key, hashes.SHA512(), backend=default_backend()) h.update(bytes(remote_ephemeral_pub_key)) new_hmac = h.finalize()[:32] ok = new_hmac == mac self._remote_ephemeral_pub_key = PublicKey(remote_ephemeral_pub_key) self._remote_app_mac = mac self._secret = crypto_scalarmult( bytes(self.local_ephemeral_key.private_key), remote_ephemeral_pub_key) self._hashed_secret = hashlib.sha256(self._secret).digest() return ok
def _scalarMult(self, base): '''Perform base**self._secret_key. Set self.is_bad if the result is all zeros. .. note:: See tor-spec Section 5.1.4 for why this is an adequate replacement for checking that none of the EXP() operations produced the point at infinity. :returns: **str** result ''' # args are: exponent, base ret = bindings.crypto_scalarmult(bytes(self._secret_key), bytes(PublicKey(base))) self.is_bad |= util.constantStrAllZero(ret) return ret
def verify_challenge(self, data): """Verify the correctness of challenge sent from the client.""" assert len(data) == 64 sent_hmac, remote_ephemeral_key = data[:32], data[32:] h = hmac.new(self.application_key, remote_ephemeral_key, digestmod='sha512') self.remote_app_hmac = h.digest()[:32] ok = self.remote_app_hmac == sent_hmac if ok: # this is (a * b) self.shared_secret = crypto_scalarmult(bytes(self.local_ephemeral_key), remote_ephemeral_key) self.remote_ephemeral_key = remote_ephemeral_key # this is hash(a * b) self.shared_hash = hashlib.sha256(self.shared_secret).digest() return ok
def verify_server_challenge(self, data): """Verify the correctness of challenge sent from the server.""" assert super(SHSClientCrypto, self).verify_challenge(data) curve_pkey = self.remote_pub_key.to_curve25519_public_key() # a_bob is (a * B) a_bob = crypto_scalarmult(bytes(self.local_ephemeral_key), bytes(curve_pkey)) self.a_bob = a_bob # this shall be hash(K | a * b | a * B) self.box_secret = hashlib.sha256(self.application_key + self.shared_secret + a_bob).digest() # and message_to_box will correspond to H = sign(A)[K | Bp | hash(a * b)] | Ap signed_message = self.local_key.sign(self.application_key + bytes(self.remote_pub_key) + self.shared_hash) message_to_box = signed_message.signature + bytes(self.local_key.verify_key) self.hello = message_to_box return True
def create_server_challenge(self): """ this is the challenge envelope that the server sends to the client. this envelope's crypto math representation: b_pub, hmac_{[ K | crypto_scalarmult(b_priv, a_pub) ]}(b_pub) where: K = <<this is the application key>> """ scalar = crypto_scalarmult(bytes(self.local_ephemeral_key.private_key), bytes(self._remote_ephemeral_pub_key)) hmac_key = self.application_key + scalar h = hmac.HMAC(hmac_key, hashes.SHA512(), backend=default_backend()) h.update(bytes(self.local_ephemeral_key.public_key)) _hmac = h.finalize() return _hmac[:32] + bytes(self.local_ephemeral_key.public_key)
def _put_key(pubkeyb64, eph_secret, file_key): """This method encrypts file key using X25519 and returns X25519 line argument list. Args: pubkeyb64: target base64url-encode X25519 public key. eph_secret: 32-bytes ephemeral secret. file_key: file key to encrypt using X25519. Returns: X25519 line argument list. It raises ValueError whether some issue is detected. """ # compute ephemeral public key try: eph_pub_key = crypto_scalarmult_base(eph_secret) except NaclRuntimeError: raise ValueError('Libsodium prevented all-zeros X25519 output.') # decode static public key static_pub_key = encoding._decode(pubkeyb64) # compute shared key try: shared_key = crypto_scalarmult(eph_secret, static_pub_key) except NaclRuntimeError: raise ValueError('Libsodium prevented all-zeros X25519 output.') # compute key encryption key kek = HKDF(algorithm=hashes.SHA256(), length=32, salt=eph_pub_key + static_pub_key, info=b'age-tool.com X25519', backend=default_backend()).derive(shared_key) # encrypt file key enc_file_key = symencrypt._encrypt_key(kek, file_key) # build and return X25519 line argument list argl = [encoding._encode(eph_pub_key), encoding._encode(enc_file_key)] return argl
def is_client_challenge_verified(self, challenge): """ this is used by the server side. if i can verify the challenge then return True otherwise return False """ assert len(challenge) == 64 mac = challenge[:32] remote_ephemeral_pub_key = challenge[32:64] h = hmac.HMAC(self.application_key[:32], hashes.SHA512(), backend=default_backend()) h.update(bytes(remote_ephemeral_pub_key)) new_hash = h.finalize()[:32] ok = new_hash == mac self._remote_ephemeral_pub_key = PublicKey(remote_ephemeral_pub_key) self._remote_app_mac = mac self._secret = crypto_scalarmult( bytes(self.local_ephemeral_key.private_key), remote_ephemeral_pub_key) self._hashed_secret = hashlib.sha256(self._secret).digest() return ok
def _get_key(argl, pubkeyb64, privkeyb64): """This method decrypts file key from X25519 line arguments. Args: argl: X25519 line argument list. pubkeyb64: base64url-encode X25519 public key. privkeyb64: base64url-encode X25519 private key. Returns: File key. It raises ValueError whether some issue is detected. """ # decode ephemeral public key eph_pub_key = encoding._decode(argl[0]) # decode encrypted file key enc_file_key = encoding._decode(argl[1]) # decode static public key static_pub_key = encoding._decode(pubkeyb64) # decode static private key priv_key = encoding._decode(privkeyb64) # compute shared key try: shared_key = crypto_scalarmult(priv_key, eph_pub_key) except NaclRuntimeError: raise ValueError('Libsodium prevented all-zeros X25519 output.') # compute key encryption key kek = HKDF(algorithm=hashes.SHA256(), length=32, salt=eph_pub_key + static_pub_key, info=b'age-tool.com X25519', backend=default_backend()).derive(shared_key) # decrypt and return file key return symencrypt._decrypt_key(kek, enc_file_key)
def expon_base(self, exp): base = crypto_scalarmult_base(exp[0]) for f in exp[1:]: base = crypto_scalarmult(f, base) return base
def tls_response(self): while True: head = yield from iofree.read(5) assert head[ 1:3] == b"\x03\x03", f"bad legacy_record_version {head[1:3]}" length = int.from_bytes(head[3:], "big") content = memoryview((yield from iofree.read(length))) if head[0] == ContentType.alert: level = AlertLevel.from_value(content[0]) description = AlertDescription.from_value(content[1]) raise Alert(level, description) elif head[0] == ContentType.handshake: self.peer_handshake = self.unpack_handshake(content) assert (self.peer_handshake.handshake_type == HandshakeType.server_hello), "expect server hello" peer_pk = self.peer_handshake.extensions[ ExtensionType.key_share].key_exchange shared_key = crypto_scalarmult(bytes(self.private_key), peer_pk) TLSCipher = self.peer_handshake.cipher_suite key_scheduler = TLSCipher.tls_hash.scheduler(shared_key) secret = key_scheduler.server_handshake_traffic_secret( self.get_context()) # server handshake cipher self.peer_cipher = TLSCipher(secret) client_handshake_traffic_secret = key_scheduler.client_handshake_traffic_secret( self.get_context()) elif head[0] == ContentType.application_data: plaintext = self.peer_cipher.decrypt(content, head).rstrip(b"\x00") content_type = ContentType.from_value(plaintext[-1]) if content_type == ContentType.handshake: self.unpack_handshake(plaintext[:-1]) if self.server_finished: # client handshake cipher self.cipher = TLSCipher( client_handshake_traffic_secret) context = b"".join(self.handshake_context) client_finished = self.cipher.verify_data(context) inner_plaintext = HandshakeType.finished.tls_inner_plaintext( client_finished) record = self.cipher.tls_ciphertext(inner_plaintext) change_cipher_spec = ContentType.change_cipher_spec.tls_plaintext( b"\x01") yield from iofree.write(change_cipher_spec + record) # server application cipher server_secret = key_scheduler.server_application_traffic_secret_0( self.get_context()) self.peer_cipher = TLSCipher(server_secret) self.server_finished = False # client application cipher client_secret = key_scheduler.client_application_traffic_secret_0( self.get_context()) self.cipher = TLSCipher(client_secret) elif content_type == ContentType.application_data: yield from iofree.write(plaintext[:-1]) elif content_type == ContentType.alert: level = AlertLevel.from_value(plaintext[0]) description = AlertDescription.from_value(plaintext[1]) raise Alert(level, description) elif content_type == ContentType.invalid: raise Exception("invalid content type") else: raise Exception(f"unexpected content type {content_type}") elif head[0] == ContentType.change_cipher_spec: assert content == b"\x01", "change_cipher should be 0x01" else: raise Exception(f"Unknown content type: {head[0]}")
def ecdh(self, other_public_key): return crypto_scalarmult(self.private_key, other_public_key)
def expon(self, base, exp): return crypto_scalarmult(bytes(exp), bytes(base))
def mix_dh(self, priv, pub): # XXX unfortunately this fails for public keys such as 0 and 1 because # libsodium ref10 implementation rejects some small-order points. sk, pk = _to_bytes(priv), _to_bytes(pub) secret = crypto_scalarmult(sk, pk) self.ck, self.k = hkdf(self.ck, secret, 2)
def get_session_keys(conn, pairing_data): """ Perform a pair verify operation as described in chapter 4.8 page 47 ff. :param conn: the http connection to the target accessory :param pairing_data: the paring data as returned by perform_pair_setup :returns: tuple of the session keys (controller_to_accessory_key and accessory_to_controller_key) """ headers = { 'Content-Type': 'application/pairing+tlv8' } # Step #1 ios --> accessory (send verify start request) (page 47) ios_key = PrivateKey.generate() request_tlv = TLV.encode_dict({ TLV.kTLVType_State: TLV.M1, TLV.kTLVType_PublicKey: bytes(ios_key.public_key), }) try: conn.request('POST', '/pair-verify', request_tlv, headers) resp = conn.getresponse() except (TimeoutError, HTTPException, OSError): return None response_tlv = TLV.decode_bytes(resp.read()) # Step #3 ios --> accessory (send SRP verify request) (page 49) if TLV.kTLVType_State not in response_tlv: return None if response_tlv[TLV.kTLVType_State] != TLV.M2: return None if TLV.kTLVType_PublicKey not in response_tlv: return None if TLV.kTLVType_EncryptedData not in response_tlv: return None # 1) generate shared secret accessorys_session_pub_key_bytes = \ bytes(response_tlv[TLV.kTLVType_PublicKey]) shared_secret = crypto_scalarmult( bytes(ios_key), bytes(PublicKey(accessorys_session_pub_key_bytes))) # 2) derive session key hkdf_inst = hkdf.Hkdf('Pair-Verify-Encrypt-Salt'.encode(), shared_secret, hash=hashlib.sha512) session_key = hkdf_inst.expand('Pair-Verify-Encrypt-Info'.encode(), 32) # 3) verify authtag on encrypted data and 4) decrypt encrypted = response_tlv[TLV.kTLVType_EncryptedData] decrypted = crypto_aead_chacha20poly1305_ietf_decrypt( bytes(encrypted), bytes(), bytes([0, 0, 0, 0]) + 'PV-Msg02'.encode(), session_key) if not decrypted: return None d1 = TLV.decode_bytes(decrypted) if TLV.kTLVType_Identifier not in d1: return None if TLV.kTLVType_Signature not in d1: return None # 5) look up pairing by accessory name accessory_name = d1[TLV.kTLVType_Identifier].decode() if pairing_data['AccessoryPairingID'] != accessory_name: return None accessory_ltpk = VerifyKey(bytes.fromhex(pairing_data['AccessoryLTPK'])) # 6) verify accessory's signature accessory_sig = d1[TLV.kTLVType_Signature] accessory_session_pub_key_bytes = response_tlv[TLV.kTLVType_PublicKey] accessory_info = accessory_session_pub_key_bytes + \ accessory_name.encode() + bytes(ios_key.public_key) try: accessory_ltpk.verify(bytes(accessory_info), bytes(accessory_sig)) except BadSignatureError: return None # 7) create iOSDeviceInfo ios_device_info = bytes(ios_key.public_key) + \ pairing_data['iOSPairingID'].encode() + \ accessorys_session_pub_key_bytes # 8) sign iOSDeviceInfo with long term secret key ios_device_ltsk_h = pairing_data['iOSDeviceLTSK'] ios_device_ltsk = SigningKey(bytes.fromhex(ios_device_ltsk_h)) ios_device_signature = ios_device_ltsk.sign(ios_device_info).signature # 9) construct sub tlv sub_tlv = TLV.encode_dict({ TLV.kTLVType_Identifier: pairing_data['iOSPairingID'].encode(), TLV.kTLVType_Signature: ios_device_signature }) # 10) encrypt and sign ciphertext = crypto_aead_chacha20poly1305_ietf_encrypt( bytes(sub_tlv), bytes(), bytes([0, 0, 0, 0]) + 'PV-Msg03'.encode(), session_key) tmp = ciphertext # 11) create tlv request_tlv = TLV.encode_dict({ TLV.kTLVType_State: TLV.M3, TLV.kTLVType_EncryptedData: tmp }) # 12) send to accessory try: conn.request('POST', '/pair-verify', request_tlv, headers) resp = conn.getresponse() except (TimeoutError, HTTPException, OSError): return None response_tlv = TLV.decode_bytes(resp.read()) # Post Step #4 verification (page 51) if TLV.kTLVType_State not in response_tlv: return None if response_tlv[TLV.kTLVType_State] != TLV.M4: return None if TLV.kTLVType_Error in response_tlv: return None # calculate session keys hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) controller_to_accessory_key = \ hkdf_inst.expand('Control-Write-Encryption-Key'.encode(), 32) hkdf_inst = hkdf.Hkdf('Control-Salt'.encode(), shared_secret, hash=hashlib.sha512) accessory_to_controller_key = \ hkdf_inst.expand('Control-Read-Encryption-Key'.encode(), 32) return controller_to_accessory_key, accessory_to_controller_key
def expon_base(self, exp): assert len(exp) > 0 base = crypto_scalarmult_base(_expand32(exp[0])) for f in exp[1:]: base = crypto_scalarmult(_expand32(f), base) return base
def generate_shared_secret_key(our_private_key: PrivateKey, peer_public_key: PublicKey) -> SecretKey: return SecretKey( crypto_scalarmult(our_private_key.encode(), peer_public_key.encode()))
def expon(self, base, exp): for f in exp: base = crypto_scalarmult(f, base) return base