Skip to content

QR enrollment: scan a TAK/ATAK enrollment QR (tak://...enroll)#101

Merged
jfuginay merged 2 commits into
mainfrom
feat/qr-enrollment
Jun 15, 2026
Merged

QR enrollment: scan a TAK/ATAK enrollment QR (tak://...enroll)#101
jfuginay merged 2 commits into
mainfrom
feat/qr-enrollment

Conversation

@jfuginay

Copy link
Copy Markdown
Contributor

Closes #100.

Onboard OmniTAK to a standard TAK Server (ArgusTAK / ATAK / TAK Server) by scanning its enrollment QR. Before this, the deep-link handler only accepted atak://…connect?host= / omnitak://server?… (and explicitly rejected other schemes), and the only in-app QR scanner was for config-profile sync — there was no path to enroll against a standard server QR.

The QR format

The de-facto ATAK enrollment deep link:

tak://com.atakmap.app/enroll?host=<host>[:port]&username=<u>&token=<t>

host is frequently URL-encoded (e.g. host=argustak.com%3A8089). username + token are one-time CSR-enrollment credentials.

Changes per area

AndroidManifest.xml — register the tak URL scheme (VIEW + BROWSABLE) alongside the existing atak / omnitak, so a QR scanned with the OS camera app opens OmniTAK.

data/DeepLinkImport.kt — added isEnrollLink + parseEnrollLink, distinct from the existing connect-form parser:

  • splits the URL-decoded host:port → connection host + connection port (default 8089),
  • maps token → the CSR Basic-auth secret (carried in ImportedServerConfig.password, which CSREnrollmentService sends as HTTP Basic auth),
  • derives a separate enrollment port (default 8446 — TAK Server enrolls on a different port than it streams on), overridable via enrollport= / enrollmentport=,
  • splitHostPort strips a malformed/out-of-range port from the host rather than poisoning DNS; handles IPv4 and bracketed IPv6.

MainActivity.kt — route enroll deep links to the existing CSREnrollmentService (checked before the connect-form matcher, so an atak://…/enroll link enrolls instead of being added cert-less and rejected at the mTLS handshake). Mirrors EnrollServerScreen's success path: add the TAKServer with the signed .p12 + pinned CA, auto-connect.

ui/screens/ServerQrScanScreen.kt (new) — full-screen CameraX/MLKit scanner, lifting the proven config-profile scanner pipeline into a reusable, parameterized component (generic accept predicate + onScanned). ServerEnrollScanRoute pairs it with the enroll/connect flow. Full-screen route, never a map-compressing panel; CAMERA permission handled in-screen.

ui/screens/ServersScreen.kt + ui/navigation/AppNav.kt — add a "Scan QR" action (top bar) → servers/scan route → ServerEnrollScanRoute. This is the reliable on-ramp that does not depend on the OS camera app routing the tak:// scheme back to us.

test/…/DeepLinkEnrollTest.kt (new) — covers splitHostPort (host:port, bare host, trailing colon, non-numeric / out-of-range port, IPv4, bracketed IPv6) and the enroll-link detect/parse. The Uri-based cases use assumeTrue to skip on the JVM Uri.parse stub (no Robolectric on the classpath) — same convention as ConfigProfileTest.

Verify

./gradlew assembleDebugBUILD SUCCESSFUL. :app:testDebugUnitTest --tests DeepLinkEnrollTest green (host:port tests run; Uri tests skip on JVM stub).

Assumptions to verify on device (ArgusTAK)

  • Enroll endpoint port/path: defaults to 8446 with /Marti/api/tls/config + /Marti/api/tls/signClient/v2 (existing CSREnrollmentService). ArgusTAK may colocate enrollment on the 8089 connection port or another port — if so, the QR needs enrollport= (now supported) or ArgusTAK's actual port confirmed.
  • QR param names: assumed host / username / token (with password tolerated as a token fallback). Confirm ArgusTAK's QR uses exactly these.
  • trust-on-first-use: enrollment defaults to trust-all (so one QR works for both self-signed self-hosted and Let's-Encrypt'd ArgusTAK); override with trust=ca on the link for strict validation.
  • OS-camera deep link vs in-app scanner: both are wired. The in-app scanner (Servers → Scan QR) is the reliable path; the tak:// OS-camera deep link depends on no other installed app claiming the scheme. Recommend leading with the in-app scanner in docs/onboarding.

Note: a separate, unrelated in-progress "Import Data Package (.zip)" change exists in the working tree on another branch and is intentionally not part of this PR.

jfuginay added 2 commits June 14, 2026 16:51
…..enroll)

Onboard OmniTAK to a standard TAK Server (ArgusTAK / ATAK / TAK Server)
by scanning its enrollment QR. Closes #100.

The QR format is the de-facto ATAK enrollment deep link:
  tak://com.atakmap.app/enroll?host=<host>[:port]&username=<u>&token=<t>
host is frequently URL-encoded (host=argustak.com%3A8089); username/token
are one-time CSR-enrollment credentials.

AndroidManifest: register the `tak` scheme (VIEW + BROWSABLE) alongside the
existing atak/omnitak so a QR scanned with the OS camera app opens OmniTAK.

DeepLinkImport: add isEnrollLink + parseEnrollLink, distinct from the
existing connect form. Splits the URL-decoded host:port (connection port,
default 8089), maps token -> the CSR Basic-auth secret, and derives a
SEPARATE enrollment port (default 8446, overridable via enrollport=) since
TAK Server enrolls on a different port than it streams on. A malformed port
after the colon is stripped from the host rather than poisoning DNS.

MainActivity: route enroll deep links to the existing CSR enrollment service
(checked before the connect-form matcher so an atak://.../enroll link
enrolls rather than being added cert-less), mirroring EnrollServerScreen's
success path — add the TAKServer with the signed .p12 + pinned CA, auto-connect.

ServersScreen: add a "Scan QR" action that opens a full-screen CameraX/MLKit
scanner (the proven config-profile scanner pipeline, lifted into a reusable
ServerQrScanScreen), decodes the enroll/connect link, and runs the same
enroll/connect flow. CAMERA permission handled in-screen. Full-screen route,
never a map-compressing panel.

Tests: DeepLinkEnrollTest covers the host:port split (incl. IPv6, malformed
ports) and the enroll-link detect/parse (Uri-based cases skip on the JVM
stub, matching ConfigProfileTest's convention).
The tak://...enroll deep-link path built the TAKServer without caCertificateName,
so the connection fell back to the system trust store and failed CertPathValidator
against a private-CA server (ArgusTAK: 'Trust anchor for certification path not found').
Set caCertificateName from the enrollment result, matching EnrollServerScreen and
ServerQrScanScreen. Verified on-device: now logs 'TLS trust: pinned CA(s)' and
'Connected to argustak.com:8089 (tls=true)'.
@jfuginay jfuginay merged commit 2b7ee49 into main Jun 15, 2026
1 check passed
@jfuginay jfuginay deleted the feat/qr-enrollment branch June 15, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QR enrollment: onboard from standard TAK/ATAK enrollment QR (tak://...enroll)

1 participant