From 8d74c11cd5e4c75fb82808dca1016ce331697929 Mon Sep 17 00:00:00 2001 From: Daniil Fadeev Date: Tue, 4 Aug 2026 16:51:35 +0300 Subject: [PATCH] fix: restore API TLS verification --- QonversionTests/APIClientTests.m | 23 +++++++++++++++++++ .../NetworkProvider/NetworkProvider.swift | 9 ++------ .../Services/QNAPIClient/QNAPIClient.m | 11 ++++----- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/QonversionTests/APIClientTests.m b/QonversionTests/APIClientTests.m index 47ca7f3a..1faab1f2 100644 --- a/QonversionTests/APIClientTests.m +++ b/QonversionTests/APIClientTests.m @@ -137,4 +137,27 @@ - (void)testThatClientDetectBrokenData { [self waitForExpectationsWithTimeout:keyQNTestTimeout handler:nil]; } +- (void)testThatServerTrustUsesSystemValidation { + NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] + initWithHost:@"attacker.invalid" + port:443 + protocol:NSURLProtectionSpaceHTTPS + realm:nil + authenticationMethod:NSURLAuthenticationMethodServerTrust]; + id challenge = OCMClassMock([NSURLAuthenticationChallenge class]); + OCMStub([challenge protectionSpace]).andReturn(protectionSpace); + + __block NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengeUseCredential; + __block NSURLCredential *credential = [NSURLCredential credentialWithUser:@"unexpected" password:@"unexpected" persistence:NSURLCredentialPersistenceNone]; + + [self.client URLSession:nil didReceiveChallenge:challenge completionHandler:^(NSURLSessionAuthChallengeDisposition receivedDisposition, NSURLCredential *receivedCredential) { + disposition = receivedDisposition; + credential = receivedCredential; + }]; + + XCTAssertEqual(disposition, NSURLSessionAuthChallengePerformDefaultHandling); + XCTAssertNil(credential); + [challenge stopMocking]; +} + @end diff --git a/Sources/NoCodes/NetworkLayer/NetworkProvider/NetworkProvider.swift b/Sources/NoCodes/NetworkLayer/NetworkProvider/NetworkProvider.swift index 5994038a..eec80651 100644 --- a/Sources/NoCodes/NetworkLayer/NetworkProvider/NetworkProvider.swift +++ b/Sources/NoCodes/NetworkLayer/NetworkProvider/NetworkProvider.swift @@ -41,14 +41,9 @@ class NetworkProvider: NSObject, NetworkProviderInterface, URLSessionDelegate { } } - // MARK: - Temporary SSL bypass for staging. Remove before release. func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, - let serverTrust = challenge.protectionSpace.serverTrust { - completionHandler(.useCredential, URLCredential(trust: serverTrust)) - } else { - completionHandler(.performDefaultHandling, nil) - } + // Keep system trust-chain and hostname validation for every endpoint. + completionHandler(.performDefaultHandling, nil) } } diff --git a/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.m b/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.m index 064922f9..3db8c1ff 100644 --- a/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.m +++ b/Sources/Qonversion/Qonversion/Services/QNAPIClient/QNAPIClient.m @@ -678,14 +678,11 @@ - (void)storeRequestIfNeeded:(NSURLRequest *)request { } } -// MARK: - Temporary SSL bypass for staging. Remove before release. - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler { - if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { - NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; - completionHandler(NSURLSessionAuthChallengeUseCredential, credential); - } else { - completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); - } + // Never manufacture a credential from an unverified SecTrust. Delegating to + // URLSession preserves the platform trust chain and hostname checks for the + // default API as well as customer-provided HTTPS proxy URLs. + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } @end