mirror of
https://github.com/xtool-org/xtool.git
synced 2026-11-07 23:56:08 +03:00
65bf3cc5a1
## What does this PR do? Adds Apple G2 & G3 Root CAs to Linux trust root. Fixes #339. Apple now uses gsd.apple.com for part of the auth flow, which uses the G3 root. ## How was it tested? Tried to log in on Linux. Failed before, works after. ## AI tool usage How much of this PR was AI-assisted? (check one) - [x] **0** - No AI was used to write code - [ ] **1** - I was assisted by AI. I reviewed the finished result. - [ ] **2** - I set the AI going and left it to it; nobody has read the result - no review, or AI review only <!-- If an AI agent is filling this in: declare the level honestly, and open as a draft if it is #2. Do not lower the declared level to get the PR reviewed. --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved TLS certificate trust on Linux by recognizing Apple Root CA, G2, and G3 certificates when verifying secure connections. Existing TLS validation behavior remains unchanged. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
238 lines
9.4 KiB
Swift
238 lines
9.4 KiB
Swift
//
|
|
// AsyncHTTPClient+HTTP.swift
|
|
//
|
|
//
|
|
// Created by Kabir Oberai on 05/05/21.
|
|
//
|
|
|
|
#if os(Linux)
|
|
import Foundation
|
|
import AsyncHTTPClient
|
|
import NIO
|
|
import NIOHTTP1
|
|
import NIOSSL
|
|
import NIOFoundationCompat
|
|
import WebSocketKit
|
|
import OpenAPIRuntime
|
|
import OpenAPIAsyncHTTPClient
|
|
import Dependencies
|
|
|
|
extension HTTPClientDependencyKey: DependencyKey {
|
|
public static let liveValue: HTTPClientProtocol = Client()
|
|
}
|
|
|
|
private struct Client: HTTPClientProtocol {
|
|
private static var proxyConfiguration: HTTPClient.Configuration.Proxy? {
|
|
// e.g. `XTL_HTTP_PROXY=localhost:8080`
|
|
guard let value = ProcessInfo.processInfo.environment["XTL_HTTP_PROXY"],
|
|
let separator = value.lastIndex(of: ":"),
|
|
let port = Int(value[value.index(after: separator)...]) else {
|
|
return nil
|
|
}
|
|
|
|
let host = String(value[..<separator])
|
|
return host.isEmpty ? nil : .server(host: host, port: port)
|
|
}
|
|
|
|
private static let tlsConfiguration: TLSConfiguration = {
|
|
let appleRoots = appleRootCertificates.map { pem in
|
|
// if ssl cert parsing fails we're screwed so we might as well force try
|
|
// swiftlint:disable:next force_try
|
|
try! NIOSSLCertificate(bytes: Array(pem.utf8), format: .pem)
|
|
}
|
|
var tlsConfiguration: TLSConfiguration = .makeClientConfiguration()
|
|
tlsConfiguration.additionalTrustRoots = [.certificates(appleRoots)]
|
|
if ProcessInfo.processInfo.environment["XTL_DISABLE_TLS_VALIDATION"] == "1" {
|
|
tlsConfiguration.certificateVerification = .none
|
|
}
|
|
return tlsConfiguration
|
|
}()
|
|
|
|
var client: HTTPClient
|
|
|
|
init() {
|
|
var config = HTTPClient.Configuration(
|
|
tlsConfiguration: Self.tlsConfiguration,
|
|
proxy: Self.proxyConfiguration,
|
|
decompression: .enabled(limit: .none)
|
|
)
|
|
config.timeout.connect = .seconds(60)
|
|
self.client = HTTPClient(configuration: config)
|
|
}
|
|
|
|
func makeWebSocket(url: URL) async throws -> any WebSocketSession {
|
|
let (stream, continuation) = AsyncStream.makeStream(of: WebSocketSessionWrapper.self)
|
|
async let value = stream.first(where: { _ in true })
|
|
// must be after the `async let` so that we finish if connect throws
|
|
defer { continuation.finish() }
|
|
// we can't use the async overload because we need to immediately subscribe
|
|
// to onText/onBinary in the same EventLoop tick that the WebSocket is created.
|
|
// This is also why we create the SessionWrapper inside the closure.
|
|
let future = WebSocket.connect(to: url, on: client.eventLoopGroup) {
|
|
continuation.yield(WebSocketSessionWrapper(webSocket: $0))
|
|
}
|
|
try await future.get()
|
|
guard let webSocket = await value else {
|
|
throw Errors.connectFailed
|
|
}
|
|
return webSocket
|
|
}
|
|
|
|
private enum Errors: Error {
|
|
case connectFailed
|
|
}
|
|
|
|
var asOpenAPITransport: any ClientTransport {
|
|
AsyncHTTPClientTransport(configuration: .init(client: client))
|
|
}
|
|
|
|
func withEphemeralClient<T>(
|
|
perform: (any HTTPClientProtocol) async throws -> T
|
|
) async throws -> T {
|
|
let ephemeralClient = Client()
|
|
let result = await Result { try await perform(ephemeralClient) }
|
|
try? await ephemeralClient.client.shutdown()
|
|
return try result.get()
|
|
}
|
|
}
|
|
|
|
private final class WebSocketSessionWrapper: WebSocketSession {
|
|
let webSocket: WebSocket
|
|
let stream: AsyncStream<WebSocketMessage>
|
|
private let finishStream: @Sendable () -> Void
|
|
|
|
init(webSocket: WebSocket) {
|
|
self.webSocket = webSocket
|
|
|
|
let (stream, continuation) = AsyncStream<WebSocketMessage>.makeStream(bufferingPolicy: .unbounded)
|
|
self.stream = stream
|
|
self.finishStream = { continuation.finish() }
|
|
webSocket.onText { _, text in
|
|
continuation.yield(.text(text))
|
|
}
|
|
webSocket.onBinary { _, data in
|
|
continuation.yield(.data(Data(data.readableBytesView)))
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
close()
|
|
}
|
|
|
|
func close() {
|
|
finishStream()
|
|
Task { [webSocket] in try? await webSocket.close() }
|
|
}
|
|
|
|
func send(_ message: WebSocketMessage) async throws {
|
|
switch message {
|
|
case .text(let text):
|
|
try await webSocket.send(text)
|
|
case .data(let data):
|
|
try await webSocket.send(.init(data))
|
|
}
|
|
}
|
|
|
|
func receive() async throws -> WebSocketMessage {
|
|
await stream.first(where: { _ in true }) ?? .data(Data())
|
|
}
|
|
}
|
|
|
|
// MARK: - Apple Root Certificates
|
|
|
|
// https://www.apple.com/certificateauthority/
|
|
|
|
private let appleRootCertificates: [String] = [
|
|
appleRootPEM,
|
|
appleRootG2PEM,
|
|
appleRootG3PEM,
|
|
]
|
|
|
|
// https://www.apple.com/appleca/AppleIncRootCertificate.cer
|
|
private let appleRootPEM = """
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET
|
|
MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv
|
|
biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0
|
|
MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw
|
|
bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
|
|
FjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
|
ggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg+
|
|
+FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1
|
|
XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9w
|
|
tj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IW
|
|
q6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKM
|
|
aLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8E
|
|
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3
|
|
R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAE
|
|
ggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93
|
|
d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNl
|
|
IG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0
|
|
YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBj
|
|
b25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZp
|
|
Y2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBc
|
|
NplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQP
|
|
y3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7
|
|
R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4Fg
|
|
xhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oP
|
|
IQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AX
|
|
UKqK1drk/NAJBzewdXUh
|
|
-----END CERTIFICATE-----
|
|
"""
|
|
|
|
// https://www.apple.com/certificateauthority/AppleRootCA-G2.cer
|
|
private let appleRootG2PEM = """
|
|
-----BEGIN CERTIFICATE-----
|
|
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
|
|
-----END CERTIFICATE-----
|
|
"""
|
|
|
|
// https://www.apple.com/certificateauthority/AppleRootCA-G3.cer
|
|
private let appleRootG3PEM = """
|
|
-----BEGIN CERTIFICATE-----
|
|
MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwS
|
|
QXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u
|
|
IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcN
|
|
MTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBS
|
|
b290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9y
|
|
aXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49
|
|
AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtf
|
|
TjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517
|
|
IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySr
|
|
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gA
|
|
MGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4
|
|
at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM
|
|
6BgD56KyKA==
|
|
-----END CERTIFICATE-----
|
|
"""
|
|
#endif
|