Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for device authentication #88

Merged
merged 4 commits into from
Jun 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,23 @@ CHANGES

Changes:

- Fix atvremote exit codes
- Support AirPlay device authentication
- Support arrow keys (left, right, up, down)
- Support scanning for Apple TVs with home sharing disabled
- Support for shuffle and repeat modes
- Support for "stop" button
- Multiple commands can be given to atvremote
- Handle additional media kinds
- Fix atvremote exit codes
- Support python 3.6
- Bump aiohttp to 1.3.5 and support 2.0.0+

Notes:

- play_url has moved to the new airplay module and no longer
accepts start position as required argument. This is a
breaking change!

Other:

- Upgrade test tools (pylint, flake, etc.)
Expand Down
44 changes: 29 additions & 15 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ well as some additional iTunes commands, like changing the media position. It im
legacy DAAP-protocol and does not support features from the new MediaRemote.framework. Support
for this might be added in the future if that protocol is ever fully reverse engineered.

**Note: AirPlay support is currently broken for tvOS 10.2, see issue #79.**

The MIT license is used for this library.

Features
Expand All @@ -18,6 +16,7 @@ Features
- Automatic discovery of devices (zeroconf/Bonjour)
- Push updates
- Remote control pairing
- AirPlay stream URL (including tvOS 10.2+)
- Playback controls (play, pause, next, stop, etc.)
- Navigation controls (select, menu, top_menu, arrow keys)
- Fetch artwork in PNG format
Expand All @@ -29,8 +28,7 @@ Requirements
------------

- python>=3.4.2
- zeroconf>=0.17.7
- aiohttp>=1.3.5
- See documentation for additional libraries

Getting started
---------------
Expand Down Expand Up @@ -77,7 +75,7 @@ It is possible to use the reference CLI application as well:
# Scanning for devices on network
$ atvremote
Found Apple TVs:
- Apple TV at 10.0.10.22 (hsgid: 00000000-1234-5678-9012- 345678901234)
- Apple TV at 10.0.10.22 (hsgid: 00000000-1234-5678-9012-345678901234)

Note: You must use 'pair' with devices that have home sharing disabled

Expand All @@ -87,6 +85,8 @@ It is possible to use the reference CLI application as well:
Play state: Playing
Position: 0/397s (0.0%)

# Passing multiple commands
$ atvremote -a next next play playing stop

# List all commands supported by a device
$ atvremote -a commands
Expand All @@ -97,7 +97,6 @@ It is possible to use the reference CLI application as well:
- next - Press key next
- pause - Press key play
- play - Press key play
- play_url - Play media from an URL on the device
- previous - Press key previous
- right - Press key right
- select - Press key select
Expand All @@ -116,15 +115,24 @@ It is possible to use the reference CLI application as well:
Playing commands:
- album - Album of the currently playing song
- artist - Artist of the currently playing song
- media_type - What type of media is currently playing, e.g. video, music
- play_state - Current play state, e.g. playing or paused
- position - Current position in the playing media (seconds)
- repeat - Current repeat mode
- media_type - Type of media is currently playing, e.g. video, music
- play_state - Play state, e.g. playing or paused
- position - Position in the playing media (seconds)
- repeat - Repeat mode
- shuffle - If shuffle is enabled or not
- title - Title of the current media, e.g. movie or song name
- total_time - Total play time in seconds

AirPlay commands:
- finish_authentication - End authentication process with PIN code
- generate_credentials - Create new credentials for authentication
- load_credentials - Load existing credentials
- play_url - Play media from an URL on the device
- start_authentication - Begin authentication proces (show PIN on screen)
- verify_authenticated - Check if loaded credentials are verified

Other commands:
- auth - Perform AirPlay device authentication
- push_updates - Listen for push updates

Type ``atvremote --help`` to list all supported commands.
Expand All @@ -133,13 +141,19 @@ Missing features and improvements
---------------------------------

Most of the core functionality is now in place and API is starting to mature
enough to soon be called "stable". The next major things to support are device
verification (#79) to make AirPlay work with tvOS (10.2+) again. After that,
implementation of the MediaRemoteTV protocol used by newer devices (tvOS) will
be given a shot.
enough to soon be called "stable". Things on the roadmap are listed below.

Planned tasks
^^^^^^^^^^^^^

- Implement MediaRemoteTV protocol
- Investigate robustness of device scanning
- Extend AirPlay support

- Easy streaming of local files

Minor tasks
^^^^^^^^^^^^
^^^^^^^^^^^

- Help command to get full help text for a command (atvremote)
- Write simple smoke test for atvremote
Expand Down
5 changes: 5 additions & 0 deletions docs/_static/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import url('default.css');

.strike {
text-decoration: line-through;
}
145 changes: 145 additions & 0 deletions docs/airplay.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
.. _pyatv-airplay:

AirPlay Support
====================
Currently there is some AirPlay functionality supported in pyatv, but it is
very limited. Only two features are supported:

- Device authentication
- Playing media via URL

Additional features will be added as needed.

Device Authentication
---------------------
In tvOS 10.2, Apple started to enforce a feature called "device authentication".
This requires every device that streams content via AirPlay to enter a PIN code
the first time before playback is started. Once done, the user will never have
to do this again. The actual feature has been available for a while but as
opt-in, so it would have to be explicitly enabled. Now it is enabled by default
and cannot be disabled. Devices not running tvOS (e.g. Apple TV 2nd and 3rd
generation) are not affected, even though device authentication can be enabled
on theses devices as well.

The device authentication process is based on the *Secure Remote Password*
protocol (SRP), with slight modifications. All the reverse engineering required
for this process was made by funtax (GitHub username) and has merly been ported
to python for usage in this library. Please see references at bottom of page
for reference implementation.

Generating credentials
^^^^^^^^^^^^^^^^^^^^^^
When performing device authentication, a device identifier and a private key is
required. Once authenticated, they can be used to authenticate without using a
PIN code. So they must be saved and re-used whenever something is to be played.

In this library, the device identifier and private key is called
*AirPlay credentials* and are concatenated into a string, using : as separator.
An example might look like this:

.. code::

D9B75D737BE2F0F1:6A26D8EB6F4AE2408757D5CA5FF9C37E96BEBB22C632426C4A02AD4FA895A85B
^ ^
Identifier Private key

New (random) credentials can be generated and loaded in the following way:

.. code:: python

credentials = yield from atv.airplay.generate_credentials()
yield from atv.airplay.load_credentials(credentials)

It is important to load the newly created credentials as they are not
automatically loaded when being generated. This is also how previously
authenticated credentials are re-used.

.. note::

There is no builtin support for storing credentials. It is up to the
application to handle this.

API Reference: :py:meth:`pyatv.interface.AirPlay.generate_credentials`,
:py:meth:`pyatv.interface.AirPlay.load_credentials`

Authenticating credentials
^^^^^^^^^^^^^^^^^^^^^^^^^^
Performing the authentication requires two steps. First, the process must be
initiated with the device so that a PIN code is displayed. Then it can be
completed by providing the PIN code back to the library:

.. code:: python

yield from atv.airplay.start_authentication()
pin = ... # Get PIN from user
yield from atv.airplay.finish_authentication(pin)

If the authentication process fails, a
:py:class:`pyatv.exceptions.DeviceAuthenticationError` is raised.

API Reference: :py:meth:`pyatv.interface.AirPlay.start_authentication`,
:py:meth:`pyatv.interface.AirPlay.finish_authentication`

Verifying credentials
^^^^^^^^^^^^^^^^^^^^^
To verify if the loaded credentials are authenticated, ``verify_authenticated``
can be used:

.. code:: python

credentials = ...
yield from atv.airplay.load_credentials(credentials)
yield from atv.airplay.verify_authenticated()

If the credentials are not properly authenticated, a
:py:class:`pyatv.exceptions.DeviceAuthenticationError` is raised.

API Reference: :py:meth:`pyatv.interface.AirPlay.verify_authenticated`

Example
^^^^^^^
A complete code example of the authentication might look like this (it's
available under ``examples``):

.. code:: python

import sys
import asyncio
from pyatv import (exceptions, helpers)


@asyncio.coroutine
def authenticate_with_device(atv):
"""Perform device authentication and print credentials."""
credentials = yield from atv.airplay.generate_credentials()
yield from atv.airplay.load_credentials(credentials)

try:
yield from atv.airplay.start_authentication()
pin = input('PIN Code: ')
yield from atv.airplay.finish_authentication(pin)
print('Credentials: {0}'.format(credentials))

except exceptions.DeviceAuthenticationError:
print('Failed to authenticate', file=sys.stderr)


helpers.auto_connect(authenticate_with_device)

Playing media
-------------
Playing a URL is as simple as passing the URL to ``play_url``:

.. code:: python

url = 'http://...'
yield from atv.airplay.play_url(url)

If the device requires device authentication, valid credentials must be loaded
using :py:meth:`pyatv.interface.AirPlay.load_credentials` first. Otherwise an
error message will be shown on the screen.

References
----------
https://github.com/funtax/AirPlayAuth
https://nto.github.io/AirPlay.html
40 changes: 35 additions & 5 deletions docs/atvremote.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,28 @@ It is possible to listen for and print push updates as they happen using the

Just press ENTER to stop.

AirPlay Device Authentication
-----------------------------
To play a URL with AirPlay on a device running tvOS 10.2 or later, *device
authentication* must be performed (or if it has been explicitly enabled).
Simply use the ``auth`` command for this:

.. code:: bash

$ atvremote -a auth
Enter PIN on screen: 1234 # Pin code displayed on screen
You may now use these credentials:
D9B75D737BE2F0F1:6A26D8EB6F4AE2408757D5CA5FF9C37E96BEBB22C632426C4A02AD4FA895A85B

The generated credentials must be used every time something is to be played
(otherwise a PIN will be required again). So make sure to save it somewhere.

To play a URL, then just use ``play_url`` and provide the credentials:

.. code:: bash

$ atvremote -a --airplay_credentials D9B75D737BE2F0F1:6A26D8EB6F4AE2408757D5CA5FF9C37E96BEBB22C632426C4A02AD4FA895A85B play_url=<some URL here>

Working with commands
---------------------
Several commands are supported by the library (and thus the device). Easiest
Expand All @@ -138,7 +160,6 @@ availble commands:
- next - Press key next
- pause - Press key play
- play - Press key play
- play_url - Play media from an URL on the device
- previous - Press key previous
- right - Press key right
- select - Press key select
Expand All @@ -157,15 +178,24 @@ availble commands:
Playing commands:
- album - Album of the currently playing song
- artist - Artist of the currently playing song
- media_type - What type of media is currently playing, e.g. video, music
- play_state - Current play state, e.g. playing or paused
- position - Current position in the playing media (seconds)
- repeat - Current repeat mode
- media_type - Type of media is currently playing, e.g. video, music
- play_state - Play state, e.g. playing or paused
- position - Position in the playing media (seconds)
- repeat - Repeat mode
- shuffle - If shuffle is enabled or not
- title - Title of the current media, e.g. movie or song name
- total_time - Total play time in seconds

AirPlay commands:
- finish_authentication - End authentication process with PIN code
- generate_credentials - Create new credentials for authentication
- load_credentials - Load existing credentials
- play_url - Play media from an URL on the device
- start_authentication - Begin authentication proces (show PIN on screen)
- verify_authenticated - Check if loaded credentials are verified

Other commands:
- auth - Perform AirPlay device authentication
- push_updates - Listen for push updates

You can for instance get what is currently playing with ``playing``:
Expand Down
3 changes: 1 addition & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static']

html_static_path = ['_static']

# -- Options for HTMLHelp output ------------------------------------------

Expand Down
13 changes: 11 additions & 2 deletions docs/faq.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
.. _pyatv-faq:

.. role:: strike
:class: strike

FAQ
===
This page tries to answer some common questions.
Expand Down Expand Up @@ -28,12 +31,18 @@ have to sit tight and wait for a fix in tvOS.
iOS 7.1 or later, OS X 10.10 or later, or iTunes 11.2 or later." on the screen.
What's wrong?**

From tvOS 10.2 and later, Apple enforces "device verification". This was optional
The device authentication process has now been reversed engineered and implemented
in pyatv. In order to get rid of this message, you must perform *device authentication*.
If you are interested in development details (like the API), check out
:ref:`the AirPlay page<pyatv-airplay>`. To try it out with ``atvremote``, instead
check out the section about it at :ref:`atvremote<pyatv-atvremote>`.

:strike:`From tvOS 10.2 and later, Apple enforces "device verification". This was optional
and disabled by default in earlier versions. After this update, a lot of 3rd party
tools broke, including pyatv. This means that it is currently not possible to stream
content to an Apple TV running tvOS 10.2 (or later). As soon as the device
verification scheme has been reversed engineered, support will be added. But there
is no timeframe for this.
is no timeframe for this.`

Technical Questions
-------------------
Expand Down
Loading