Allow pinned self-signed TLS for private IPv4 ranges on iOS 17+
This commit is contained in:
@@ -156,8 +156,8 @@
|
|||||||
"CODE_SIGN_STYLE" = "Automatic";
|
"CODE_SIGN_STYLE" = "Automatic";
|
||||||
"GENERATE_INFOPLIST_FILE" = "NO";
|
"GENERATE_INFOPLIST_FILE" = "NO";
|
||||||
"INFOPLIST_FILE" = "MusicBridge/Info.plist";
|
"INFOPLIST_FILE" = "MusicBridge/Info.plist";
|
||||||
"CURRENT_PROJECT_VERSION" = "1";
|
"CURRENT_PROJECT_VERSION" = "2";
|
||||||
"MARKETING_VERSION" = "0.1.0";
|
"MARKETING_VERSION" = "0.1.1";
|
||||||
"SWIFT_OPTIMIZATION_LEVEL" = "-Onone";
|
"SWIFT_OPTIMIZATION_LEVEL" = "-Onone";
|
||||||
"ENABLE_TESTABILITY" = "YES";
|
"ENABLE_TESTABILITY" = "YES";
|
||||||
"SWIFT_ACTIVE_COMPILATION_CONDITIONS" = "DEBUG";
|
"SWIFT_ACTIVE_COMPILATION_CONDITIONS" = "DEBUG";
|
||||||
@@ -177,8 +177,8 @@
|
|||||||
"CODE_SIGN_STYLE" = "Automatic";
|
"CODE_SIGN_STYLE" = "Automatic";
|
||||||
"GENERATE_INFOPLIST_FILE" = "NO";
|
"GENERATE_INFOPLIST_FILE" = "NO";
|
||||||
"INFOPLIST_FILE" = "MusicBridge/Info.plist";
|
"INFOPLIST_FILE" = "MusicBridge/Info.plist";
|
||||||
"CURRENT_PROJECT_VERSION" = "1";
|
"CURRENT_PROJECT_VERSION" = "2";
|
||||||
"MARKETING_VERSION" = "0.1.0";
|
"MARKETING_VERSION" = "0.1.1";
|
||||||
"SWIFT_OPTIMIZATION_LEVEL" = "-O";
|
"SWIFT_OPTIMIZATION_LEVEL" = "-O";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,6 +34,24 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsLocalNetworking</key>
|
<key>NSAllowsLocalNetworking</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSExceptionDomains</key>
|
||||||
|
<dict>
|
||||||
|
<key>10.0.0.0/8</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>172.16.0.0/12</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>192.168.0.0/16</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# MusicBridge iOS — план
|
# MusicBridge iOS — план
|
||||||
|
|
||||||
Обновлено: 2026-09-10. Пользователь разрешил начать нативное iOS-приложение.
|
Обновлено: 2026-09-12. Пользователь разрешил начать нативное iOS-приложение.
|
||||||
Репозиторий: `C:/Users/areso/iOS MusicBridge`; origin — dev.yukinoki.ru/musicbridge/ios.
|
Репозиторий: `C:/Users/areso/iOS MusicBridge`; origin — dev.yukinoki.ru/musicbridge/ios.
|
||||||
Windows-агент — соседний проект `C:/Users/areso/MusicBridge/MusicBridge.Agent`.
|
Windows-агент — соседний проект `C:/Users/areso/MusicBridge/MusicBridge.Agent`.
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ Windows-агент — соседний проект `C:/Users/areso/MusicBridge
|
|||||||
- [x] Подключён GitHub remote polskikh13/musicbridgeios; Actions и чтение логов через gh работают.
|
- [x] Подключён GitHub remote polskikh13/musicbridgeios; Actions и чтение логов через gh работают.
|
||||||
- [ ] Интеграционные тесты TLS: верный/чужой/истёкший сертификат, redirect, WSS.
|
- [ ] Интеграционные тесты TLS: верный/чужой/истёкший сертификат, redirect, WSS.
|
||||||
- [ ] Проверить 401 и отзыв устройства, Keychain после переустановки/блокировки.
|
- [ ] Проверить 401 и отзыв устройства, Keychain после переустановки/блокировки.
|
||||||
- [ ] Настроить подпись и установку на физический iPhone.
|
- [x] Установить на физический iPhone: пользователь подтвердил установку через iloader 2026-09-12.
|
||||||
- [ ] Проверить LAN/плеер/команды/обложку с настоящим Windows-агентом.
|
- [ ] Проверить LAN/плеер/команды/обложку с настоящим Windows-агентом.
|
||||||
- [ ] Камера для QR, системные разрешения и обработка отказа.
|
- [ ] Камера для QR, системные разрешения и обработка отказа.
|
||||||
- [ ] Bonjour-поиск `_musicbridge._tcp`, список ПК и обновление адреса при смене IP.
|
- [ ] Bonjour-поиск `_musicbridge._tcp`, список ПК и обновление адреса при смене IP.
|
||||||
@@ -45,6 +45,24 @@ Windows-агент — соседний проект `C:/Users/areso/MusicBridge
|
|||||||
Потеря ответа на успешное сопряжение может оставить устройство на ПК: удалить его
|
Потеря ответа на успешное сопряжение может оставить устройство на ПК: удалить его
|
||||||
в окне агента перед повтором. Облачный runner не имеет доступа к домашней LAN.
|
в окне агента перед повтором. Облачный 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`: контракт и проверка входных данных.
|
- `MusicBridge/Models.swift`: контракт и проверка входных данных.
|
||||||
|
|||||||
@@ -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")
|
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)]
|
deps = [add("test-dependency", "PBXTargetDependency", target=uid("MusicBridgetarget"), targetProxy=proxy)]
|
||||||
else:
|
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,
|
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",
|
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))
|
buildConfigurationList=configurations(name, settings), buildPhases=phases, buildRules=[], dependencies=deps))
|
||||||
@@ -106,6 +106,12 @@ info = dict(CFBundleDevelopmentRegion="ru", CFBundleDisplayName="MusicBridge", C
|
|||||||
LSRequiresIPhoneOS=True, UILaunchScreen={},
|
LSRequiresIPhoneOS=True, UILaunchScreen={},
|
||||||
UISupportedInterfaceOrientations=["UIInterfaceOrientationPortrait"],
|
UISupportedInterfaceOrientations=["UIInterfaceOrientationPortrait"],
|
||||||
NSLocalNetworkUsageDescription="MusicBridge подключается к вашему ПК, чтобы показывать музыку и управлять воспроизведением.",
|
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))
|
(ROOT / "MusicBridge" / "Info.plist").write_bytes(plistlib.dumps(info, sort_keys=False))
|
||||||
print("Generated Xcode project, shared scheme and Info.plist.")
|
print("Generated Xcode project, shared scheme and Info.plist.")
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ referenced = set(re.findall(r'"([A-F0-9]{24})"', project))
|
|||||||
assert defined == referenced, "Project has dangling object references."
|
assert defined == referenced, "Project has dangling object references."
|
||||||
info = plistlib.loads(generated[2].read_bytes())
|
info = plistlib.loads(generated[2].read_bytes())
|
||||||
assert info["NSLocalNetworkUsageDescription"]
|
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])
|
ET.parse(generated[1])
|
||||||
fixture = json.loads((root / "MusicBridgeTests/state-v1.json").read_text(encoding="utf-8"))
|
fixture = json.loads((root / "MusicBridgeTests/state-v1.json").read_text(encoding="utf-8"))
|
||||||
assert fixture["protocolVersion"] == 1 and fixture["positionSeconds"] == 42.5
|
assert fixture["protocolVersion"] == 1 and fixture["positionSeconds"] == 42.5
|
||||||
|
|||||||
Reference in New Issue
Block a user