Esempio n. 1
0
class SRPAuthHandler:
    """Handle SRP data and crypto routines for auth and verification."""

    def __init__(self):
        """Initialize a new SRPAuthHandler."""
        self.seed = None
        self.session = None
        self.client_session_key = None  # TODO: can this be removed?
        self._auth_private = None
        self._auth_public = None
        self._verify_private = None
        self._verify_public = None

    def initialize(self, seed=None):
        """Initialize handler operation.

        This method will generate new encryption keys and must be called prior
        to doing authentication or verification.
        """
        self.seed = seed or os.urandom(32)  # Generate new seed if not provided
        signing_key = SigningKey(self.seed)
        verifying_key = signing_key.get_verifying_key()
        self._auth_private = signing_key.to_seed()
        self._auth_public = verifying_key.to_bytes()
        log_binary(_LOGGER,
                   'Authentication keys',
                   Private=self._auth_private,
                   Public=self._auth_public)

    def verify1(self):
        """First device verification step."""
        self._check_initialized()
        self._verify_private = curve25519.Private(secret=self.seed)
        self._verify_public = self._verify_private.get_public()
        log_binary(_LOGGER,
                   'Verification keys',
                   Private=self._verify_private.serialize(),
                   Public=self._verify_public.serialize())
        verify_public = self._verify_public.serialize()
        return b'\x01\x00\x00\x00' + verify_public + self._auth_public

    def verify2(self, atv_public_key, data):
        """Last device verification step."""
        self._check_initialized()
        log_binary(_LOGGER, 'Verify', PublicSecret=atv_public_key, Data=data)

        # Generate a shared secret key
        public = curve25519.Public(atv_public_key)
        shared = self._verify_private.get_shared_key(
            public, hashfunc=lambda x: x)  # No additional hashing used
        log_binary(_LOGGER, 'Shared secret', Secret=shared)

        # Derive new AES key and IV from shared key
        aes_key = hash_sha512('Pair-Verify-AES-Key', shared)[0:16]
        aes_iv = hash_sha512('Pair-Verify-AES-IV', shared)[0:16]
        log_binary(_LOGGER, 'Pair-Verify-AES', Key=aes_key, IV=aes_iv)

        # Sign public keys and encrypt with AES
        signer = SigningKey(self._auth_private)
        signed = signer.sign(self._verify_public.serialize() + atv_public_key)
        signature, _ = aes_encrypt(modes.CTR, aes_key, aes_iv, data, signed)
        log_binary(_LOGGER, 'Signature', Signature=signature)

        # Signature is prepended with 0x00000000 (alignment?)
        return b'\x00\x00\x00\x00' + signature

    def step1(self, username, password):
        """First authentication step."""
        self._check_initialized()
        context = AtvSRPContext(
            str(username), str(password),
            prime=constants.PRIME_2048,
            generator=constants.PRIME_2048_GEN)
        self.session = SRPClientSession(
            context, binascii.hexlify(self._auth_private).decode())

    def step2(self, pub_key, salt):
        """Second authentication step."""
        self._check_initialized()
        pk_str = binascii.hexlify(pub_key).decode()
        salt = binascii.hexlify(salt).decode()
        self.client_session_key, _, _ = self.session.process(pk_str, salt)
        _LOGGER.debug('Client session key: %s', self.client_session_key)

        # Generate client public and session key proof.
        client_public = self.session.public
        client_session_key_proof = self.session.key_proof
        _LOGGER.debug('Client public: %s, proof: %s',
                      client_public, client_session_key_proof)

        if not self.session.verify_proof(self.session.key_proof_hash):
            raise AuthenticationError('proofs do not match (mitm?)')
        return client_public, client_session_key_proof

    def step3(self):
        """Last authentication step."""
        self._check_initialized()
        # TODO: verify: self.client_session_key same as self.session.key_b64()?
        session_key = binascii.unhexlify(self.client_session_key)

        aes_key = hash_sha512('Pair-Setup-AES-Key', session_key)[0:16]
        tmp = bytearray(hash_sha512('Pair-Setup-AES-IV', session_key)[0:16])
        tmp[-1] = tmp[-1] + 1  # Last byte must be increased by 1
        aes_iv = bytes(tmp)
        log_binary(_LOGGER, 'Pair-Setup-AES', Key=aes_key, IV=aes_iv)

        epk, tag = aes_encrypt(modes.GCM, aes_key, aes_iv, self._auth_public)
        log_binary(_LOGGER, 'Pair-Setup EPK+Tag', EPK=epk, Tag=tag)

        return epk, tag

    def _check_initialized(self):
        if not self.seed:
            raise NoCredentialsError()
Esempio n. 2
0
File: remote.py Progetto: tgu/SRP
    print("K:", ensure_hash_size(server_session.key))

if args.command == "client":
    client_session = SRPClientSession(context, private=args.private)
    print("a:", client_session.private)

    # Client => Server: username, A
    print("A:", client_session.public)

    # Server => Client: s, B
    sys.stdout.write("s: ")
    sys.stdout.flush()
    s = input()
    sys.stdout.write("B: ")
    sys.stdout.flush()
    B = input()
    client_session.process(B, s)

    # Client => Server: M
    print("M:", ensure_hash_size(client_session.key_proof))

    # Server => Client: HAMK
    sys.stdout.write("HAMK: ")
    sys.stdout.flush()
    HAMK = input()
    assert client_session.verify_proof(HAMK)
    print("OK")

    # Always keep the key secret! It is printed to validate the implementation.
    print("K:", ensure_hash_size(client_session.key))
Esempio n. 3
0
client_public_A = client_session.public

# the client has received B
# the server has received A

#####
# HERE WE ARE IN THE SERVER
server_session.process(client_public_A, salt)  # generating the common secret

server_key_proof = server_session.key_proof
server_key_proof_hash = server_session.key_proof_hash
print(server_key_proof)
print(server_key_proof_hash)

#####
# HERE WE ARE IN THE CLIENT
client_session.process(server_public_B, salt)  #generating the common secret

client_key_proof = client_session.key_proof
client_key_proof_hash = client_session.key_proof_hash
print(client_key_proof)
print(client_key_proof_hash)

#####
# HERE WE ARE IN THE SERVER
assert server_session.verify_proof(client_key_proof)

#####
# HERE WE ARE IN THE CLIENT
assert client_session.verify_proof(server_key_proof_hash)
Esempio n. 4
0
def pairing(socket, config):
    msg = ProtocolMessage_pb2.ProtocolMessage()
    msg.type = ProtocolMessage_pb2.ProtocolMessage.CRYPTO_PAIRING_MESSAGE
    msg.Extensions[CryptoPairingMessage_pb2.cryptoPairingMessage].status = 0
    msg.Extensions[
        CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData = tlv_build(
            {
                kTLVType_Method: b'\x00',
                kTLVType_State: b'\x01'
            })
    msg.Extensions[CryptoPairingMessage_pb2.cryptoPairingMessage].state = 2
    send(msg, socket)
    msg = receive(socket)
    parsed = tlv_parse(msg.Extensions[
        CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData)

    appletv_public = parsed[kTLVType_PublicKey]
    salt = parsed[kTLVType_Salt]

    code = input("Enter code displayed by Apple TV: ")
    session = SRPClientSession(
        SRPContext("Pair-Setup",
                   code,
                   PRIME_3072,
                   PRIME_3072_GEN,
                   hashlib.sha512,
                   bits_random=256,
                   bits_salt=128))
    session.process(binascii.hexlify(appletv_public), binascii.hexlify(salt))
    our_public = binascii.unhexlify(session.public)
    key_proof = binascii.unhexlify(session.key_proof)

    msg = ProtocolMessage_pb2.ProtocolMessage()
    msg.type = ProtocolMessage_pb2.ProtocolMessage.CRYPTO_PAIRING_MESSAGE
    msg.Extensions[CryptoPairingMessage_pb2.cryptoPairingMessage].status = 0
    tlv = tlv_build({
        kTLVType_State: b'\x03',
        kTLVType_PublicKey: our_public,
        kTLVType_Proof: key_proof
    })
    msg.Extensions[
        CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData = tlv

    send(msg, socket)
    msg = receive(socket)
    parsed = tlv_parse(msg.Extensions[
        CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData)

    proof = parsed[kTLVType_Proof]
    if not session.verify_proof(binascii.hexlify(proof)):
        print("proof does not match!")
        exit(1)

    ltsk, ltpk = ed25519.create_keypair()

    hkdf = HKDF(hashes.SHA512(), 32, b"Pair-Setup-Controller-Sign-Salt",
                b"Pair-Setup-Controller-Sign-Info", default_backend())
    x = hkdf.derive(binascii.unhexlify(session.key))

    device_id = bytes(config['device_id'], 'utf-8')
    info = x + device_id + ltpk.to_bytes()
    subtlv = tlv_build({
        kTLVType_Identifier: device_id,
        kTLVType_PublicKey: ltpk.to_bytes(),
        kTLVType_Signature: ltsk.sign(info)
    })

    hkdf = HKDF(hashes.SHA512(), 32, b"Pair-Setup-Encrypt-Salt",
                b"Pair-Setup-Encrypt-Info", default_backend())
    x = hkdf.derive(binascii.unhexlify(session.key))

    cha_cha_poly = ChaCha20Poly1305(x)
    encrypted = cha_cha_poly.encrypt(b"\0\0\0\0PS-Msg05", subtlv, None)

    msg = ProtocolMessage_pb2.ProtocolMessage()
    msg.type = ProtocolMessage_pb2.ProtocolMessage.CRYPTO_PAIRING_MESSAGE
    msg.Extensions[CryptoPairingMessage_pb2.cryptoPairingMessage].status = 0
    msg.Extensions[
        CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData = tlv_build(
            {
                kTLVType_State: b'\x05',
                kTLVType_EncryptedData: encrypted
            })
    send(msg, socket)
    msg = receive(socket)
    parsed = tlv_parse(msg.Extensions[
        CryptoPairingMessage_pb2.cryptoPairingMessage].pairingData)

    encrypted = parsed[kTLVType_EncryptedData]
    subtlv = tlv_parse(
        cha_cha_poly.decrypt(b"\0\0\0\0PS-Msg06", encrypted, None))

    hkdf = HKDF(hashes.SHA512(), 32, b"Pair-Setup-Accessory-Sign-Salt",
                b"Pair-Setup-Accessory-Sign-Info", default_backend())
    x = hkdf.derive(binascii.unhexlify(session.key))

    info = x + subtlv[kTLVType_Identifier] + subtlv[kTLVType_PublicKey]
    ed25519.VerifyingKey(subtlv[kTLVType_PublicKey]).verify(
        subtlv[kTLVType_Signature], info)

    pickle.dump(
        {
            "seed": ltsk.to_seed(),
            "peer_id": subtlv[kTLVType_Identifier],
            "peer_public_key": subtlv[kTLVType_PublicKey]
        }, open("data/pairing.state", "wb"))
Esempio n. 5
0
class SRPAuthHandler:
    """Handle SRP crypto routines for auth and key derivation."""

    def __init__(self):
        """Initialize a new SRPAuthHandler."""
        self.pairing_id = str(uuid.uuid4()).encode()
        self._signing_key = None
        self._auth_private = None
        self._auth_public = None
        self._verify_private = None
        self._verify_public = None
        self._public_bytes = None
        self._session = None
        self._shared = None
        self._session_key = None

    def initialize(self):
        """Initialize operation by generating new keys."""
        self._signing_key = Ed25519PrivateKey.from_private_bytes(os.urandom(32))
        self._auth_private = self._signing_key.private_bytes(
            encoding=serialization.Encoding.Raw,
            format=serialization.PrivateFormat.Raw,
            encryption_algorithm=serialization.NoEncryption(),
        )
        self._auth_public = self._signing_key.public_key().public_bytes(
            encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw
        )
        self._verify_private = X25519PrivateKey.from_private_bytes(os.urandom(32))
        self._verify_public = self._verify_private.public_key()
        self._public_bytes = self._verify_public.public_bytes(
            encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw
        )
        return self._auth_public, self._public_bytes

    def verify1(self, credentials, session_pub_key, encrypted):
        """First verification step."""
        self._shared = self._verify_private.exchange(
            X25519PublicKey.from_public_bytes(session_pub_key)
        )

        session_key = hkdf_expand(
            "Pair-Verify-Encrypt-Salt", "Pair-Verify-Encrypt-Info", self._shared
        )

        chacha = chacha20.Chacha20Cipher(session_key, session_key)
        decrypted_tlv = hap_tlv8.read_tlv(
            chacha.decrypt(encrypted, nounce="PV-Msg02".encode())
        )

        identifier = decrypted_tlv[TlvValue.Identifier]
        signature = decrypted_tlv[TlvValue.Signature]

        if identifier != credentials.atv_id:
            raise exceptions.AuthenticationError("incorrect device response")

        info = session_pub_key + bytes(identifier) + self._public_bytes
        ltpk = Ed25519PublicKey.from_public_bytes(bytes(credentials.ltpk))

        try:
            ltpk.verify(bytes(signature), bytes(info))
        except InvalidSignature as ex:
            raise exceptions.AuthenticationError("signature error") from ex

        device_info = self._public_bytes + credentials.client_id + session_pub_key

        device_signature = Ed25519PrivateKey.from_private_bytes(credentials.ltsk).sign(
            device_info
        )

        tlv = hap_tlv8.write_tlv(
            {
                TlvValue.Identifier: credentials.client_id,
                TlvValue.Signature: device_signature,
            }
        )

        return chacha.encrypt(tlv, nounce="PV-Msg03".encode())

    def verify2(self, salt, output_info, input_info):
        """Last verification step.

        The derived keys (output, input) are returned here.
        """
        output_key = hkdf_expand(salt, output_info, self._shared)
        input_key = hkdf_expand(salt, input_info, self._shared)
        log_binary(_LOGGER, "Keys", Output=output_key, Input=input_key)
        return output_key, input_key

    def step1(self, pin):
        """First pairing step."""
        context = SRPContext(
            "Pair-Setup",
            str(pin),
            prime=constants.PRIME_3072,
            generator=constants.PRIME_3072_GEN,
            hash_func=hashlib.sha512,
        )
        self._session = SRPClientSession(
            context, binascii.hexlify(self._auth_private).decode()
        )

    def step2(self, atv_pub_key, atv_salt):
        """Second pairing step."""
        pk_str = binascii.hexlify(atv_pub_key).decode()
        salt = binascii.hexlify(atv_salt).decode()
        self._session.process(pk_str, salt)

        if not self._session.verify_proof(self._session.key_proof_hash):
            raise exceptions.AuthenticationError("proofs do not match")

        pub_key = binascii.unhexlify(self._session.public)
        proof = binascii.unhexlify(self._session.key_proof)
        log_binary(_LOGGER, "Client", Public=pub_key, Proof=proof)
        return pub_key, proof

    def step3(self, additional_data=None):
        """Third pairing step."""
        ios_device_x = hkdf_expand(
            "Pair-Setup-Controller-Sign-Salt",
            "Pair-Setup-Controller-Sign-Info",
            binascii.unhexlify(self._session.key),
        )

        self._session_key = hkdf_expand(
            "Pair-Setup-Encrypt-Salt",
            "Pair-Setup-Encrypt-Info",
            binascii.unhexlify(self._session.key),
        )

        device_info = ios_device_x + self.pairing_id + self._auth_public
        device_signature = self._signing_key.sign(device_info)

        tlv = {
            TlvValue.Identifier: self.pairing_id,
            TlvValue.PublicKey: self._auth_public,
            TlvValue.Signature: device_signature,
        }

        if additional_data:
            tlv.update(additional_data)

        chacha = chacha20.Chacha20Cipher(self._session_key, self._session_key)
        encrypted_data = chacha.encrypt(
            hap_tlv8.write_tlv(tlv), nounce="PS-Msg05".encode()
        )
        log_binary(_LOGGER, "Data", Encrypted=encrypted_data)
        return encrypted_data

    def step4(self, encrypted_data):
        """Last pairing step."""
        chacha = chacha20.Chacha20Cipher(self._session_key, self._session_key)
        decrypted_tlv_bytes = chacha.decrypt(encrypted_data, nounce="PS-Msg06".encode())

        if not decrypted_tlv_bytes:
            raise exceptions.AuthenticationError("data decrypt failed")

        decrypted_tlv = hap_tlv8.read_tlv(decrypted_tlv_bytes)
        _LOGGER.debug("PS-Msg06: %s", decrypted_tlv)

        atv_identifier = decrypted_tlv[TlvValue.Identifier]
        atv_signature = decrypted_tlv[TlvValue.Signature]
        atv_pub_key = decrypted_tlv[TlvValue.PublicKey]
        log_binary(
            _LOGGER,
            "Device",
            Identifier=atv_identifier,
            Signature=atv_signature,
            Public=atv_pub_key,
        )

        # TODO: verify signature here

        return HapCredentials(
            atv_pub_key, self._auth_private, atv_identifier, self.pairing_id
        )
Esempio n. 6
0
class SRPAuthHandler:
    """Handle SRP data and crypto routines for auth and verification."""
    def __init__(self):
        """Initialize a new SRPAuthHandler."""
        self.seed = None
        self.session = None
        self._public_bytes = None
        self._auth_private = None
        self._auth_public = None
        self._verify_private = None
        self._verify_public = None

    def initialize(self, seed=None):
        """Initialize handler operation.

        This method will generate new encryption keys and must be called prior
        to doing authentication or verification.
        """
        self.seed = seed or os.urandom(32)  # Generate new seed if not provided
        signing_key = Ed25519PrivateKey.from_private_bytes(self.seed)
        verifying_key = signing_key.public_key()
        self._auth_private = signing_key.private_bytes(
            encoding=serialization.Encoding.Raw,
            format=serialization.PrivateFormat.Raw,
            encryption_algorithm=serialization.NoEncryption(),
        )
        self._auth_public = verifying_key.public_bytes(
            encoding=serialization.Encoding.Raw,
            format=serialization.PublicFormat.Raw)
        log_binary(
            _LOGGER,
            "Authentication keys",
            Private=self._auth_private,
            Public=self._auth_public,
        )

    def verify1(self):
        """First device verification step."""
        self._check_initialized()
        self._verify_private = X25519PrivateKey.from_private_bytes(self.seed)
        self._verify_public = self._verify_private.public_key()
        verify_private_bytes = self._verify_private.private_bytes(
            encoding=serialization.Encoding.Raw,
            format=serialization.PrivateFormat.Raw,
            encryption_algorithm=serialization.NoEncryption(),
        )
        self._public_bytes = self._verify_public.public_bytes(
            encoding=serialization.Encoding.Raw,
            format=serialization.PublicFormat.Raw)
        log_binary(
            _LOGGER,
            "Verification keys",
            Private=verify_private_bytes,
            Public=self._public_bytes,
        )

        return b"\x01\x00\x00\x00" + self._public_bytes + self._auth_public

    def verify2(self, atv_public_key, data):
        """Last device verification step."""
        self._check_initialized()
        log_binary(_LOGGER, "Verify", PublicSecret=atv_public_key, Data=data)

        # Generate a shared secret key
        shared = self._verify_private.exchange(
            X25519PublicKey.from_public_bytes(atv_public_key))
        log_binary(_LOGGER, "Shared secret", Secret=shared)

        # Derive new AES key and IV from shared key
        aes_key = hash_sha512("Pair-Verify-AES-Key", shared)[0:16]
        aes_iv = hash_sha512("Pair-Verify-AES-IV", shared)[0:16]
        log_binary(_LOGGER, "Pair-Verify-AES", Key=aes_key, IV=aes_iv)

        # Sign public keys and encrypt with AES
        signer = Ed25519PrivateKey.from_private_bytes(self._auth_private)
        signed = signer.sign(self._public_bytes + atv_public_key)
        signature, _ = aes_encrypt(modes.CTR, aes_key, aes_iv, data, signed)
        log_binary(_LOGGER, "Signature", Signature=signature)

        # Signature is prepended with 0x00000000 (alignment?)
        return b"\x00\x00\x00\x00" + signature

    def step1(self, username, password):
        """First authentication step."""
        self._check_initialized()
        context = AtvSRPContext(
            str(username),
            str(password),
            prime=constants.PRIME_2048,
            generator=constants.PRIME_2048_GEN,
        )
        self.session = SRPClientSession(
            context,
            binascii.hexlify(self._auth_private).decode())

    def step2(self, pub_key, salt):
        """Second authentication step."""
        self._check_initialized()
        pk_str = binascii.hexlify(pub_key).decode()
        salt = binascii.hexlify(salt).decode()
        client_session_key, _, _ = self.session.process(pk_str, salt)
        _LOGGER.debug("Client session key: %s", client_session_key)

        # Generate client public and session key proof.
        client_public = self.session.public
        client_session_key_proof = self.session.key_proof
        _LOGGER.debug("Client public: %s, proof: %s", client_public,
                      client_session_key_proof)

        if not self.session.verify_proof(self.session.key_proof_hash):
            raise AuthenticationError("proofs do not match (mitm?)")
        return client_public, client_session_key_proof

    def step3(self):
        """Last authentication step."""
        self._check_initialized()
        session_key = binascii.unhexlify(self.session.key)

        aes_key = hash_sha512("Pair-Setup-AES-Key", session_key)[0:16]
        tmp = bytearray(hash_sha512("Pair-Setup-AES-IV", session_key)[0:16])
        _LOGGER.debug("Increase last byte from %d to %s", tmp[-1], tmp[-1] + 1)
        tmp[-1] = tmp[-1] + 1  # Last byte must be increased by 1
        aes_iv = bytes(tmp)
        log_binary(_LOGGER, "Pair-Setup-AES", Key=aes_key, IV=aes_iv)

        epk, tag = aes_encrypt(modes.GCM, aes_key, aes_iv, self._auth_public)
        log_binary(_LOGGER, "Pair-Setup EPK+Tag", EPK=epk, Tag=tag)

        return epk, tag

    def _check_initialized(self):
        if not self.seed:
            raise NoCredentialsError("no credentials available")
Esempio n. 7
0
class SRPAuthenticationHandler(object):
    """
    Handle SRP (Secure Remote Password) data and crypto routines for auth and verification.
    """
    def __init__(self):
        self.seed = None
        self.session = None
        self._auth_private = None
        self._auth_public = None
        self._verify_private = None
        self._verify_public = None
        self.client_session_key = None

    def initialize(self, seed):
        """
        This method will generate new encryption keys and must be called prior to doing authentication or verification.
        :param seed: seed used for generation or key
        """
        self.seed = binascii.unhexlify(seed)
        signing_key = SigningKey(self.seed)
        verifying_key = signing_key.get_verifying_key()
        self._auth_private = signing_key.to_seed()
        self._auth_public = verifying_key.to_bytes()

    # region pairing

    @requires_credentials
    def step1(self, username, password):
        """
        First authentication step.
        :param username: username
        :param password: password
        """
        context = AtvSRPContext(str(username),
                                str(password),
                                prime=constants.PRIME_2048,
                                generator=constants.PRIME_2048_GEN)
        self.session = SRPClientSession(
            context,
            binascii.hexlify(self._auth_private).decode())

    @requires_credentials
    def step2(self, pub_key, salt):
        """
        Second authentication step. (Run SRP)
        :param pub_key: Apple TVs public key
        :param salt: Apple TVs salt
        """
        pk_str = binascii.hexlify(pub_key).decode()
        salt = binascii.hexlify(salt).decode()

        self.client_session_key, _, _ = self.session.process(pk_str, salt)

        # Generate client public and session key proof.
        client_public = self.session.public
        client_session_key_proof = self.session.key_proof

        if not self.session.verify_proof(self.session.key_proof_hash):
            raise DeviceAuthenticationError('proofs do not match (mitm?)')
        return binascii.unhexlify(client_public), binascii.unhexlify(
            client_session_key_proof)

    @requires_credentials
    def step3(self):
        """
        Last authentication step. (Run AES)
        :return epk, tag
        """
        session_key = binascii.unhexlify(self.client_session_key)

        aes_key = hash_sha512('Pair-Setup-AES-Key', session_key)[0:16]
        tmp = bytearray(hash_sha512('Pair-Setup-AES-IV', session_key)[0:16])
        tmp[-1] += +1  # Last byte must be increased by 1
        aes_iv = bytes(tmp)

        return aes_encrypt(modes.GCM, aes_key, aes_iv, self._auth_public)

    # endregion

    # region verification steps

    @requires_credentials
    def verify1(self):
        """
        First device verification step.
        """
        self._verify_private = curve25519.Private(secret=self.seed)
        self._verify_public = self._verify_private.get_public()
        verify_public = self._verify_public.serialize()
        return b'\x01\x00\x00\x00' + verify_public + self._auth_public

    @requires_credentials
    def verify2(self, atv_public_key, data):
        """
        Last device verification step.
        """
        # Generate a shared secret key
        public = curve25519.Public(atv_public_key)
        shared = self._verify_private.get_shared_key(
            public, hashfunc=lambda x: x)  # No additional hashing used

        # Derive new AES key and IV from shared key
        aes_key = hash_sha512('Pair-Verify-AES-Key', shared)[0:16]
        aes_iv = hash_sha512('Pair-Verify-AES-IV', shared)[0:16]

        # Sign public keys and encrypt with AES
        signer = SigningKey(self._auth_private)
        signed = signer.sign(self._verify_public.serialize() + atv_public_key)
        signature, _ = aes_encrypt(modes.CTR, aes_key, aes_iv, data, signed)

        # Signature is prepended with 0x00000000 (alignment?)
        return b'\x00\x00\x00\x00' + signature
Esempio n. 8
0
class SRPAuthHandler:
    """Handle SRP crypto routines for auth and key derivation."""

    def __init__(self):
        """Initialize a new SRPAuthHandler."""
        self.pairing_id = str(uuid.uuid4()).encode()
        self._signing_key = None
        self._auth_private = None
        self._auth_public = None
        self._verify_private = None
        self._verify_public = None
        self._session = None
        self._shared = None
        self._session_key = None
        self._client_session_key = None  # TODO: can remove?

    def initialize(self):
        """Initialize operation by generating new keys."""
        self._signing_key = SigningKey(os.urandom(32))
        self._auth_private = self._signing_key.to_seed()
        self._auth_public = self._signing_key.get_verifying_key().to_bytes()
        self._verify_private = curve25519.Private(secret=os.urandom(32))
        self._verify_public = self._verify_private.get_public()
        return self._auth_public, self._verify_public.serialize()

    def verify1(self, credentials, session_pub_key, encrypted):
        """First verification step."""
        # No additional hashing used
        self._shared = self._verify_private.get_shared_key(
            curve25519.Public(session_pub_key), hashfunc=lambda x: x)

        session_key = hkdf_expand('Pair-Verify-Encrypt-Salt',
                                  'Pair-Verify-Encrypt-Info',
                                  self._shared)

        chacha = chacha20.Chacha20Cipher(session_key, session_key)
        decrypted_tlv = tlv8.read_tlv(
            chacha.decrypt(encrypted, nounce='PV-Msg02'.encode()))

        identifier = decrypted_tlv[tlv8.TLV_IDENTIFIER]
        signature = decrypted_tlv[tlv8.TLV_SIGNATURE]

        if identifier != credentials.atv_id:
            raise Exception('incorrect device response')  # TODO: new exception

        info = session_pub_key + \
            bytes(identifier) + self._verify_public.serialize()
        ltpk = VerifyingKey(bytes(credentials.ltpk))
        ltpk.verify(bytes(signature), bytes(info))  # throws if no match

        device_info = self._verify_public.serialize() + \
            credentials.client_id + session_pub_key

        device_signature = SigningKey(credentials.ltsk).sign(device_info)

        tlv = tlv8.write_tlv({tlv8.TLV_IDENTIFIER: credentials.client_id,
                              tlv8.TLV_SIGNATURE: device_signature})

        return chacha.encrypt(tlv, nounce='PV-Msg03'.encode())

    def verify2(self):
        """Last verification step.

        The derived keys (output, input) are returned here.
        """
        output_key = hkdf_expand('MediaRemote-Salt',
                                 'MediaRemote-Write-Encryption-Key',
                                 self._shared)

        input_key = hkdf_expand('MediaRemote-Salt',
                                'MediaRemote-Read-Encryption-Key',
                                self._shared)

        log_binary(_LOGGER, 'Keys', Output=output_key, Input=input_key)
        return output_key, input_key

    def step1(self, pin):
        """First pairing step."""
        context = SRPContext(
            'Pair-Setup', str(pin),
            prime=constants.PRIME_3072,
            generator=constants.PRIME_3072_GEN,
            hash_func=hashlib.sha512)
        self._session = SRPClientSession(
            context, binascii.hexlify(self._auth_private).decode())

    def step2(self, atv_pub_key, atv_salt):
        """Second pairing step."""
        pk_str = binascii.hexlify(atv_pub_key).decode()
        salt = binascii.hexlify(atv_salt).decode()
        self._client_session_key, _, _ = self._session.process(pk_str, salt)

        if not self._session.verify_proof(self._session.key_proof_hash):
            raise exceptions.AuthenticationError('proofs do not match (mitm?)')

        pub_key = binascii.unhexlify(self._session.public)
        proof = binascii.unhexlify(self._session.key_proof)
        log_binary(_LOGGER, 'Client', Public=pub_key, Proof=proof)
        return pub_key, proof

    def step3(self):
        """Third pairing step."""
        ios_device_x = hkdf_expand(
            'Pair-Setup-Controller-Sign-Salt',
            'Pair-Setup-Controller-Sign-Info',
            binascii.unhexlify(self._client_session_key))

        self._session_key = hkdf_expand(
            'Pair-Setup-Encrypt-Salt',
            'Pair-Setup-Encrypt-Info',
            binascii.unhexlify(self._client_session_key))

        device_info = ios_device_x + self.pairing_id + self._auth_public
        device_signature = self._signing_key.sign(device_info)

        tlv = tlv8.write_tlv({tlv8.TLV_IDENTIFIER: self.pairing_id,
                              tlv8.TLV_PUBLIC_KEY: self._auth_public,
                              tlv8.TLV_SIGNATURE: device_signature})

        chacha = chacha20.Chacha20Cipher(self._session_key, self._session_key)
        encrypted_data = chacha.encrypt(tlv, nounce='PS-Msg05'.encode())
        log_binary(_LOGGER, 'Data', Encrypted=encrypted_data)
        return encrypted_data

    def step4(self, encrypted_data):
        """Last pairing step."""
        chacha = chacha20.Chacha20Cipher(self._session_key, self._session_key)
        decrypted_tlv_bytes = chacha.decrypt(
            encrypted_data, nounce='PS-Msg06'.encode())
        if not decrypted_tlv_bytes:
            raise Exception('data decrypt failed')  # TODO: new exception
        decrypted_tlv = tlv8.read_tlv(decrypted_tlv_bytes)
        _LOGGER.debug('PS-Msg06: %s', decrypted_tlv)

        atv_identifier = decrypted_tlv[tlv8.TLV_IDENTIFIER]
        atv_signature = decrypted_tlv[tlv8.TLV_SIGNATURE]
        atv_pub_key = decrypted_tlv[tlv8.TLV_PUBLIC_KEY]
        log_binary(_LOGGER,
                   'Device',
                   Identifier=atv_identifier,
                   Signature=atv_signature,
                   Public=atv_pub_key)

        # TODO: verify signature here

        return Credentials(atv_pub_key, self._signing_key.to_seed(),
                           atv_identifier, self.pairing_id)