INSIDE ATLANTIS

TDIF technical profile

Scope: technical profile only; no accreditation claims. Atlantis, its agencies and every person are fictional.

Source of requirements. The current instrument is the Digital ID (AGDIS) Data Standards 2024: Schedule 1 (onboarding), Schedule 2 (AGDIS OpenID Connect Profile, "based off" TDIF 06B OpenID Connect 1.0 Profile release 4.8) and Schedule 3 (attribute profile). Clause numbers below are from Schedules 1 and 2. Schedule 3 wasn't readable from the published HTML; attribute claim names below are the standard OIDC ones the profile reuses. TDIF roles map to the demo as: Identity Service Provider (ISP) = Atlantis ID and Poseidon ID; Identity Exchange (IXP) = the Keycloak exchange; Relying Party (RP) = Agency A. Agency B's wallet flow is outside the OIDC profile and is listed separately.

Status key: ✅ meets · ⚠️ partial · ❌ gap.

1. RP ↔ Exchange (Agency A → Keycloak)

# Requirement Clause Status Now Gap / change
1.1 Authorization code flow, exact redirect URI, state, nonce Sch 2 §1.7 ✅ All present in oidc-client.ts —
1.2 PKCE with S256; plain refused Sch 2 §1.8.1.5 ✅ S256 on every request; Keycloak client requires S256 —
1.3 Client authentication only private_key_jwt; assertion ≤ 300 s with iss, sub, aud, jti, exp Sch 2 §1.8.1.2 ✅ Agency A signs ES256 client assertions (3 min) with a persistent key published at /jwks; Keycloak client uses Signed JWT Agency A signs a client assertion (code exists in keys.ts) and publishes a JWKS; Keycloak client switches to "Signed JWT" with the JWKS URL.
1.4 Request objects signed with the client's key; IXP validates Sch 2 §1.7.5, §1.8.5 ✅ Agency A sends a signed request JWT; Keycloak requires and verifies it Agency A sends a signed request JWT; Keycloak client set to require request objects and verify with the same JWKS.
1.5 Request-object encryption to the IXP Sch 2 §1.7.5 ✅ Agency A encrypts its signed request object to the exchange's RSA-OAEP-256 key (A256GCM). A client policy (tdif-agdis-rp: secure-request-object with encryption required and nbf verified, PKCE enforcer, private_key_jwt only) refuses plain requests: "Request object not encrypted"
1.6 ID token signed RS256 (or stronger) Sch 2 §1.8.3 ✅ Keycloak RS256; client allows RS256/ES256/PS256 —
1.7 ID token claims: iss, aud, sub (pairwise), acr, jti, exp, iat, nbf, nonce Sch 2 §1.8.3.1 ✅ All present. nbf comes from the Atlantis extension's mapper (nbf = iat) on ID and access tokens
1.8 ID token ≤ 300 s; access token ≤ 3 600 s Sch 2 §1.8.3 ✅ Both 300 s —
1.9 Pairwise sub per RP (OIDC Core §8.1); sector identifier recommended Sch 1 §1.2.2, Sch 2 §1.8.3.1 ✅ oidc-sha256-pairwise-sub-mapper, one redirect host Sector identifier URI: roadmap if Agency A gains more hosts.
1.10 acr_values or essential acr honoured; achieved level always returned Sch 2 §1.9.3, §1.8.6 ✅ Changing payment details at Agency A asks for an essential ip1:cl2 in the claims parameter, with prompt=login and max_age=0. The exchange maps TDIF levels to Keycloak LoA, passes max_age upstream, and its post-broker-login gate refuses when the provider's achieved level is lower. Poseidon ID (CL1) is refused; a met level completes Verified live with an Atlantis ID passkey: Auth0 asked again, achieved ip1:cl2, step-up completed.
1.11 Consent collected at the IXP before returning to the RP Sch 2 §1.10 ✅ Keycloak consent screen for Agency A; recorded in the LOGIN event (consent: GRANTED) and shown on the dashboard Turn on client consent for Agency A in the exchange realm (scope-level consent screen); consent is then in Keycloak's audit events.
1.12 RP audit ID generated per RP request, returned to the RP, never sent to the ISP Sch 1 §2.1.2 ✅ rp_audit_id claim from the extension's mapper equals the exchange's code_id on every event of the sign-in; Agency A shows it; it never goes upstream An SSO re-login reuses the session's id.
1.13 Discovery and JWKS, key rotation Sch 2 §1.8 ✅ Keycloak discovery; Agency A reads jwks_uri —
1.14 Single logout if SSO is offered; front-channel supported Sch 1 §2.1.6–7 ✅ OIDC front-channel logout (iss + sid) to Agency A; signing out at the exchange ends the Agency A session, and RP-initiated logout reaches Poseidon ID
1.15 Double-blind: RP must not learn the ISP; ISP must not learn the RP Sch 1 (exchange purpose) ✅ idp claim removed; Agency A's pages and events never name a provider Remove the claim and the wording; only the exchange (and the dashboard's exchange lane) knows.

2. ISP ↔ Exchange (Auth0 / Poseidon → Keycloak)

# Requirement Clause Status Now Gap / change
2.1 Same OIDC profile on the ISP leg: code flow, PKCE, private_key_jwt, signed request objects Sch 2 §2 ✅ Poseidon / ⚠️ Auth0 Code flow + PKCE S256 and private_key_jwt on both legs. The extension's broker override sends signed request objects; Poseidon ID now requires them. Auth0 accepts signed request objects only on Enterprise with the Highly Regulated Identity add-on, so the Auth0 leg stays unsigned Done for Poseidon; Auth0 is a plan limit.
2.2 IXP verifies iss, aud, nonce, exp, iat, nbf Sch 2 §2.6.5 ✅ Keycloak broker validation —
2.3 ISP returns the achieved acr as a TDIF URN Sch 2 §1.8.6 ✅ Auth0 Action: the IP level from Atlantis ID's identity verification (app_metadata.tdif_ip: IP1, IP1 Plus or IP2, see section 7) with passkey → CL2, password → CL1 (claim tdif_acr). IP2 has no CL1 level, so a password sign-in to an IP2 identity asserts ip1p:cl1. Poseidon fixed ip1:cl1 Atlantis ID: an Auth0 Action sets the level from the sign-in method (passkey → ip1:cl2, password → ip1:cl1). Poseidon: fixed ip1:cl1. The exchange imports it and passes it on.
2.4 ISP audit identifier in state, distinct from the RP audit ID; ISP logs requests and responses with client_id and state Sch 2 §2.6.1, §2.7.1 ✅ Keycloak uses its own state per broker login; Auth0 tenant logs and Poseidon events are both collected —
2.5 ISP should issue pairwise identifiers Sch 1 §1.2.3 ✅ Poseidon / ◐ Auth0 Poseidon issues pairwise sub. Auth0 can't change sub, so the Atlantis ID Action adds https://id-atlantis.xyz/pairwise_sub (HMAC of client and user id) and the exchange links accounts by that claim, not Auth0's global id Verified live with an Atlantis ID passkey sign-in. Auth0's own sub still appears in the token it sends the exchange; the exchange doesn't call Auth0's userinfo endpoint.
2.6 ID token lifetime ≤ 300 s Sch 2 §1.8.3.1 ✅ Auth0 apps 300 s; Poseidon 300 s —
2.7 Exchange retains no attributes beyond the transaction Sch 1 (role of the IXP) ✅ After the RP's token call the extension's listener removes the brokered user's names, email, username and attributes; a pseudonymous record keeps the provider link, pairwise subject and consent. The next sign-in refreshes attributes for that transaction only
2.8 Consent / notice when the IXP remembers the ISP choice Sch 1 §2.2 ✅ The exchange never remembers the choice —

3. Assurance levels

# Requirement Status Change
3.1 acr format urn:id.gov.au:tdif:acr:<ip>:<cl>; levels IP1–IP4 × CL1–CL3 (Sch 1 Table 1) ✅ Adopt the URN scheme. Demo levels: IP1 (self-asserted), IP1 Plus (passport chip) and IP2 (passport and Medicare card) with CL1 (password) or CL2 (passkey, phishing-resistant multi-factor). The exchange's acr.loa.map holds all 13 levels of Sch 1 Table 1.
3.2 Level flows ISP → IXP → RP unchanged; RP can request a level ✅ Auth0 Action and Poseidon mapper (2.3), Keycloak attribute import + acr protocol mapper replacing Keycloak's own, Agency A shows the level and sends acr_values.
3.3 Level shown to the citizen and on the dashboard ✅ Agency A account page and dashboard event detail.

4. Consent and attribute minimisation

# Requirement Status Change
4.1 Express consent before attributes reach the RP; consent recorded in audit logs (TDIF 04 PRIV-03-09) ✅ Item 1.11. Keycloak records GRANT_CONSENT; the collector already forwards it.
4.2 Only requested attributes released; standard claim names (given_name, family_name, birthdate, email) ✅ Scopes profile email; names match.
4.3 Citizen can see and revoke consent (user dashboard, Sch 1 §2.3) ✅ Agency A links to the exchange's account console, which lists the consent and removes it; the next sign-in asks again. REVOKE_GRANT shows on the dashboard. The console still carries Keycloak's own branding.

5. Audit

# Requirement Status Change
5.1 Every participant logs authentication requests/responses, consent, attribute release, with timestamps ✅ The hub links every event to the one before with HMAC-SHA-256; /events/verify checks the chain and the dashboard shows its status; a reset starts a new chain that records the old head. Retention is still the demo's lifetime.
5.2 RP audit ID in ASP and exchange logs ✅ Item 1.12.
5.3 Logs never contain credentials or full attribute values ✅ Designed in from the start.

6. Outside the OIDC profile

The wallet credential flow (Atlantis ID → wallet → Agency B over OpenID4VCI/OpenID4VP) is not covered by Schedule 2. It should be presented as a complementary pattern, with its own privacy properties (selective disclosure, no exchange in the path), not as TDIF-conformant.

7. Identity proofing (Digital ID (Accreditation) Rules 2024)

Not part of the OIDC profile above, and not counted in it. Atlantis ID proofs identities with Atlantis Wallet reading an Australian passport over NFC.

# Requirement Rule Status How
7.1 Technical verification of an Australian passport: ICAO Doc 9303 passive authentication with remote PKI r 5.12(a), Sch 1 item 2, Sch 4 item 1 ✅ services/emrtd.ts: data-group hashes against the SOD, the SOD's CMS signature by the Document Signer, the DS certificate chained to one of DFAT's CSCA keys (2013, 2017, 2020, 2024, with link certificates; certs/au-csca). The wallet reads EF.SOD, DG1, DG14 and DG15, never DG2 (the photo)
7.2 Check DFAT's certificate revocation list r 5.12(b) ✅ Atlantis ID fetches the CRL from passports.gov.au at start-up and every 12 hours, checks DFAT's signature on it, and refuses a revoked Document Signer or an out-of-date list
7.3 Chip is genuine, not a copy Doc 9303 Part 11 §6 ◐ Active authentication with Atlantis ID's own challenge, checked on the server when the chip has a DG15 key (RSA ISO 9796-2 or ECDSA). Chip authentication (DG14) runs on the phone and is reported, not checked by the server
7.4 Name and date of birth verified only by source or technical verification (IP1 Plus and above) r 5.10 item 9 ✅ The passport's MRZ replaces the self-asserted name and date of birth; they can't be edited while verified
7.5 One identity per document r 5.10 item 2 ✅ An HMAC of issuing state and document number binds the passport to one Atlantis ID
7.6 Passport current, or expired by no more than 3 years Sch 1 item 2, Sch 4 item 1 ✅ Checked from the MRZ
7.7 IP1 Plus: one photo ID r 5.10 item 11 ✅ The verified passport
7.8 IP2: a commencement of identity credential and a UitC credential, linking credential if names differ r 5.10 items 10, 12, 13 ◐ Passport as the CoI; a Medicare card as the UitC with the name compared against the passport. The Medicare check is simulated: there is no Document Verification Service connection, so only the number's check digit is tested
7.9 Fraud and deceased checks r 5.10 items 3, 6 ❌ Not in the demo