Skip to content
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
5 changes: 3 additions & 2 deletions examples/_login.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from __future__ import annotations

from findmy.reports import (
from findmy import (
AppleAccount,
AsyncAppleAccount,
LocalAnisetteProvider,
LoginState,
RemoteAnisetteProvider,
SmsSecondFactorMethod,
TrustedDeviceSecondFactorMethod,
)
from findmy.reports.anisette import LocalAnisetteProvider, RemoteAnisetteProvider


def _login_sync(account: AppleAccount) -> None:
Expand Down
6 changes: 3 additions & 3 deletions examples/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import logging
from pathlib import Path

from findmy import KeyPair
from findmy.accessory import FindMyAccessory
from findmy.scanner import (
from findmy import (
FindMyAccessory,
KeyPair,
NearbyOfflineFindingDevice,
OfflineFindingScanner,
SeparatedOfflineFindingDevice,
Expand Down
82 changes: 74 additions & 8 deletions findmy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,81 @@
"""A package providing everything you need to work with Apple's FindMy network."""

from . import errors, keys, plist, reports, scanner
from .accessory import FindMyAccessory
from .keys import KeyPair
from .accessory import FindMyAccessory, FindMyAccessoryMapping, RollingKeyPairSource
from .errors import (
InvalidCredentialsError,
InvalidStateError,
UnauthorizedError,
UnhandledProtocolError,
)
from .keys import HasHashedPublicKey, HasPublicKey, KeyPair, KeyPairMapping, KeyPairType
from .reports import (
AccountStateMapping,
AnisetteMapping,
AppleAccount,
AsyncAppleAccount,
AsyncSmsSecondFactor,
AsyncTrustedDeviceSecondFactor,
BaseAnisetteProvider,
BaseAppleAccount,
BaseSecondFactorMethod,
LocalAnisetteMapping,
LocalAnisetteProvider,
LocationReport,
LocationReportDecryptedMapping,
LocationReportEncryptedMapping,
LocationReportMapping,
LoginState,
RemoteAnisetteMapping,
RemoteAnisetteProvider,
SmsSecondFactorMethod,
SyncSmsSecondFactor,
SyncTrustedDeviceSecondFactor,
TrustedDeviceSecondFactorMethod,
)
from .scanner import (
NearbyOfflineFindingDevice,
OfflineFindingDevice,
OfflineFindingScanner,
SeparatedOfflineFindingDevice,
)

__all__ = (
"AccountStateMapping",
"AnisetteMapping",
"AppleAccount",
"AsyncAppleAccount",
"AsyncSmsSecondFactor",
"AsyncTrustedDeviceSecondFactor",
"BaseAnisetteProvider",
"BaseAppleAccount",
"BaseSecondFactorMethod",
"FindMyAccessory",
"FindMyAccessoryMapping",
"HasHashedPublicKey",
"HasPublicKey",
"InvalidCredentialsError",
"InvalidStateError",
"KeyPair",
"errors",
"keys",
"plist",
"reports",
"scanner",
"KeyPairMapping",
"KeyPairType",
"LocalAnisetteMapping",
"LocalAnisetteProvider",
"LocationReport",
"LocationReportDecryptedMapping",
"LocationReportEncryptedMapping",
"LocationReportMapping",
"LoginState",
"NearbyOfflineFindingDevice",
"OfflineFindingDevice",
"OfflineFindingScanner",
"RemoteAnisetteMapping",
"RemoteAnisetteProvider",
"RollingKeyPairSource",
"SeparatedOfflineFindingDevice",
"SmsSecondFactorMethod",
"SyncSmsSecondFactor",
"SyncTrustedDeviceSecondFactor",
"TrustedDeviceSecondFactorMethod",
"UnauthorizedError",
"UnhandledProtocolError",
)
26 changes: 12 additions & 14 deletions findmy/accessory.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@

from typing_extensions import override

from findmy.util.abc import Serializable
from findmy.util.files import read_data_json, read_data_plist, save_and_return_json

from .keys import KeyGenerator, KeyPair, KeyType
from . import util
from .keys import KeyGenerator, KeyPair, KeyPairType
from .util import crypto

if TYPE_CHECKING:
Expand Down Expand Up @@ -93,7 +91,7 @@ def keys_between(
yield ind, key


class FindMyAccessory(RollingKeyPairSource, Serializable[FindMyAccessoryMapping]):
class FindMyAccessory(RollingKeyPairSource, util.abc.Serializable[FindMyAccessoryMapping]):
"""A findable Find My-accessory using official key rollover."""

def __init__( # noqa: PLR0913
Expand All @@ -116,8 +114,8 @@ def __init__( # noqa: PLR0913
:param skn: The SKN for the primary key.
:param sks: The SKS for the secondary key.
"""
self._primary_gen = AccessoryKeyGenerator(master_key, skn, KeyType.PRIMARY)
self._secondary_gen = AccessoryKeyGenerator(master_key, sks, KeyType.SECONDARY)
self._primary_gen = _AccessoryKeyGenerator(master_key, skn, KeyPairType.PRIMARY)
self._secondary_gen = _AccessoryKeyGenerator(master_key, sks, KeyPairType.SECONDARY)
self._paired_at: datetime = paired_at
if self._paired_at.tzinfo is None:
self._paired_at = self._paired_at.astimezone()
Expand Down Expand Up @@ -270,7 +268,7 @@ def from_plist(
name: str | None = None,
) -> FindMyAccessory:
"""Create a FindMyAccessory from a .plist file dumped from the FindMy app."""
device_data = read_data_plist(plist)
device_data = util.files.read_data_plist(plist)

# PRIVATE master key. 28 (?) bytes.
master_key = device_data["privateKey"]["key"]["data"][-28:]
Expand All @@ -295,7 +293,7 @@ def from_plist(
alignment_date = None
index = None
if key_alignment_plist:
alignment_data = read_data_plist(key_alignment_plist)
alignment_data = util.files.read_data_plist(key_alignment_plist)

# last observed date
alignment_date = alignment_data["lastIndexObservationDate"].replace(
Expand Down Expand Up @@ -335,7 +333,7 @@ def to_json(self, path: str | Path | None = None, /) -> FindMyAccessoryMapping:
"alignment_index": self._alignment_index,
}

return save_and_return_json(res, path)
return util.files.save_and_return_json(res, path)

@classmethod
@override
Expand All @@ -344,7 +342,7 @@ def from_json(
val: str | Path | FindMyAccessoryMapping,
/,
) -> FindMyAccessory:
val = read_data_json(val)
val = util.files.read_data_json(val)
assert val["type"] == "accessory"

try:
Expand All @@ -368,14 +366,14 @@ def from_json(
raise ValueError(msg) from None


class AccessoryKeyGenerator(KeyGenerator[KeyPair]):
class _AccessoryKeyGenerator(KeyGenerator[KeyPair]):
"""KeyPair generator. Uses the same algorithm internally as FindMy accessories do."""

def __init__(
self,
master_key: bytes,
initial_sk: bytes,
key_type: KeyType = KeyType.UNKNOWN,
key_type: KeyPairType = KeyPairType.UNKNOWN,
) -> None:
"""
Initialize the key generator.
Expand Down Expand Up @@ -411,7 +409,7 @@ def initial_sk(self) -> bytes:
return self._initial_sk

@property
def key_type(self) -> KeyType:
def key_type(self) -> KeyPairType:
"""The type of key this generator produces."""
return self._key_type

Expand Down
8 changes: 4 additions & 4 deletions findmy/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from pathlib import Path


class KeyType(Enum):
class KeyPairType(Enum):
"""Enum of possible key types."""

UNKNOWN = 0
Expand Down Expand Up @@ -133,7 +133,7 @@ class KeyPair(HasPublicKey, Serializable[KeyPairMapping]):
def __init__(
self,
private_key: bytes,
key_type: KeyType = KeyType.UNKNOWN,
key_type: KeyPairType = KeyPairType.UNKNOWN,
name: str | None = None,
) -> None:
"""Initialize the :meth:`KeyPair` with the private key bytes."""
Expand All @@ -147,7 +147,7 @@ def __init__(
self._name = name

@property
def key_type(self) -> KeyType:
def key_type(self) -> KeyPairType:
"""Type of this key."""
return self._key_type

Expand Down Expand Up @@ -217,7 +217,7 @@ def from_json(cls, val: str | Path | KeyPairMapping, /) -> KeyPair:
try:
return cls(
private_key=base64.b64decode(val["private_key"]),
key_type=KeyType(val["key_type"]),
key_type=KeyPairType(val["key_type"]),
name=val["name"],
)
except KeyError as e:
Expand Down
42 changes: 39 additions & 3 deletions findmy/reports/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,52 @@
"""Code related to fetching location reports."""

from .account import AppleAccount, AsyncAppleAccount
from .anisette import BaseAnisetteProvider, RemoteAnisetteProvider
from .account import AccountStateMapping, AppleAccount, AsyncAppleAccount, BaseAppleAccount
from .anisette import (
AnisetteMapping,
BaseAnisetteProvider,
LocalAnisetteMapping,
LocalAnisetteProvider,
RemoteAnisetteMapping,
RemoteAnisetteProvider,
)
from .reports import (
LocationReport,
LocationReportDecryptedMapping,
LocationReportEncryptedMapping,
LocationReportMapping,
)
from .state import LoginState
from .twofactor import SmsSecondFactorMethod, TrustedDeviceSecondFactorMethod
from .twofactor import (
AsyncSmsSecondFactor,
AsyncTrustedDeviceSecondFactor,
BaseSecondFactorMethod,
SmsSecondFactorMethod,
SyncSmsSecondFactor,
SyncTrustedDeviceSecondFactor,
TrustedDeviceSecondFactorMethod,
)

__all__ = (
"AccountStateMapping",
"AnisetteMapping",
"AppleAccount",
"AsyncAppleAccount",
"AsyncSmsSecondFactor",
"AsyncTrustedDeviceSecondFactor",
"BaseAnisetteProvider",
"BaseAppleAccount",
"BaseSecondFactorMethod",
"LocalAnisetteMapping",
"LocalAnisetteProvider",
"LocationReport",
"LocationReportDecryptedMapping",
"LocationReportEncryptedMapping",
"LocationReportMapping",
"LoginState",
"RemoteAnisetteMapping",
"RemoteAnisetteProvider",
"SmsSecondFactorMethod",
"SyncSmsSecondFactor",
"SyncTrustedDeviceSecondFactor",
"TrustedDeviceSecondFactorMethod",
)
Loading