From 5b41b3924a38068e5806e7610cbb9b772a7aebcf Mon Sep 17 00:00:00 2001 From: Shane Maynard Date: Thu, 1 Oct 2026 18:09:25 -0400 Subject: [PATCH] fix: remove conflicting ATS CA pin and verify physical LiveKit trust --- Resources/Info.plist | 15 --------------- Tests/TLSLiveTests.swift | 15 +++++++++++++++ docs/ENGINEERING_LEARNINGS.md | 4 ++++ docs/PROJECT_STATUS.md | 15 +++++++++++++-- project.yml | 5 ----- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/Resources/Info.plist b/Resources/Info.plist index 9c4a6de..93b0c77 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -24,21 +24,6 @@ NSAllowsLocalNetworking - NSPinnedDomains - - 192.168.86.2 - - NSIncludesSubdomains - - NSPinnedCAIdentities - - - SPKI-SHA256-BASE64 - vmrfRuHv1oHBBiEmJP4WX3C7mtVy3C4+UJ1Yk/Q5hMU= - - - - NSMicrophoneUsageDescription Hope Voice uses the microphone for live voice conversation. diff --git a/Tests/TLSLiveTests.swift b/Tests/TLSLiveTests.swift index d74b734..4891b45 100644 --- a/Tests/TLSLiveTests.swift +++ b/Tests/TLSLiveTests.swift @@ -1,4 +1,5 @@ import Foundation +import LiveKit import Testing @testable import Voice @@ -15,6 +16,14 @@ struct TLSLiveTests { #expect(!data.isEmpty) } + @Test func systemTrustedLANHTTPS() async throws { + guard ProcessInfo.processInfo.environment["HOPE_TLS_LIVE"] == "1" else { return } + print("HOPE_TLS_PROBE system trust validation started") + let (_, response) = try await URLSession.shared.data(from: URL(string: "https://192.168.86.2:9178/api/auth/me")!) + #expect((response as? HTTPURLResponse)?.statusCode == 401) + print("HOPE_TLS_PROBE system trust validation completed") + } + @Test func nativeAccountLogin() async throws { let env = ProcessInfo.processInfo.environment guard env["HOPE_TLS_LIVE"] == "1", let username = env["HOPE_TEST_USERNAME"], @@ -25,6 +34,12 @@ struct TLSLiveTests { let login = try await client.login(username: username, password: password) #expect(login.user.username == username) #expect(try await client.me(token: login.token).id == login.user.id) + let bootstrap = try await TokenClient(config: config, bearerToken: login.token).fetchToken() + let room = Room() + try await room.connect(url: config.livekitURL.absoluteString, token: bootstrap.token) + #expect(room.connectionState == .connected) + await room.disconnect() + print("HOPE_TLS_PROBE authenticated LiveKit WSS connected and disconnected; microphone not enabled") try await client.logout(token: login.token) print("HOPE_TLS_PROBE native login/me/logout completed") } diff --git a/docs/ENGINEERING_LEARNINGS.md b/docs/ENGINEERING_LEARNINGS.md index 7e771f8..4b41dd9 100644 --- a/docs/ENGINEERING_LEARNINGS.md +++ b/docs/ENGINEERING_LEARNINGS.md @@ -38,6 +38,10 @@ Read [PROJECT_STATUS.md](PROJECT_STATUS.md) for the current product scope. - Gate live TLS tests with explicit opt-in and print started/skipped scope; a test returning early is not runtime proof. Regenerate XcodeGen for added test membership. Physical test bundles require a development team override, not just app signing. +- Proven device ATS trap: `NSPinnedCAIdentities` can still reject a manually + full-trusted self-signed server with -1202/-9807. Same-phone A/B removal of this + redundant rule made system HTTPS and actual LiveKit WSS pass. Retain exact app + HTTP pin + normal SDK system trust; do not infer validity-length cause from dates. - Certificate profiles require public-only verified data, user installation/full trust, and persistent hosting. Copying a file into an active container is erased at redeploy. Exact nginx alias may expose a public profile from cert mount; never diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md index 0fed076..60423f1 100644 --- a/docs/PROJECT_STATUS.md +++ b/docs/PROJECT_STATUS.md @@ -104,8 +104,19 @@ unit suite:58 passed, including two explicit-opt-in live TLS cases skipped in of run. Actual opted-in physical HTTPS test passed separately. New live tests use environment-only credentials, never fixtures/source/argv. Updated signed phone build passed (`/tmp/hope-square-phone-build.log`) and app installed/launch succeeded -on the iPhone14ProMax (`dev.shane.voice`, 18:00 local). Real WSS/login after profile trust and -physical speech remain user qualification. +on the iPhone14ProMax (`dev.shane.voice`, 18:00 local). Physical speech remains user qualification. + +Follow-up: user installed profile/full trust but SDK still reported validation cert +invalid. Reproduced physically: exact pinned HTTPS passed; system URLSession HTTPS +failed -1202/-9807. Controlled A/B removed redundant `NSPinnedCAIdentities` in +XcodeGen (normal iOS trust, HTTPS/WSS and exact HTTP leaf pin remain unchanged). +System trust then passed on same phone, establishing ATS CA-only rule conflict, +not proven certificate validity-length rejection. Final physical native live test +**3 passed**: pinned HTTPS, system-trusted HTTPS, and admin login/me/bootstrap → +actual LiveKit SDK authenticated WSS connect/disconnect → logout; no microphone +enabled. Evidence `/tmp/hope-phone-wss-qualified.log`. John Doe remains deleted. +Signed corrected build passed `/tmp/hope-square-tls-fixed-build.log`. + ## Current direction diff --git a/project.yml b/project.yml index e805d01..ba411d7 100644 --- a/project.yml +++ b/project.yml @@ -38,11 +38,6 @@ targets: NSMicrophoneUsageDescription: "Hope Voice uses the microphone for live voice conversation." NSAppTransportSecurity: NSAllowsLocalNetworking: true - NSPinnedDomains: - 192.168.86.2: - NSIncludesSubdomains: true - NSPinnedCAIdentities: - - SPKI-SHA256-BASE64: vmrfRuHv1oHBBiEmJP4WX3C7mtVy3C4+UJ1Yk/Q5hMU= settings: base: PRODUCT_BUNDLE_IDENTIFIER: dev.shane.voice