Skip to content

CRITICAL: AttestationHandler positional arg mismatch — device_id passed as key_id, breaking iOS attestation #5

Description

@pyrahermesagent

Severity: CRITICAL

File: ApiApp/serializers.py
Line: 72

Description

DeviceRegisterSerializer.validate() calls AttestationHandler with positional arguments:

handler = AttestationHandler(nonce, platform, attestation, assertion, device_id, public_key)

But AttestationHandler.__init__ signature is:

def __init__(
    self,
    nonce: str,
    platform: Literal["android", "ios"],
    attestation_token: str | None = None,
    assertion_token: str | None = None,
    key_id: str | None = None,
    public_key: EllipticCurvePublicKey | None = None
):

This means device_id is passed as the key_id parameter (5th positional arg), not as a device identifier.

Impact

For Android:

  • urlsafe_b64decode_padded(device_id) runs on every registration. Most device IDs are valid base64 so this does not crash, but the decoded _key_id bytes are meaningless (device ID bytes, not a key ID). This is silently ignored for Android since _key_id is only used in iOS flows.

For iOS:

  • New device registration: AppleConfig(self._key_id, ...) uses decoded device_id bytes as the App Attest key identifier. This does not match the actual key ID from Apple, so attestation verification will fail or behave unpredictably.
  • Subsequent registration with assertion: Assertion(..., sha256(self._nonce).digest(), self._public_key, config) uses self._key_id (decoded device_id) in the config. This is wrong — the key_id should be the actual App Attest key identifier.
  • Result: iOS devices cannot reliably re-register after their first attestation, and the assertion-based verification path is broken.

Root Cause

Positional argument call instead of keyword arguments. The serializer author intended to pass device_id as a separate concept but it silently landed in the key_id parameter slot.

Fix

Use keyword arguments in the serializer call:

handler = AttestationHandler(
    nonce=nonce,
    platform=platform,
    attestation_token=attestation,
    assertion_token=assertion,
    public_key=public_key
)

key_id is not passed because it is an Apple-specific internal identifier that the device does not send — the server derives it from the attestation response. For iOS re-registration (assertion flow), the key_id should be stored on the device record and retrieved from there.

Additionally, store key_id on the AttestedFCMDevice model so it can be used for subsequent assertion verification.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions