From a17a7f8027b6260ea0a0bfbc3ae4b4fea7f54172 Mon Sep 17 00:00:00 2001 From: ares Date: Sat, 12 Sep 2026 15:33:17 +0300 Subject: [PATCH] Allow pinned self-signed TLS for private IPv4 ranges on iOS 17+ --- MusicBridge.xcodeproj/project.pbxproj | 8 ++++---- MusicBridge/Info.plist | 18 ++++++++++++++++++ PLAN.md | 22 ++++++++++++++++++++-- tools/generate_project.py | 10 ++++++++-- tools/validate_project.py | 6 +++++- 5 files changed, 55 insertions(+), 9 deletions(-) diff --git a/MusicBridge.xcodeproj/project.pbxproj b/MusicBridge.xcodeproj/project.pbxproj index 1266a80..f825869 100644 --- a/MusicBridge.xcodeproj/project.pbxproj +++ b/MusicBridge.xcodeproj/project.pbxproj @@ -156,8 +156,8 @@ "CODE_SIGN_STYLE" = "Automatic"; "GENERATE_INFOPLIST_FILE" = "NO"; "INFOPLIST_FILE" = "MusicBridge/Info.plist"; - "CURRENT_PROJECT_VERSION" = "1"; - "MARKETING_VERSION" = "0.1.0"; + "CURRENT_PROJECT_VERSION" = "2"; + "MARKETING_VERSION" = "0.1.1"; "SWIFT_OPTIMIZATION_LEVEL" = "-Onone"; "ENABLE_TESTABILITY" = "YES"; "SWIFT_ACTIVE_COMPILATION_CONDITIONS" = "DEBUG"; @@ -177,8 +177,8 @@ "CODE_SIGN_STYLE" = "Automatic"; "GENERATE_INFOPLIST_FILE" = "NO"; "INFOPLIST_FILE" = "MusicBridge/Info.plist"; - "CURRENT_PROJECT_VERSION" = "1"; - "MARKETING_VERSION" = "0.1.0"; + "CURRENT_PROJECT_VERSION" = "2"; + "MARKETING_VERSION" = "0.1.1"; "SWIFT_OPTIMIZATION_LEVEL" = "-O"; }; }; diff --git a/MusicBridge/Info.plist b/MusicBridge/Info.plist index 4ef1dee..1a6357f 100644 --- a/MusicBridge/Info.plist +++ b/MusicBridge/Info.plist @@ -34,6 +34,24 @@ NSAllowsLocalNetworking + NSExceptionDomains + + 10.0.0.0/8 + + NSExceptionAllowsInsecureHTTPLoads + + + 172.16.0.0/12 + + NSExceptionAllowsInsecureHTTPLoads + + + 192.168.0.0/16 + + NSExceptionAllowsInsecureHTTPLoads + + + diff --git a/PLAN.md b/PLAN.md index 04ac770..85721d0 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,6 +1,6 @@ # MusicBridge iOS — план -Обновлено: 2026-09-10. Пользователь разрешил начать нативное iOS-приложение. +Обновлено: 2026-09-12. Пользователь разрешил начать нативное iOS-приложение. Репозиторий: `C:/Users/areso/iOS MusicBridge`; origin — dev.yukinoki.ru/musicbridge/ios. Windows-агент — соседний проект `C:/Users/areso/MusicBridge/MusicBridge.Agent`. @@ -32,7 +32,7 @@ Windows-агент — соседний проект `C:/Users/areso/MusicBridge - [x] Подключён GitHub remote polskikh13/musicbridgeios; Actions и чтение логов через gh работают. - [ ] Интеграционные тесты TLS: верный/чужой/истёкший сертификат, redirect, WSS. - [ ] Проверить 401 и отзыв устройства, Keychain после переустановки/блокировки. -- [ ] Настроить подпись и установку на физический iPhone. +- [x] Установить на физический iPhone: пользователь подтвердил установку через iloader 2026-09-12. - [ ] Проверить LAN/плеер/команды/обложку с настоящим Windows-агентом. - [ ] Камера для QR, системные разрешения и обработка отказа. - [ ] Bonjour-поиск `_musicbridge._tcp`, список ПК и обновление адреса при смене IP. @@ -45,6 +45,24 @@ Windows-агент — соседний проект `C:/Users/areso/MusicBridge Потеря ответа на успешное сопряжение может оставить устройство на ПК: удалить его в окне агента перед повтором. Облачный runner не имеет доступа к домашней LAN. +## Проверка на iPhone 2026-09-12 + +- Windows GUI запущен с `--tray --lan`, порт TCP 8765 слушает. +- На iPhone первая попытка сопряжения завершилась ошибкой недействительного + сертификата сервера. Установка приложения уже работает; это отдельная TLS-проблема. +- Подготовлены ATS-исключения только для RFC1918 CIDR: `10.0.0.0/8`, + `172.16.0.0/12`, `192.168.0.0/16`. Начиная с iOS 17, для IP нужны явные + исключения. Клиент продолжает требовать HTTPS, точный SHA-256 отпечаток, + действующий сертификат и запрещает redirect. Глобального отключения ATS нет. +- Добавлены настоящие HTTPS/WSS проверки в симуляторе с сертификатом того же + типа, что у агента: RSA 2048, самоподписанный, CA:FALSE, SAN localhost/127.0.0.1, + срок пять лет. Отдельно проверяются чужой отпечаток и истёкший сертификат. + `tools/tls_fixture.py` запускается в CI перед сборкой тестов; закрытые ключи + создаются временно на runner и не включаются в приложение или артефакты. +- Сообщение «непригодные данные» при импорте приглашения пока не разобрано: + пользователь попросил отложить его. Следующий ручной шаг — повторить сопряжение + после установки новой IPA, затем проверить команды, прогресс и обложку. + ## Карта - `MusicBridge/Models.swift`: контракт и проверка входных данных. diff --git a/tools/generate_project.py b/tools/generate_project.py index 44d46b1..770e903 100644 --- a/tools/generate_project.py +++ b/tools/generate_project.py @@ -61,7 +61,7 @@ for name, sources, is_test in [("MusicBridge", app_sources, False), ("MusicBridg proxy = add("test-proxy", "PBXContainerItemProxy", containerPortal=uid("project"), proxyType=1, remoteGlobalIDString=uid("MusicBridgetarget"), remoteInfo="MusicBridge") deps = [add("test-dependency", "PBXTargetDependency", target=uid("MusicBridgetarget"), targetProxy=proxy)] else: - settings.update(INFOPLIST_FILE="MusicBridge/Info.plist", CURRENT_PROJECT_VERSION="1", MARKETING_VERSION="0.1.0") + settings.update(INFOPLIST_FILE="MusicBridge/Info.plist", CURRENT_PROJECT_VERSION="2", MARKETING_VERSION="0.1.1") targets.append(add(name + "target", "PBXNativeTarget", name=name, productName=name, productReference=product, productType="com.apple.product-type.bundle.unit-test" if is_test else "com.apple.product-type.application", buildConfigurationList=configurations(name, settings), buildPhases=phases, buildRules=[], dependencies=deps)) @@ -106,6 +106,12 @@ info = dict(CFBundleDevelopmentRegion="ru", CFBundleDisplayName="MusicBridge", C LSRequiresIPhoneOS=True, UILaunchScreen={}, UISupportedInterfaceOrientations=["UIInterfaceOrientationPortrait"], NSLocalNetworkUsageDescription="MusicBridge подключается к вашему ПК, чтобы показывать музыку и управлять воспроизведением.", - NSAppTransportSecurity=dict(NSAllowsLocalNetworking=True)) + # iOS 17+ requires explicit IP exceptions for custom self-signed trust. + # BridgeClient still requires HTTPS, an exact SHA-256 pin and valid dates. + # Never disable ATS globally or include public IP ranges. + NSAppTransportSecurity=dict(NSAllowsLocalNetworking=True, NSExceptionDomains={ + network: dict(NSExceptionAllowsInsecureHTTPLoads=True) + for network in ("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16") + })) (ROOT / "MusicBridge" / "Info.plist").write_bytes(plistlib.dumps(info, sort_keys=False)) print("Generated Xcode project, shared scheme and Info.plist.") diff --git a/tools/validate_project.py b/tools/validate_project.py index ddb3939..9dc7f28 100644 --- a/tools/validate_project.py +++ b/tools/validate_project.py @@ -25,7 +25,11 @@ referenced = set(re.findall(r'"([A-F0-9]{24})"', project)) assert defined == referenced, "Project has dangling object references." info = plistlib.loads(generated[2].read_bytes()) assert info["NSLocalNetworkUsageDescription"] -assert info["NSAppTransportSecurity"] == {"NSAllowsLocalNetworking": True} +assert info["NSAppTransportSecurity"] == { + "NSAllowsLocalNetworking": True, + "NSExceptionDomains": {network: {"NSExceptionAllowsInsecureHTTPLoads": True} + for network in ("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")}, +} ET.parse(generated[1]) fixture = json.loads((root / "MusicBridgeTests/state-v1.json").read_text(encoding="utf-8")) assert fixture["protocolVersion"] == 1 and fixture["positionSeconds"] == 42.5