From 4f2ac910798d72bee9eb219beb000982bdc55c24 Mon Sep 17 00:00:00 2001 From: Soner YUKSEL Date: Fri, 3 Nov 2023 12:43:12 -0400 Subject: [PATCH] Fix brave/brave-ios#8203: Apple Search Ads Install Attribution (brave/brave-ios#8194) --- App/iOS/Delegates/AppDelegate.swift | 8 + App/iOS/Delegates/SceneDelegate.swift | 77 +++++-- Package.swift | 26 --- .../Extensions/AsyncAwait.swift | 10 +- .../Extensions/URLSessionExtensions.swift | 69 +++++- .../BraveCertificateUtils.swift | 1 + .../CertificatePinning.swift | 194 ----------------- .../Certificates/AmazonRootCA1.cer | Bin 837 -> 0 bytes .../Certificates/AmazonRootCA2.cer | Bin 1349 -> 0 bytes .../Certificates/AmazonRootCA3.cer | Bin 442 -> 0 bytes .../Certificates/AmazonRootCA4.cer | Bin 502 -> 0 bytes .../Certificates/GlobalSignRootCA_E46.cer | Bin 527 -> 0 bytes .../Certificates/GlobalSignRootCA_R1.cer | Bin 889 -> 0 bytes .../Certificates/GlobalSignRootCA_R3.cer | Bin 867 -> 0 bytes .../Certificates/GlobalSignRootCA_R46.cer | Bin 1374 -> 0 bytes .../Certificates/GlobalSignRootCA_R5.cer | Bin 546 -> 0 bytes .../Certificates/GlobalSignRootCA_R6.cer | Bin 1415 -> 0 bytes .../Certificates/ISRGRootCA_X1.cer | Bin 1391 -> 0 bytes .../Certificates/ISRGRootCA_X2.cer | Bin 543 -> 0 bytes .../Certificates/SFSRootCAG2.cer | Bin 1011 -> 0 bytes Sources/Growth/DAU.swift | 17 +- Sources/Growth/GrowthPreferences.swift | 1 + Sources/Growth/URP/AdAttributionData.swift | 70 ++++++ Sources/Growth/URP/UrpService.swift | 77 ++++++- Sources/Growth/URP/UserReferralProgram.swift | 32 ++- Sources/Shared/AppConstants.swift | 13 ++ .../CertificatePinningTest.swift | 204 ------------------ .../expired.badssl.com-intermediate-ca-1.cer | Bin 1400 -> 0 bytes .../expired.badssl.com-intermediate-ca-2.cer | Bin 1548 -> 0 bytes .../expired.badssl.com-leaf.cer | Bin 1359 -> 0 bytes .../expired.badssl.com-root-ca.cer | Bin 1082 -> 0 bytes .../self-signed.badssl.com.cer | Bin 893 -> 0 bytes .../untrusted.badssl.com-leaf.cer | Bin 1181 -> 0 bytes .../untrusted.badssl.com-root.cer | Bin 1666 -> 0 bytes .../Certificates/intermediate.cer | Bin 1334 -> 0 bytes .../Certificates/root.cer | Bin 969 -> 0 bytes Tests/GrowthTests/DAUTests.swift | 8 +- 37 files changed, 327 insertions(+), 480 deletions(-) rename Sources/{Brave => BraveShared}/Extensions/AsyncAwait.swift (94%) delete mode 100644 Sources/CertificateUtilities/CertificatePinning.swift delete mode 100644 Sources/CertificateUtilities/Certificates/AmazonRootCA1.cer delete mode 100644 Sources/CertificateUtilities/Certificates/AmazonRootCA2.cer delete mode 100644 Sources/CertificateUtilities/Certificates/AmazonRootCA3.cer delete mode 100644 Sources/CertificateUtilities/Certificates/AmazonRootCA4.cer delete mode 100644 Sources/CertificateUtilities/Certificates/GlobalSignRootCA_E46.cer delete mode 100644 Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R1.cer delete mode 100644 Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R3.cer delete mode 100644 Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R46.cer delete mode 100644 Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R5.cer delete mode 100644 Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R6.cer delete mode 100644 Sources/CertificateUtilities/Certificates/ISRGRootCA_X1.cer delete mode 100644 Sources/CertificateUtilities/Certificates/ISRGRootCA_X2.cer delete mode 100644 Sources/CertificateUtilities/Certificates/SFSRootCAG2.cer create mode 100644 Sources/Growth/URP/AdAttributionData.swift delete mode 100644 Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/expired.badssl.com-intermediate-ca-1.cer delete mode 100644 Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/expired.badssl.com-intermediate-ca-2.cer delete mode 100644 Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/expired.badssl.com-leaf.cer delete mode 100644 Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/expired.badssl.com-root-ca.cer delete mode 100644 Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/self-signed.badssl.com.cer delete mode 100644 Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/untrusted.badssl.com-leaf.cer delete mode 100644 Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/untrusted.badssl.com-root.cer delete mode 100644 Tests/CertificateUtilitiesTests/Certificates/intermediate.cer delete mode 100644 Tests/CertificateUtilitiesTests/Certificates/root.cer diff --git a/App/iOS/Delegates/AppDelegate.swift b/App/iOS/Delegates/AppDelegate.swift index 488e447f17f8..d4959f9e69ae 100644 --- a/App/iOS/Delegates/AppDelegate.swift +++ b/App/iOS/Delegates/AppDelegate.swift @@ -219,6 +219,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { UrpLog.log("Failed to initialize user referral program") } + if Preferences.URP.installAttributionLookupOutstanding.value == nil { + // Similarly to referral lookup, this prefrence should be set if it is a new user + // Trigger install attribution fetch only first launch + Preferences.URP.installAttributionLookupOutstanding.value = isFirstLaunch + + SceneDelegate.shouldHandleInstallAttributionFetch = true + } + #if canImport(BraveTalk) BraveTalkJitsiCoordinator.sendAppLifetimeEvent( .didFinishLaunching(options: launchOptions ?? [:]) diff --git a/App/iOS/Delegates/SceneDelegate.swift b/App/iOS/Delegates/SceneDelegate.swift index 0f8a04f3da22..e8d38da7b0cc 100644 --- a/App/iOS/Delegates/SceneDelegate.swift +++ b/App/iOS/Delegates/SceneDelegate.swift @@ -27,6 +27,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { internal var window: UIWindow? private var windowProtection: WindowProtection? static var shouldHandleUrpLookup = false + static var shouldHandleInstallAttributionFetch = false private var cancellables: Set = [] private let log = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "scene-delegate") @@ -80,14 +81,23 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } .store(in: &cancellables) + // Handle URP Lookup at first launch if SceneDelegate.shouldHandleUrpLookup { - // TODO: Find a better way to do this when multiple windows are involved. SceneDelegate.shouldHandleUrpLookup = false if let urp = UserReferralProgram.shared { browserViewController.handleReferralLookup(urp) } } + + // Handle Install Attribution Fetch at first launch + if SceneDelegate.shouldHandleInstallAttributionFetch { + SceneDelegate.shouldHandleInstallAttributionFetch = false + + if let urp = UserReferralProgram.shared { + browserViewController.handleSearchAdsInstallAttribution(urp) + } + } // Setup Playlist Car-Play // TODO: Decide what to do if we have multiple windows @@ -196,8 +206,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // We try to send DAU ping each time the app goes to foreground to work around network edge cases // (offline, bad connection etc.). - // Also send the ping only after the URP lookup has processed. - if Preferences.URP.referralLookupOutstanding.value == false { + // Also send the ping only after the URP lookup and install attribution has processed. + if Preferences.URP.referralLookupOutstanding.value == false, Preferences.URP.installAttributionLookupOutstanding.value == false { AppState.shared.dau.sendPingToServer() } @@ -531,29 +541,54 @@ extension SceneDelegate: UIViewControllerRestoration { extension BrowserViewController { func handleReferralLookup(_ urp: UserReferralProgram) { - if Preferences.URP.referralLookupOutstanding.value == true { - urp.referralLookup() { referralCode, offerUrl in - // Attempting to send ping after first urp lookup. - // This way we can grab the referral code if it exists, see issue #2586. - AppState.shared.dau.sendPingToServer() - if let code = referralCode { - let retryTime = AppConstants.buildChannel.isPublic ? 1.days : 10.minutes - let retryDeadline = Date() + retryTime - - Preferences.NewTabPage.superReferrerThemeRetryDeadline.value = retryDeadline - - // TODO: Set the code in core somehow if we want to support Super Referrals again - // then call updateSponsoredImageComponentIfNeeded - } - - guard let url = offerUrl?.asURL else { return } - self.openReferralLink(url: url) - } + performProgramReferralLookup(urp, refCode: UserReferralProgram.getReferralCode()) } else { urp.pingIfEnoughTimePassed() } } + + func handleSearchAdsInstallAttribution(_ urp: UserReferralProgram) { + urp.adCampaignLookup() { [weak self] response, error in + guard let self = self else { return } + + let refCode = self.generateReferralCode(attributionData: response, fetchError: error) + // Setting up referral code value + // This value should be set before first DAU ping + Preferences.URP.referralCode.value = refCode + Preferences.URP.installAttributionLookupOutstanding.value = false + } + } + + private func generateReferralCode(attributionData: AdAttributionData?, fetchError: Error?) -> String { + // Prefix code "001" with BRV for organic iOS installs + var referralCode = "BRV001" + + if fetchError == nil, attributionData?.attribution == true, let campaignId = attributionData?.campaignId { + // Adding ASA User refcode prefix to indicate + // Apple Ads Attribution is true + referralCode = "ASA\(String(campaignId))" + } + + return referralCode + } + + private func performProgramReferralLookup(_ urp: UserReferralProgram, refCode: String?) { + urp.referralLookup(refCode: refCode) { referralCode, offerUrl in + // Attempting to send ping after first urp lookup. + // This way we can grab the referral code if it exists, see issue #2586. + if Preferences.URP.installAttributionLookupOutstanding.value == false { + AppState.shared.dau.sendPingToServer() + } + let retryTime = AppConstants.buildChannel.isPublic ? 1.days : 10.minutes + let retryDeadline = Date() + retryTime + + Preferences.NewTabPage.superReferrerThemeRetryDeadline.value = retryDeadline + + guard let url = offerUrl?.asURL else { return } + self.openReferralLink(url: url) + } + } } extension UIWindowScene { diff --git a/Package.swift b/Package.swift index eb7cdc408a7b..201752d59c07 100644 --- a/Package.swift +++ b/Package.swift @@ -82,21 +82,6 @@ var package = Package( .target( name: "CertificateUtilities", dependencies: ["Shared"], - resources: [ - .copy("Certificates/AmazonRootCA1.cer"), - .copy("Certificates/AmazonRootCA2.cer"), - .copy("Certificates/AmazonRootCA3.cer"), - .copy("Certificates/AmazonRootCA4.cer"), - .copy("Certificates/GlobalSignRootCA_E46.cer"), - .copy("Certificates/GlobalSignRootCA_R1.cer"), - .copy("Certificates/GlobalSignRootCA_R3.cer"), - .copy("Certificates/GlobalSignRootCA_R46.cer"), - .copy("Certificates/GlobalSignRootCA_R5.cer"), - .copy("Certificates/GlobalSignRootCA_R6.cer"), - .copy("Certificates/ISRGRootCA_X1.cer"), - .copy("Certificates/ISRGRootCA_X2.cer"), - .copy("Certificates/SFSRootCAG2.cer"), - ], plugins: ["LoggerPlugin"] ), .testTarget( @@ -104,17 +89,6 @@ var package = Package( dependencies: ["CertificateUtilities", "BraveShared", "BraveCore", "MaterialComponents"], exclude: [ "Certificates/self-signed.conf" ], resources: [ - .copy("Certificates/root.cer"), - .copy("Certificates/leaf.cer"), - .copy("Certificates/intermediate.cer"), - .copy("Certificates/self-signed.cer"), - .copy("Certificates/expired.badssl.com/expired.badssl.com-intermediate-ca-1.cer"), - .copy("Certificates/expired.badssl.com/expired.badssl.com-intermediate-ca-2.cer"), - .copy("Certificates/expired.badssl.com/expired.badssl.com-leaf.cer"), - .copy("Certificates/expired.badssl.com/expired.badssl.com-root-ca.cer"), - .copy("Certificates/expired.badssl.com/self-signed.badssl.com.cer"), - .copy("Certificates/expired.badssl.com/untrusted.badssl.com-leaf.cer"), - .copy("Certificates/expired.badssl.com/untrusted.badssl.com-root.cer"), .copy("Certificates/certviewer/brave.com.cer"), .copy("Certificates/certviewer/github.com.cer"), ] diff --git a/Sources/Brave/Extensions/AsyncAwait.swift b/Sources/BraveShared/Extensions/AsyncAwait.swift similarity index 94% rename from Sources/Brave/Extensions/AsyncAwait.swift rename to Sources/BraveShared/Extensions/AsyncAwait.swift index 09f3214dd36d..3e17b9cdb4f9 100644 --- a/Sources/Brave/Extensions/AsyncAwait.swift +++ b/Sources/BraveShared/Extensions/AsyncAwait.swift @@ -5,7 +5,7 @@ import Foundation -extension Sequence { +public extension Sequence { func asyncForEach(_ operation: (Element) async throws -> Void) async rethrows { for element in self { try await operation(element) @@ -103,7 +103,7 @@ extension Sequence { } } -extension Task where Failure == Error { +public extension Task where Failure == Error { @discardableResult static func retry( priority: TaskPriority? = nil, @@ -129,7 +129,7 @@ extension Task where Failure == Error { } } -extension Task where Success == Never, Failure == Never { +public extension Task where Success == Never, Failure == Never { /// Suspends the current task for at least the given duration /// in seconds. /// @@ -137,12 +137,12 @@ extension Task where Success == Never, Failure == Never { /// this function throws `CancellationError`. /// /// This function doesn't block the underlying thread. - public static func sleep(seconds: TimeInterval) async throws { + static func sleep(seconds: TimeInterval) async throws { try await sleep(nanoseconds: NSEC_PER_MSEC * UInt64(seconds * 1000)) } } -extension Task where Failure == Error { +public extension Task where Failure == Error { @discardableResult static func delayed( bySeconds seconds: TimeInterval, diff --git a/Sources/BraveShared/Extensions/URLSessionExtensions.swift b/Sources/BraveShared/Extensions/URLSessionExtensions.swift index d9e3c0b84be2..3a503a1b3195 100644 --- a/Sources/BraveShared/Extensions/URLSessionExtensions.swift +++ b/Sources/BraveShared/Extensions/URLSessionExtensions.swift @@ -5,13 +5,16 @@ import Foundation import Shared import os.log +import Combine extension URLSession { @discardableResult public func request( _ url: URL, method: HTTPMethod = .get, - parameters: [String: Any], + headers: [String: String] = [:], + parameters: [String: Any] = [:], + rawData: Data? = nil, encoding: ParameterEncoding = .query, _ completion: @escaping (Result) -> Void ) -> URLSessionDataTask! { @@ -19,7 +22,9 @@ extension URLSession { let request = try buildRequest( url, method: method, + headers: headers, parameters: parameters, + rawData: rawData, encoding: encoding) let task = self.dataTask(with: request) { data, response, error in @@ -44,6 +49,60 @@ extension URLSession { return nil } } + + public func request( + _ url: URL, + method: HTTPMethod = .get, + headers: [String: String] = [:], + parameters: [String: Any] = [:], + rawData: Data? = nil, + encoding: ParameterEncoding = .query + ) -> AnyPublisher { + do { + let request = try buildRequest( + url, + method: method, + headers: headers, + parameters: parameters, + rawData: rawData, + encoding: encoding) + + return dataTaskPublisher(for: request) + .tryMap({ data, response in + try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) + }) + .mapError({ $0 as Error }) + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } catch { + Logger.module.error("\(error.localizedDescription)") + return Fail(error: error).eraseToAnyPublisher() + } + } + + public func request( + _ url: URL, + method: HTTPMethod = .get, + headers: [String: String] = [:], + parameters: [String: Any] = [:], + rawData: Data? = nil, + encoding: ParameterEncoding = .query + ) async throws -> (Any, URLResponse) { + do { + let request = try buildRequest( + url, + method: method, + headers: headers, + parameters: parameters, + rawData: rawData, + encoding: encoding) + + return try await data(for: request) + } catch { + Logger.module.error("\(error.localizedDescription)") + throw error + } + } } extension URLSession { @@ -56,6 +115,7 @@ extension URLSession { } public enum ParameterEncoding { + case textPlain case json case query } @@ -63,8 +123,9 @@ extension URLSession { private func buildRequest( _ url: URL, method: HTTPMethod, - headers: [String: String] = [:], + headers: [String: String], parameters: [String: Any], + rawData: Data?, encoding: ParameterEncoding ) throws -> URLRequest { @@ -72,6 +133,10 @@ extension URLSession { request.httpMethod = method.rawValue headers.forEach({ request.setValue($0.value, forHTTPHeaderField: $0.key) }) switch encoding { + case .textPlain: + request.setValue("text/plain", forHTTPHeaderField: "Content-Type") + request.httpBody = rawData + case .json: request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) diff --git a/Sources/CertificateUtilities/BraveCertificateUtils.swift b/Sources/CertificateUtilities/BraveCertificateUtils.swift index f6765d1269e7..0ad64e00c65c 100644 --- a/Sources/CertificateUtilities/BraveCertificateUtils.swift +++ b/Sources/CertificateUtilities/BraveCertificateUtils.swift @@ -4,6 +4,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. import Foundation +import Shared public struct BraveCertificateUtils { /// Formats a hex string diff --git a/Sources/CertificateUtilities/CertificatePinning.swift b/Sources/CertificateUtilities/CertificatePinning.swift deleted file mode 100644 index 115432ac6ca6..000000000000 --- a/Sources/CertificateUtilities/CertificatePinning.swift +++ /dev/null @@ -1,194 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -import Foundation -import Shared -import os.log - -// Taken from: https://github.com/Brandon-T/Jarvis and modified to simplify - -public class PinningCertificateEvaluator: NSObject, URLSessionDelegate { - struct ExcludedPinningHostUrls { - static let urls = [ - "laptop-updates.brave.com", - "updates.bravesoftware.com", - "updates-cdn.bravesoftware.com", - ] - } - - private let hosts: [String] - private let certificates: [SecCertificate] - private let options: PinningOptions - - public init(hosts: [String], options: PinningOptions = [.default, .validateHost, .anchorSpecificAndSystemTrusts]) { - self.hosts = hosts - self.options = options - - // Load certificates in the main bundle.. - self.certificates = { - let paths = Set( - [".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { - Bundle.module.paths(forResourcesOfType: $0, inDirectory: nil) - }.joined()) - - return paths.compactMap({ path -> SecCertificate? in - guard let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData else { - return nil - } - return SecCertificateCreateWithData(nil, certificateData) - }) - }() - } - - public init(hosts: [String: SecCertificate], options: PinningOptions = [.default, .validateHost, .anchorSpecificAndSystemTrusts]) { - self.hosts = hosts.map({ $0.key }) - self.certificates = hosts.map({ $0.value }) - self.options = options - } - - public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - - DispatchQueue.global(qos: .userInitiated).async { - // Certificate pinning - if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { - if let serverTrust = challenge.protectionSpace.serverTrust { - do { - let host = challenge.protectionSpace.host - if ExcludedPinningHostUrls.urls.contains(host) { - return completionHandler(.performDefaultHandling, nil) - } - - if !self.canPinHost(host) { - self.fatalErrorInDebugModeIfPinningFailed() - throw self.error(reason: "Host not specified for pinning: \(host)") - } - - try self.evaluate(serverTrust, forHost: host) - return completionHandler(.useCredential, URLCredential(trust: serverTrust)) - } catch { - Logger.module.error("\(error.localizedDescription)") - self.fatalErrorInDebugModeIfPinningFailed() - return completionHandler(.cancelAuthenticationChallenge, nil) - } - } - self.fatalErrorInDebugModeIfPinningFailed() - return completionHandler(.cancelAuthenticationChallenge, nil) - } - return completionHandler(.performDefaultHandling, nil) - } - } - - private func canPinHost(_ host: String) -> Bool { - return hosts.contains(host) - } - - private func error(reason: String) -> NSError { - return NSError(domain: "com.brave.pinning-certificate-evaluator", code: -1, userInfo: [NSLocalizedDescriptionKey: reason]) - } - - func evaluate(_ trust: SecTrust, forHost host: String) throws { - // Certificate validation - guard !certificates.isEmpty else { - throw error(reason: "Empty Certificates") - } - - // Certificate anchoring - if options.contains(.anchorSpecificTrustsOnly) || options.contains(.anchorSpecificAndSystemTrusts) { - // Add the certificates to the trust - guard SecTrustSetAnchorCertificates(trust, certificates as CFArray) == errSecSuccess else { - throw error(reason: "Certificate Anchor Failed") - } - - if options.contains(.anchorSpecificTrustsOnly) { - // Trust only the passed in certificates (true) - // - // This is the default behaviour, however we do it explicitly to throw an exception - // immediately upon failure - // The default behaviour will silently ignore the exception until validation - guard SecTrustSetAnchorCertificatesOnly(trust, true) == errSecSuccess else { - throw error(reason: "Self-Signed Certificate Anchor Only Failed") - } - } else { - // Trust also the built in system certificates (false) - guard SecTrustSetAnchorCertificatesOnly(trust, false) == errSecSuccess else { - throw error(reason: "Certificate Anchor Only Failed") - } - } - } - - // Default validation - if options.contains(.default) { - guard SecTrustSetPolicies(trust, SecPolicyCreateSSL(true, nil)) == errSecSuccess else { - throw error(reason: "Trust Set Policies Failed") - } - - var err: CFError? - if !SecTrustEvaluateWithError(trust, &err) { - if let err = err as Error? { - throw error(reason: "Trust Evaluation Failed: \(err)") - } - - throw error(reason: "Unable to Evaluate Trust") - } - } - - // Host validation - if options.contains(.validateHost) { - guard SecTrustSetPolicies(trust, SecPolicyCreateSSL(true, host as CFString)) == errSecSuccess else { - throw error(reason: "Trust Set Policies for Host Failed") - } - - var err: CFError? - if !SecTrustEvaluateWithError(trust, &err) { - if let err = err as Error? { - throw error(reason: "Trust Evaluation Failed: \(err)") - } - - throw error(reason: "Unable to Evaluate Trust") - } - } - - // Certificate binary matching - let serverCertificates = Set( - (SecTrustCopyCertificateChain(trust) as? [SecCertificate] ?? []) - .map({ SecCertificateCopyData($0) as Data }) - ) - - // Set Certificate validation - let clientCertificates = Set(certificates.compactMap({ SecCertificateCopyData($0) as Data })) - if serverCertificates.isDisjoint(with: clientCertificates) { - throw error(reason: "Pinning Failed") - } - } - - private func fatalErrorInDebugModeIfPinningFailed() { - if !AppConstants.buildChannel.isPublic { - assertionFailure("An SSL Pinning error has occurred") - } - } - - public struct PinningOptions: OptionSet { - public let rawValue: Int - - public init(rawValue: Int) { - self.rawValue = rawValue - } - - /// System's default pinning policies - public static let `default` = PinningOptions(rawValue: 1 << 0) - - /// Host Validation - public static let validateHost = PinningOptions(rawValue: 1 << 1) - - /// Anchor ONLY the specified trusts passed to `PinningCertificateEvaluator.evaluate(trust:host:)` - public static let anchorSpecificTrustsOnly = PinningOptions(rawValue: 1 << 2) - - /// Anchor the specified trusts passed to `PinningCertificateEvaluator.evaluate(trust:host:)` - /// Also anchors the default trusts of the System - public static let anchorSpecificAndSystemTrusts = PinningOptions(rawValue: 1 << 3) - - /// Default pinning policies + Host Validation + Anchor the specified trusts + Anchor system trusts - public static let all: PinningOptions = [.default, .validateHost, .anchorSpecificAndSystemTrusts] - } -} diff --git a/Sources/CertificateUtilities/Certificates/AmazonRootCA1.cer b/Sources/CertificateUtilities/Certificates/AmazonRootCA1.cer deleted file mode 100644 index 86b7dcd0b416fcbfd1636f5c019403ede4ddb413..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 837 zcmXqLVs@5qwPB{BO^B} zgMpDG!O>~FbfMn1Qdeu^Gg(*9Tf}> z*0AF)}i2s%R)Y zRdMxk;d=LmfRft08`nF^zRb+{9-)}ca~YmCeB;(ugPrbQ?IZmt`C3y-6Hm@&Zwbc1Q{rc}h`cPmw8PecTBE+ObT9 diff --git a/Sources/CertificateUtilities/Certificates/AmazonRootCA2.cer b/Sources/CertificateUtilities/Certificates/AmazonRootCA2.cer deleted file mode 100644 index 86d70df56ac1baf2944e3de7e6dd95e3d5c4812b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1349 zcmXqLVs$iVV%A*1%*4pVB+QmG|I#$mw)tHAPunVfM62!YGB@C53z>PMfbg$IHj7 zu`+Pxo37K@34y|Ud~@0UY~bjFyzdTj9E@rB)$1MAG3Q@ToQ*WB=2$FK1uwj1=>&|Tz+h1LHVr3%~ob=!8Q}sSm z)|Emgk3XlBBB94twr>a*Xf!_=ym@#1w@DZ0T#)!ODeqRZ|FYznK1+Ycy;n@B zQi#t56anD%}O;RyM=MVC`YJj83p#gK=A=^U5d=GEofU(#)i)J!`) z!E&mgT#5R>yRX+XF*7nSE_O0-FyIHKPFZ0_#{Vp=2FyUpfDa_V4-#MjW`Z^YSrDI( zMT|vc1J4H^gDZh4#*gq+HN*#Vf!7#UWrXz1swSo~nom9x*j&Zx3} z#lXB_I^X0yQ7gXb^c1zP-1w)y_-}Gy#nSf3^9B!j1vUvs@Gw_yH&jkuThYwfmeF$R|IhPkvnEwOKkI$; zdE#>M)1OxgO`e>+{Dpq-q$3Ft8_qBVN<_EzCPgN1Tf61ntlw(i8PpHNhMJemJQ+~` zI8x8$v5xAyH$Fz?Tjoo>^o~_Hdh4%DyVx=37gu(x&2lY>Tgh!6{K&0Z{qF;}Po3M( zIBh$vp_X@Z=}D^-j=~Aw1A0W)ZOWArl?tzTHc9Jd>Vziy9H#Z_gsjx3nXz1#UDS}g za`wlkySCJbZ16j|QNASJ;#=UW*}rra30_&Lt1jibas4L0Lf!)rZr3u|P6waf{4d+_ ztYclR4}VQjWmV+aI4{06vscb2uB_Se<;>0<>^z%FVs;$Q?^rlt`Il8c_orKZkrGXk z+dhAv?MBV@=?jHEMF>a+GaE?nkm~$4XYX|8zQvrMWxEwWZn^(N=HmQRCbm~G*Mv8> zykE35rl(wUdB%b4bq<$SYDDMjVD#CpH~?(^8c znWiqe%oX%;uPzSe2h=C}|)L5?~e!|uI6EpB8_0?C z8k!oI8kre@L6kVJi2)Lq5~CvxgxT1^Ze(JFI+mG{o!NvPvMpuV6tNOBFYb%~JOZ2mNz<8|7- z#ZCqe2K+!T$qF+v{%2t|U{dSkN_}D7~2eFL3}iABF2F1+wtrXpdP^UR|= qjsTU+VS4{@%AC#9e!f1Y^<$gPKdT8MV%K|;RXk+w8`NISum%7{D~MeH diff --git a/Sources/CertificateUtilities/Certificates/AmazonRootCA4.cer b/Sources/CertificateUtilities/Certificates/AmazonRootCA4.cer deleted file mode 100644 index 10ef4413d71f2d7bea1f20618459f1eb18311119..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 502 zcmXqLV*F&##8|O_nTe5!Nti8X{`G^q1$;D}pB7eE>zt4}$Y;RC#-Y{ban6>7nc2Y7 zklTQhjX9KsO_(V(*pT0V7sTNbW^>FS0&MJHH!?A@v1&K6Feou6F|b@(-DU2-IGFd}!LDlW z71eIu$s3Zs=}O<`C{(<}@FIEdEcM?XEc0St*)BcVxN32}23zzz_7VtS}?ve->5) zW*}w22NK{139taew9P;k#OGrXV-dOh=D1aA-kFED%4UWAp7XgaH~eWMa_BN=Fc_pV z8CZ2Ie%xCpxc0*Ph-v!QUeB$Y(5rnuE%dO?V~v#sT<&}y%p-;V>&~2_;iu(q>T4Xu zWXSNUl)=yXh}9IKi6=1>+kVJ0V6LrDX15Qj@xz&$5FDKRHFGd)isuS~Z%(NM-f3M9oWEQ}--l%HRs z;A&!KAScdiXlY<tC;=DjX1ea>|lo<%Hv4dUD#K^{~-N?eA#GJ&yGKX*D`Ry;} z1(lbY{95X^e65kB(H8cV>Zhjc=DEZraQs&1hUq*7!kmnKH(t5F+PA4e`|KYL-rH;T zENL@;Tfo6k`f4(FwO-I1p#Nq0 z85#exumHoh&43@o7Y6ZJ4VZzHfh=p0F*4N9RL6T diff --git a/Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R1.cer b/Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R1.cer deleted file mode 100644 index 1e6967febb897f69cc81064c6ba584fc91bcfb13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 889 zcmXqLVlFjkVv1eB%*4pV#LdD01dO8IQHQ4(@Un4gwRyCC=VfGMWo0l3H{>?pWMd9x zVH0L@ay67R5C?I%gazDl@{;D#ZXBDIdNV~3j<37Lqj72Aczv@HL@@;G_rtlX=ia0qY|mhT4XLYN9@iB*y;{bnfPJAYm%&-lUjH9=uk-|n%DfMFcSP^|pO-rV^4V-UGNZM8k1h|{k<)o*_D`ErPycp)6|$LBG40x&lDx?- zi5Gj1yKO4vYkMKtbu~BVYK7R;MK4cqS-k$FBJ5urGFiCQN_vl+$<}Vxo}K008*j@i zOkB+woHyZIwRy?T0{4kZfyrW(>o%@)RO2`yseRFHmB+Odt?SJu|8=wu=14kwv9~_I zT=Pvamo3g?nKC1yUkTQ@3 z3GlIqv4|v8r%Ac;PCwNdpkK>!)?~&1KYzWELm8NqfT7IDaIN@xjepsN-u-WOE!t;t zRKsX%&7Yh(I@)_V=LPT2j$0+@vF$0x;?1XQ6MZkcac;Idyx?(isPv~@@7G#Bblz#P z_?do}n)Uky0bxInas9P8dxX0naLV)|MR}r z*_rJ2ZPlOqdous(33;T7A|GKXaDFy3OTT)%0X$;N61lx5I1`pJY9Zx%Ta!?LEgc?CX+) z_6G-l(SCXS@3!wv@vWB}C2luvW|286zoa2wRN>@3o(?YAYiXg6{&<|!RLgt-09X)I A(*OVf diff --git a/Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R3.cer b/Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R3.cer deleted file mode 100644 index 232c4b6121f7236eb429572c591127e8aa60166f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 867 zcmXqLVvaXxVsc-=%*4pV#LdD01dNIi!5oVWc-c6$+C196^D;7WvoaX?7%CXZu`!3T za0`pO=j10P<^*S^=P3l`=a(orJ1XcZ1Q{C&8wi3_a0zoERKNt8kp&Ip#CZ)Y4U7#f z3=KdaN}SgSnM-Bcni!Rky~D`Lz}&>h&tTBR$i>ve$jER;wQNEqTZNL?*8|PlT25)q z`^#D;cyw(?(H}P^=i{5Y=CZ`AoYwPxn9$_*FlaSTqkBQl-IR;3zv?XJZ?fglUN`;v zHjy@g%H7t&4dp!?4?QnsCF#q@{hF3>zf*mx#eBBwb|+7(Me-Kk+i>Eg8eg;MvG>v4 zmsk=`c`noVmTCR%^a+iLPv>?ehMTV`5xG?_`;jk@3_*EunPN{PO=3Mc%Jd@cgsZLDb_S zlVPG{+>Yt**OqTjnN_tv{-E&t*-5{7a~_0bimhb6mG`oFa$(uA%+@AxCT2zk#>Gws z4hDR{_><*lWc<&<0!$HX2K*qtFo@4;zzn1eWI+OaEMhDo{U5yRSLnH_tn&@{l{~Ba z-lMzHdyqpJm}r2Z%*f#FaQi^Os(&YV-hZDK;_A738Uv@}n$5y(Z5r&xr?Q`w?A*nm zyKV{B<*y$<@^|eoPWNg)?owUxV0~UrKC@<@v8C(bz9&1wb5{Kkn)W96nC=smoSjpW zf8PntNDs4X-f`Yk@$kuvf9Gx;3SM<)Lf6X=v2Hz6?^Z=gm=bbRTvM6jq*l?z>U;6vh=ZpX(UQTUo1KYI8Vet)FR?K>|cBM&Qs zftw+>0Vf-CC<~h~las5Vq=7hy!zC=>o|B)Hm=m0to~Mvkrdyn7C}SW6l42GXMv@B3 z&o5C3GBGod6X!LwG%z+YfPg4*UZ5a?OEr6%n3Rycz{twL+{DDsV9>3OgzC}8s>Wn?tFE@YRikKhT9*N&_)Dcs zPvbb>Q~g-`z}%lApQJyAWr^+AX>C1QVKM8qQx=nnM6|3(DNugKymwMsJ`^Owh7WjUfD0auKCtfqzFtU*6M*g8~Mzz16 z#Re~{6aBNhv?lh=k8k2KZ;oe6p8j0@L7RQ)&78`RvYfdx%kubMR`RS+&RTq5+Q4xB z#*pfNS*O!h-ATQ$Xr9+BcE#+*{@X_)f8A3$z%u9Ys%W#xFKqW#Eu70;wvqez+`s2% ztYACzLu>KE<^|hTi}KrsB;vCY{~Ub(6GX zE${5RA-l-Q*!SoAC0f=lnR07xx-Ry!S$;z$a8roPWWOWtt0%W6sJ-I!c<^=i@s}Bw zc4rwDo~c|XxZ=aizfa}&=&v{v{<`dl;xhN(P~VLGtV`ZqS6iKQ%h}xf(Xp*hH#JLI z9lw0rcOFO8=XdArl76pwk#OeX@r5zXu18$Yd=}3ZImyCgcS&Vg>InnMQ$~}`qvZs@`B&WGv$UGT?DIQHriblaTS$FQW3s%+V@;c^Fgyu z0$ceya(6roo8NhFTAHPMicX&Ix}@s;2_=#{!q!Y$BWCn=ZAR$78M`i<-1~Ae@i1T8 zH}95&cf8eGjUp;1hUzprZ!VFqTBSNq_WHu^ooAi&SeB$U`*xac%UNOORjF8^birxQ zMF+zP3-_^{Kb8LW>xYKR2`8JilvppYTL28cgrEPX zyXSq(0jbs7=BpgLU!37FK}Y0^eUQ|XOQ%H+CuW|1^i$&KF5~$ki&ZyYWCVVriQB;Crqe9rT`OZdy2)4H5L&Hv5gRFQn~ z5r2O5m4&t?(_6QDSUO%)_;bN(^RE~AN4j8?faItY{wRxPgWnpGE2ryJJ zP-0^aW#JZGeqAijbzXF! zJ=KIuzUuC48{>sX=lU+mcbsZbe^RdH^~HIfliH`W^H{ZdrOlrkIoWr5B3ql+VkZL! z13qA=$nrBX{%2tU1~;1lKZq|3;kJQGfj`#uh>s1x_GJG%e!}} z`NI52lP)}vaXlW%KME}v#q?GC%S;CWoE zA9wSd=rYX(oN9_$NlXTXm$vrDys~~+!P&Z^bdvRnn7Ur)s&z|Lp SnIFGfKh!$?Q~Pt;1akl<_O(j@ diff --git a/Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R6.cer b/Sources/CertificateUtilities/Certificates/GlobalSignRootCA_R6.cer deleted file mode 100644 index 3492b9555d8a927fc048accbdfd510d973a4bf8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1415 zcmXqLVr@2PV$NQ`%*4pV#OL~KH*>S`;nq}-Xa8LT4S3l&wc0$|zVk9N@~|=(_!ue} z$gwepvTzHFyXWL5CFTTYrspXH<>!|uI6ErnDg>Ds3L6N5RB#D%AymKwnUMtzrbf z7RhT}D$uKbzGw38+~7wZH9Z3C7XQ;Wl_%Ccc-3RGYRi&upQm~WvFHBn+0=Rd!h*!| z6A>jFt|(i5kF`3r==NOBd$BIM6FxSc4WIO@ZchM9vdR7I$rAgr%{Ey+S)*dM{loih zGQ#1Lx82+H<5BDO;zc`5SGj(AFxPBhen{8HqOd%@j-!E2E}~Q3Ek0^7ZML6Z6u0|l zHZ#w1gFS`jPnW$>4}38Fd-9e>uasbWK_l0SS*5b`tlC%^=G|_%ruy>^>pb;H zpEAqnS3j37ywS{m@&A1T!JnWsAklyin0{sX85#exumCd!n*l$FFAUt}2QM&?2T3clNEnDUU{`>etANRnk)c`j?Td?lrv{y!cYtNV z#LQ-Z{64y*oN&oy5m`>wTczfjL3M=6i0;sxcI$G64QH=U6C z7a^^oy&`9ATM4e8`Et>2H?s`?&tUywgCL-KSq* zf7`U1Go!Z543}AQX18c(@Gc!z{wGJ~Np6%dx#}}_*W2&%>s6+*RIFg${jTxpt@xU5 zuZ0szRF`kI^FE;yw)gmn_ZE+0CVi;XtY?ygew1xBAHBZk2sXLxY73M8i@v-TJt6k1Drt4hk L&8$%;mk0m=(y?My diff --git a/Sources/CertificateUtilities/Certificates/ISRGRootCA_X1.cer b/Sources/CertificateUtilities/Certificates/ISRGRootCA_X1.cer deleted file mode 100644 index 9d2132e7f1e352fabac7eafb231488b5da91ffb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1391 zcmXqLV$C*aVh&!w%*4pVB*@StaDKxjhsTjF$q#lXH+3@@@Un4gwRyCC=VfH%W@Rw& zH{>?pWMd9xVH0Kw4K~y?PzQ0igcUsVN>YpRQcDzqQMi96N{2F6x@sQ zOA8D|4TM2TnT2^ggM-`^g7WiA6e0`_)UeSUJ_S;M+V-u>HW)=goaf7yL{%}fvF;1?F_{JHX*^)7mb z_cWAjyQP1@qPLp4KvBB%lYz~z{&jb6C9i%h=6|S9(7WzD_ly5q%k{o&s`h%|Bc#ex z(95j3;9;=J8{wPpB=-w!_Uf_kT$~tqZ%sS8l;RAn=gy-c5l%vESRjulRoaDHHpQelw1#&mWmj<25Ut_nWV1qwMTG%s)L@ zZ#3Rz-J*5P@#PxEvZ-ABH|}5EDDklY(M=kbokat@+bL(=ez`Qo=d9_8$g;*;h-`WLMh;lRc_g>Iv-DFqo zCF5PpD)i^rs|NwXHO`YuHlHea-Y3t;=GdnK4#`;nE(6$dNYTB&bR(NQ2+$oz?wqHJLsjX!HYm3h*_fBZ@a%uek ze*2NA(-ox)>ah}I#svAgPldH?sMd^L9VXJTe#U|j5E;9$T9Os}&1 zjEw(TSb({M&43@o7Y6ZJ4VZzHfhFz=l^iUlGsD^9O_ z?o;@C)1`#9mMgeli7SS+ehlD?e0}ag-X~KPhVT7{&D4o6YKug*3J5*#Pa(8&H7gpwUsuC^Ywq~GKr43@rUtb$j%*V zXSzC!JAHIpY?|)Bn-;WsJ~s2)HigcR z-KW{sqcnToqipMNtEK|qJDkTmPjj*R=DdjQJNf?H>f^h&YWulf^SYpR=4sI>j;y6q zAB!&hzU1vmo%p4{|F6+t(%W~vdiUeP>Iq_(+2h=TYs}f5dM+QCHs|Whty&MJN;P<_ z^RZ+cWl3o${YKsGG(t*4WP8`@Gk9!gJ;MzXRq} z=D1zmBD#56Ufpb-X;wRebnUN2Km5&csO6u^ip8C`)?_`D(Av1dIWhXO{2lAwvQN4% zdQ0z%8|T;t|E@mm82|syq6>)@52x)|6WeWmz4WT_ftiBq<~klMDs9=va+>1r8-Ysu9sFU+jzdgu+i;Y98&EuRc3p2BUzah5) zCmVAp3!5-gXt1HCfjWr8C9L3?SCU$kms+9_oSIx(lvz@#5R_V+npl*aq2OMWUs_-& zY9I_!$}G&|864!U5R{)^q7Y$ZAScdiWME)vU}69UQR2KnmZhP&p&5uDMSTm)30&A6SaOKU4F2Dcb zm*X4DJEf;G*m>k%Ep{?+FyI4*fGj^F<9`+wU|6#m@PqimAU>-BGmtWn1qtx6h_Q&& zI89r3&)dWL*Q6iwdd}x|uPid3iX0Qn84L!gOa|4Jer^FRhg{m-q+JXCN6ftUORkyhaaI#U`uuza;ap9t6a2L{-?Pgs zl5d(Nte71a!_crvKeGN?`%hKpw&$7BL9-@2EPQ-Jm)9@<+X>H`+dfN$t<&CCBJ%qD zZ?(r#yJV;7Wm!i|>2weKuexAzL2&b}mJ*S}>GN1JtnT+YM9F&jop+vS{XFa%L&52w zch$A)te)>N{OS7bP{MiPNG9!uGbZl$cix+jn#*)vXX3JD&+oNLc-Ex(Ew;*Cx%Hii z-Y#C|9meeDD+ZAek_f&-n9TI)D_j}ew*jYqV9Z;Uo_wC|NGvNvHXns%A{7~Pv#u&3(uFc z-8&&E+u?A&)=l=m-0_F!{wv=;_NzD4Zza=ncR@G1FMsz|YpU&DnV4j*u}xR&n?PI2 z$%5sV)^67!V7OH{5j|q J(7RD%7XbdRdH( Bool { - if AppConstants.buildChannel == .debug || AppConstants.buildChannel == .enterprise { - Logger.module.info("Development build detected, no server ping.") - return false - } - guard Preferences.DAU.sendUsagePing.value else { Logger.module.debug("DAU ping disabled by the user.") return false @@ -239,7 +242,7 @@ public class DAU { } func channelParam(for channel: AppBuildChannel = AppConstants.buildChannel) -> URLQueryItem { - return URLQueryItem(name: "channel", value: channel.serverChannelParam) + return URLQueryItem(name: "channel", value: channel.dauServerChannelParam) } func braveCoreParams(for braveStats: BraveStats) -> [URLQueryItem] { diff --git a/Sources/Growth/GrowthPreferences.swift b/Sources/Growth/GrowthPreferences.swift index 409795105e6e..04853bd9ed7d 100644 --- a/Sources/Growth/GrowthPreferences.swift +++ b/Sources/Growth/GrowthPreferences.swift @@ -29,6 +29,7 @@ extension Preferences { public static let referralCodeDeleteDate = Option(key: "urp.referral.delete-date", default: nil) /// Whether the ref code lookup has still yet to occur public static let referralLookupOutstanding = Option(key: "urp.referral.lookkup-completed", default: nil) + public static let installAttributionLookupOutstanding = Option(key: "install.attribution.lookup-completed", default: nil) } public final class Review { diff --git a/Sources/Growth/URP/AdAttributionData.swift b/Sources/Growth/URP/AdAttributionData.swift new file mode 100644 index 000000000000..8f47c018998a --- /dev/null +++ b/Sources/Growth/URP/AdAttributionData.swift @@ -0,0 +1,70 @@ +// Copyright 2023 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os.log + +public struct AdAttributionData { + // A value of true returns if a user clicks an Apple Search Ads impression up to 30 days before your app download. + // If the API can’t find a matching attribution record, the attribution value is false. + public let attribution: Bool + // The identifier of the organization that owns the campaign. + // organizationId is the same as your account in the Apple Search Ads UI. + public let organizationId: Int? + // The type of conversion is either Download or Redownload. + public let conversionType: String? + // The unique identifier for the campaign. + public let campaignId: Int + // The country or region for the campaign. + public let countryOrRegion: String? + + init(attribution: Bool, organizationId: Int? = nil, conversionType: String? = nil, campaignId: Int, countryOrRegion: String? = nil) { + self.attribution = attribution + self.organizationId = organizationId + self.conversionType = conversionType + self.campaignId = campaignId + self.countryOrRegion = countryOrRegion + } +} + +enum SerializationError: Error { + case missing(String) + case invalid(String, Any) +} + +extension AdAttributionData { + init(json: [String: Any]?) throws { + guard let json = json else { + throw SerializationError.invalid("Invalid json Dictionary", "") + } + + // Attribution and campaignId are the major properties here + // They will indicate if the Apple Searhs Ads is clicked and for which campaign + guard let attribution = json["attribution"] as? Bool else { + Logger.module.error("Failed to unwrap json to Ad Attribution property.") + UrpLog.log("Failed to unwrap json to Ad Attribution property. \(json)") + + throw SerializationError.missing("Attribution Context") + } + + guard let campaignId = json["campaignId"] as? Int else { + Logger.module.error("Failed to unwrap json to Campaign Id property.") + UrpLog.log("Failed to unwrap json to Campaign Id property. \(json)") + + throw SerializationError.missing("Campaign Id") + } + + if let conversionType = json["conversionType"] as? String { + guard conversionType == "Download" || conversionType == "Redownload" else { + throw SerializationError.invalid("Conversion Type", conversionType) + } + } + + self.attribution = attribution + self.organizationId = json["orgId"] as? Int + self.conversionType = json["conversionType"] as? String + self.campaignId = campaignId + self.countryOrRegion = json["countryOrRegion"] as? String + } +} diff --git a/Sources/Growth/URP/UrpService.swift b/Sources/Growth/URP/UrpService.swift index 25025dc48ccf..1d15af655e25 100644 --- a/Sources/Growth/URP/UrpService.swift +++ b/Sources/Growth/URP/UrpService.swift @@ -7,6 +7,8 @@ import BraveShared import CertificateUtilities import SwiftyJSON import os.log +import Combine +import BraveCore enum UrpError { case networkError, downloadIdNotFound, ipNotFound, endpointError @@ -21,20 +23,19 @@ struct UrpService { static let downLoadId = "download_id" } - let host: String + private let host: String + private let adServicesURL: String private let apiKey: String - let sessionManager: URLSession - private let certificateEvaluator: PinningCertificateEvaluator + private let sessionManager: URLSession + private let certificateEvaluator: URPCertificatePinningService - init?(host: String, apiKey: String) { + init?(host: String, apiKey: String, adServicesURL: String) { self.host = host self.apiKey = apiKey - - guard let hostUrl = URL(string: host), let normalizedHost = hostUrl.normalizedHost() else { return nil } + self.adServicesURL = adServicesURL // Certificate pinning - certificateEvaluator = PinningCertificateEvaluator(hosts: [normalizedHost]) - + certificateEvaluator = URPCertificatePinningService() sessionManager = URLSession(configuration: .default, delegate: certificateEvaluator, delegateQueue: .main) } @@ -76,6 +77,31 @@ struct UrpService { } } } + + @MainActor func adCampaignTokenLookupQueue(adAttributionToken: String) async throws -> (AdAttributionData?) { + guard let endPoint = URL(string: adServicesURL) else { + Logger.module.error("AdServicesURLString can not be resolved: \(adServicesURL)") + throw URLError(.badURL) + } + + let attributionDataToken = adAttributionToken.data(using: .utf8) + + do { + let (result, _) = try await sessionManager.adServicesAttributionApiRequest(endPoint: endPoint, rawData: attributionDataToken) + UrpLog.log("Ad Attribution response: \(result)") + + if let resultData = result as? Data { + let jsonResponse = try JSONSerialization.jsonObject(with: resultData, options: []) as? [String: Any] + let adAttributionData = try AdAttributionData(json: jsonResponse) + + return adAttributionData + } + } catch { + throw error + } + + return (nil) + } func checkIfAuthorizedForGrant(with downloadId: String, completion: @escaping (Bool?, UrpError?) -> Void) { guard var endPoint = URL(string: host) else { @@ -109,9 +135,40 @@ struct UrpService { extension URLSession { /// All requests to referral api use PUT method, accept and receive json. func urpApiRequest(endPoint: URL, params: [String: String], completion: @escaping (Result) -> Void) { - - self.request(endPoint, method: .put, parameters: params, encoding: .json) { response in + request(endPoint, method: .put, parameters: params, encoding: .json) { response in completion(response) } } + + // Apple ad service attricution request requires plain text encoding with post method and passing token as rawdata + func adServicesAttributionApiRequest(endPoint: URL, rawData: Data?) async throws -> (Any, URLResponse) { + // According to attributiontoken API docs + // An error reponse can occur API call is done too quickly after receiving a valid token. + // A best practice is to initiate retries at intervals of 5 seconds, with a maximum of three attempts. + return try await Task.retry(retryCount: 3, retryDelay: 5) { + return try await self.request(endPoint, method: .post, rawData: rawData, encoding: .textPlain) + }.value + } +} + +class URPCertificatePinningService: NSObject, URLSessionDelegate { + func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { + if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { + if let serverTrust = challenge.protectionSpace.serverTrust { + let result = BraveCertificateUtility.verifyTrust(serverTrust, host: challenge.protectionSpace.host, port: challenge.protectionSpace.port) + // Cert is valid and should be pinned + if result == 0 { + return (.useCredential, URLCredential(trust: serverTrust)) + } + + // Cert is valid and should not be pinned + // Let the system handle it and we'll show an error if the system cannot validate it + if result == Int32.min { + return (.performDefaultHandling, nil) + } + } + return (.cancelAuthenticationChallenge, nil) + } + return (.performDefaultHandling, nil) + } } diff --git a/Sources/Growth/URP/UserReferralProgram.swift b/Sources/Growth/URP/UserReferralProgram.swift index 2483e4abb53f..e6af1550fdd4 100644 --- a/Sources/Growth/URP/UserReferralProgram.swift +++ b/Sources/Growth/URP/UserReferralProgram.swift @@ -7,6 +7,7 @@ import Shared import Preferences import WebKit import os.log +import AdServices public class UserReferralProgram { @@ -20,6 +21,8 @@ public class UserReferralProgram { static let staging = "https://laptop-updates.bravesoftware.com" static let prod = "https://laptop-updates.brave.com" } + + let adServicesURLString = "https://api-adservices.apple.com/api/v1/" // In case of network problems when looking for referrral code // we retry the call few times while the app is still alive. @@ -47,7 +50,7 @@ public class UserReferralProgram { return nil } - guard let urpService = UrpService(host: host, apiKey: apiKey) else { return nil } + guard let urpService = UrpService(host: host, apiKey: apiKey, adServicesURL: adServicesURLString) else { return nil } UrpLog.log("URP init, host: \(host)") @@ -55,7 +58,7 @@ public class UserReferralProgram { } /// Looks for referral and returns its landing page if possible. - public func referralLookup(completion: @escaping (_ refCode: String?, _ offerUrl: String?) -> Void) { + public func referralLookup(refCode: String? = nil, completion: @escaping (_ refCode: String?, _ offerUrl: String?) -> Void) { UrpLog.log("first run referral lookup") let referralBlock: (ReferralData?, UrpError?) -> Void = { [weak self] referral, error in @@ -75,7 +78,7 @@ public class UserReferralProgram { withTimeInterval: self.referralLookupRetry.retryTimeInterval, repeats: true ) { [weak self] _ in - self?.referralLookup() { refCode, offerUrl in + self?.referralLookup(refCode: refCode) { refCode, offerUrl in completion(refCode, offerUrl) } } @@ -114,7 +117,28 @@ public class UserReferralProgram { // Since ref-code method may not be repeatable (e.g. clipboard was cleared), this should be retrieved from prefs, // and not use the passed in referral code. - service.referralCodeLookup(refCode: UserReferralProgram.getReferralCode(), completion: referralBlock) + service.referralCodeLookup(refCode: refCode, completion: referralBlock) + } + + public func adCampaignLookup(completion: @escaping ((AdAttributionData)?, Error?) -> Void) { + // Fetching ad attibution token + do { + let adAttributionToken = try AAAttribution.attributionToken() + + Task { @MainActor in + do { + let result = try await service.adCampaignTokenLookupQueue(adAttributionToken: adAttributionToken) + completion(result, nil) + } catch { + Logger.module.info("Could not retrieve ad campaign attibution from ad services") + completion(nil, error) + } + } + } catch { + Logger.module.info("Couldnt fetch attribute tokens with error: \(error)") + completion(nil, error) + return + } } private func initRetryPingConnection(numberOfTimes: Int32) { diff --git a/Sources/Shared/AppConstants.swift b/Sources/Shared/AppConstants.swift index 18ffbcda1b99..968464826592 100644 --- a/Sources/Shared/AppConstants.swift +++ b/Sources/Shared/AppConstants.swift @@ -37,6 +37,19 @@ public enum AppBuildChannel: String { return "invalid" } } + + public var dauServerChannelParam: String { + switch self { + case .release: + return "release" + case .beta: + return "beta" + case .dev, .debug: + return "developer" + case .enterprise: + return "invalid" + } + } } public struct KVOConstants: Equatable { diff --git a/Tests/CertificateUtilitiesTests/CertificatePinningTest.swift b/Tests/CertificateUtilitiesTests/CertificatePinningTest.swift index beb137e1462d..3cabb8d0d01b 100644 --- a/Tests/CertificateUtilitiesTests/CertificatePinningTest.swift +++ b/Tests/CertificateUtilitiesTests/CertificatePinningTest.swift @@ -7,211 +7,7 @@ import BraveShared @testable import CertificateUtilities @testable import BraveCore -extension CertificatePinningTest { - private func certificate(named: String) -> SecCertificate { - let path = Bundle.module.path(forResource: named, ofType: ".cer")! - let certificateData = try! Data(contentsOf: URL(fileURLWithPath: path)) as CFData - return SecCertificateCreateWithData(nil, certificateData)! - } - - private func trust(for certificates: [SecCertificate]) -> SecTrust { - var trust: SecTrust! - SecTrustCreateWithCertificates(certificates as CFTypeRef, SecPolicyCreateBasicX509(), &trust) - return trust - } -} - class CertificatePinningTest: XCTestCase { - - private lazy var leaf = certificate(named: "leaf") - private lazy var intermediate = certificate(named: "intermediate") - private lazy var root = certificate(named: "root") - - func testPinningWithHostValidation() { - let host = "www.apple.com" - let trust = self.trust(for: [leaf, intermediate, root]) - let evaluator = PinningCertificateEvaluator(hosts: [host: leaf], options: [.default, .validateHost]) - - do { - try evaluator.evaluate(trust, forHost: host) - } catch { - XCTFail("Validation failed but should have succeeded: \(error.localizedDescription)") - } - } - - func testFailPinningWithHostValidation() { - let host = "github.com" - let trust = self.trust(for: [leaf, intermediate, root]) - let evaluator = PinningCertificateEvaluator(hosts: [host: leaf], options: [.default, .validateHost]) - - do { - try evaluator.evaluate(trust, forHost: host) - XCTFail("Validation succeeded but should have failed") - } catch { - - } - } - - func testExpiredCertificate() { - let leaf = certificate(named: "expired.badssl.com-leaf") - let intermediate = certificate(named: "expired.badssl.com-intermediate-ca-1") - let intermediate2 = certificate(named: "expired.badssl.com-intermediate-ca-2") - let root = certificate(named: "expired.badssl.com-root-ca") - - let host = "badssl.com" - let trust = self.trust(for: [leaf, intermediate, intermediate2, root]) - let evaluator = PinningCertificateEvaluator(hosts: [host: leaf], options: [.default, .validateHost]) - - do { - try evaluator.evaluate(trust, forHost: host) - XCTFail("Validation succeeded but should have failed") - } catch { - - } - } - - func testUntrustedRoot() { - let leaf = certificate(named: "untrusted.badssl.com-leaf") - let root = certificate(named: "untrusted.badssl.com-root") - - let host = "badssl.com" - let trust = self.trust(for: [root]) - let evaluator = PinningCertificateEvaluator(hosts: [host: leaf], options: [.default, .validateHost]) - - do { - try evaluator.evaluate(trust, forHost: host) - XCTFail("Validation succeeded but should have failed") - } catch { - - } - } - - func testSelfSignedRoot() { - let leaf = certificate(named: "untrusted.badssl.com-leaf") - let root = certificate(named: "self-signed.badssl.com") - - let host = "badssl.com" - let trust = self.trust(for: [root]) - let evaluator = PinningCertificateEvaluator(hosts: [host: leaf], options: [.default, .validateHost]) - - do { - try evaluator.evaluate(trust, forHost: host) - XCTFail("Validation succeeded but should have failed") - } catch { - - } - } - - // As of iOS 13, it's extremely hard to :allow: a self-signed certificate! - // iOS 13 is validating the certificate against a list of CA's.. so to even use a self-signed certificate, you have to add it to the keychain/keystore as trusted.. - // Or sign it with a trusted CA.. For now this test has been disabled as we aren't using self-signed certificates anyway. - // https://support.apple.com/en-us/HT210176 - func testSelfSignedRootAllowed() { - let leaf = certificate(named: "self-signed") - - let host = "unit-test.brave.com" - let trust = self.trust(for: [leaf]) - let evaluator = PinningCertificateEvaluator(hosts: [host: leaf], options: [.default, .validateHost, .anchorSpecificTrustsOnly]) - - do { - try evaluator.evaluate(trust, forHost: host) - } catch { - XCTFail("Validation failed but should have succeeded: \(error.localizedDescription)") - } - } - - // Same as :testSelfSignedRootAllowed: - func testSelfSignedRootAllowed2() { - let leaf = certificate(named: "expired.badssl.com-leaf") - let root = certificate(named: "self-signed.badssl.com") - - let host = "badssl.com" - let trust = self.trust(for: [root]) - let evaluator = PinningCertificateEvaluator(hosts: [host: leaf], options: [.default, .validateHost, .anchorSpecificTrustsOnly]) - - do { - try evaluator.evaluate(trust, forHost: host) - } catch { - XCTFail("Validation failed but should have succeeded: \(error.localizedDescription)") - } - } - - // Test whether or not exception URLs are NOT pinned! - func testLivePinningSuccess() { - let urls = PinningCertificateEvaluator.ExcludedPinningHostUrls.urls.map({ "https://\($0)" }) - - var expectations = [XCTestExpectation]() - for host in urls { - let expectation = XCTestExpectation(description: "Test Pinning Live URLs: \(host)") - expectations.append(expectation) - - guard let hostUrl = URL(string: host), - let normalizedHost = hostUrl.normalizedHost() - else { - - XCTFail("Invalid URL/Host for pinning: \(host)") - expectation.fulfill() - return - } - - let certificateEvaluator = PinningCertificateEvaluator(hosts: [normalizedHost]) - let sessionManager = URLSession(configuration: .default, delegate: certificateEvaluator, delegateQueue: .main) - - sessionManager.request(hostUrl, method: .put, parameters: ["unit-test": "unit-value"], encoding: .json) { response in - switch response { - case .success: - break - - case .failure(let error as NSError): - if error.code == NSURLErrorCancelled { - XCTFail("Invalid URL/Host for pinning: \(error.localizedDescription) for host: \(host)") - } - } - - expectation.fulfill() - }.resume() - sessionManager.finishTasksAndInvalidate() - } - - wait(for: expectations, timeout: 10.0) - } - - // Test whether or not pinning actually works on a live URL - func testLivePinningFailure() { - let urls = ["https://brave.com"] - - var managers = [URLSession]() - var expectations = [XCTestExpectation]() - for host in urls { - let expectation = XCTestExpectation(description: "Test Pinning Live URLs: \(host)") - expectations.append(expectation) - - guard let hostUrl = URL(string: host), - let normalizedHost = hostUrl.normalizedHost() - else { - - XCTFail("Invalid URL/Host for pinning: \(host)") - expectation.fulfill() - return - } - - let certificateEvaluator = PinningCertificateEvaluator(hosts: [normalizedHost]) - let sessionManager = URLSession(configuration: .default, delegate: certificateEvaluator, delegateQueue: .main) - managers.append(sessionManager) - - sessionManager.dataTask(with: hostUrl) { data, response, error in - if let error = error as NSError?, error.code == NSURLErrorCancelled { - XCTFail("Invalid URL/Host for pinning: \(error.localizedDescription) for host: \(host)") - } - - expectation.fulfill() - }.resume() - sessionManager.finishTasksAndInvalidate() - } - - wait(for: expectations, timeout: 10.0) - } - // Test whether pinning via Brave-Core works // https://github.com/brave/brave-core/blob/master/chromium_src/net/tools/transport_security_state_generator/input_file_parsers.cc func testBraveCoreLivePinningSuccess() { diff --git a/Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/expired.badssl.com-intermediate-ca-1.cer b/Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/expired.badssl.com-intermediate-ca-1.cer deleted file mode 100644 index ad75f0fc5419119a8f36aba983d42c13c6b18c82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1400 zcmXqLVl6RfVu@M6%*4pVB%q%5F6_1E=f2g~3l{xpxu;~n%f_kI=F#?@mywZ&mBAq2 zklTQhjX9KsO_(Xz)lkGh2*lwM=5|a;2`MTqE>UoFGE_5A0f}-8%fdxnD@sy}@)C0t zLP7!*{8CHG^NX?#l?)U>>X?NkG1WOcDg@={ml(*2^BNc!m>L@x8kkrZTSSTT8Xr?e)pb%`8bxG1NBDgg8$LDi;*&sNkGhRFavNnVeXXnV+ZNSXz>i zUzAx=Y0$)k5>||?49rbT{0u;GE~X|XMuv$mLMC0}3fs7bSLN=}^DgNnGqueZ*D4n< z6z&PWf8>{zP``J*K-k{-1x4Ev>rZ;~i!Zl*yn!Y0-G9O9$G^1Mas9H3`8oNSN!3k} z?B{bfw=QU^DqCMT;rg4uyuaq;{JOuP>5E9+&wWRh>HF8utl#=4-MMHl55u+2*V#Ik z-}cXt5m{aL^l{#w8!MhnQn}@vowOtL@2i7*a<+AU=IfpHWOh#G%pVmV2Ci4vKIFA= zP55IpEl_<=`r%W0+p=G|9Y3zHV55BMJr9muOZ--TYIZYkdHv!{qla_4Uy;`An7uyI z8eK6;)mv82xcTluVDn*4H@{rFnZKrr->NYVceLpZe9OHpX1z|b)cx=i_IqB&9+|z~ zb>^eRQ7T(@tqik0pTFwAXT-h_EB=4kvG%3;?_E2-a9jO+;p`;Wd86gD@W-3GA9g0} z=+OvUBO*J8>v`ep^L%%0S58~#vDfDN`mMZ5J_iIF--KCzyS(U8^!80GNaN+QxRXU2R+|OyuVRJ3p-z1^F zk&nQT$}{_BWZ$b06!4^&QvNEyh26!5W#v54$m zU&mzoYuQJemcF&`EM~m9X=HQ-oQP%l85#exumE#yn*l#axiE;&YQPMn3#pvFxko}c zOOHQR=ZkHrGQ%Gu64I;-<~_|vo9`t L|Is>Mz#<<2E=x9y diff --git a/Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/expired.badssl.com-intermediate-ca-2.cer b/Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/expired.badssl.com-intermediate-ca-2.cer deleted file mode 100644 index 7d7e8f271e43b37257755ed76258f83533d2fc98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1548 zcmXqLV&gDqV*apznTe5!NkCgK@72vxvmBAGyxWUwde{wk**LY@JlekVGBWb8G8iCn^}^YVyJDP36f(LR)Wd}1v@G@rxulDre!84 zmSpDVDL9svWaJlRmQ)(ZiSrto7#JBE8Gu2QIIoct%l>tM=+0mehQ3*LT8Ce;an;7{SfZ|+C zO^l2TeTSGNA1;Vo^4h|*SLZL|h0C4?QjT*cvAT*`typk?hpArn%tZKZm_Od`*m7q{&aV)n$~Wm>Ajm<9!v|rP#}>ot^b`a`{cO`cb&R;w)*7}tyvn1 z6Mge1`KvDdRAqWs?T*{h3u_`xd;a@PbPlkOoGh0UsQ4~IWc9z&fC@XG4Ki)Qzp`iS zd0;(7t~KZm^JEo?ywmD|uFneYsX8y=edx9H?Y6oTXLH}_$*LE}-f=Z(SoL_5Blr5f zF73B=K9!zZzwT*k(^4j8Mh3>kO^m6)5J@zU2Zph%GK++PScAy!^>s|Pzm|QpY3W=0 z&SJ)!n?^=g3}itH_*lePL?*1yvYHabePhBap|Iln&ReWBZaoL5JXwB5#{VoVz?9u) zAOun_4B~SbumLG1Mh1uxsw`p#B5WMmY>cd|?97aC7L$QANQ(lCh=Gs+8ygFd$zZ_2 z#+Kg5!^p&F-~*DBXK^)fHgH^EzreQ5I-{hdz)D{~xhO|3IX^c)B|kY4#L$PN5@2FR zNz3r0tOwL!PzbY@k&z|UAlbkg#y4PU(}x>eg2Q0Pl$4O7(&7?lN1$OP1}bpPjBOH7 z&H2g21t_|alOix&>YDMx+oW3Enqy18ignT4uqBKyHLAs>l?!)62EY2D)#s1!8 zy%OuLo^b8Ag>_ERqA5R|r0&1XXX|l2#Ky4d$%{KTw>~Vcc=Jv9&c(YqeNz2PUxn06 z+kW_8XkV{q+@F-)=k4}Rd!2c9{{xSYNlgMzdECN38Q;2aBKhqx`6;Z^yrd>x{QmlU zf6zK!j+=HX76iUL*j{q={z9+*dKSQ|d&3)hX>v9@we zoI;w^(w)n;S|0p5!}FgsLtM84&_yXI1PlgS;nsZ2K} z{M3DAepM>rLb}5B8&2G=>zgv}-Az+5pP)HK(oIrzN5l2gSK2hPTnf~blUB}e%iq6i qLcyi5^45y?uT)oxp9?7JVX#{D_;#|%{2y$EjE|ZWuKdg2yAc4zK1JmK diff --git a/Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/expired.badssl.com-leaf.cer b/Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/expired.badssl.com-leaf.cer deleted file mode 100644 index a82e960ee80f5becb02851dfd6c45c0ac7515d60..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1359 zcmXqLV)Zs?VliI8%*4pVB;fUYs^_m+`|Jhy#g|k(%;YrSW#iOp^Jx3d%gD&h%3#nq z!I0a4lZ`o)g-w{r-N{heKoZ2^5EgPTN=+q3M+KkE+{}{H6hkuu6ObIUur5?CDA-ZKB|kSYGfyEb zF()%6u_QA;Pa!xpxwI%1NEMXy+8$9Pm!?@p-jr%K`p|Z5w_Toor+J8RHl$tX*Gt#Cy-n zDS83s%hF{Md%ih39d)}b)~-Ik8dr6F=(!*Ilr6)gItHh?Mw{e%J43GM}#lO41I}|ajnZ$9k zC%h%Nk@fVmf7u~39MoHn3+yf~c%LcZ$nF|i^E1WXR_E}_n%9%1eA+IaYGq<(WMEv} z#CR1LA{W7tAgjzGVIbBZGGTp|)s!gi8xvj$g%#g--eRS3>$!m}FpOpSSj1RF=Ds^v z-N?eSD`-A>&r)go-{8a~%g@O8pM`~)iFJVi4@kK%h|gre0MV?s*EpwGlQN_`;h5l~N zeDNZRJ*85w%^t`v5fFWI%UHF>dMnp)Mhjc@zY#ajywc6Itl2mHJ@+;4i03`^`#1Z@ ze%?NzVVPNtNc00${@hhO7FJTQ#N^XPhk4o&iz4}DJ>I_-v^(fEZ%=Ay?y<%1uU?sU_TxJB z?@Pi(Y^FG}R&_5lcfbC%_RgsC%f_kI=F#?@mywZ`mBAq2klTQhjX9KsO_(Xz)lkGh z2*lwM=5|a;2`MTqE>UoFGE_5A0f}-8%fdxnD@sy}@)C0tLP7!*{8CHG^NX?#l?)U> z>X?NkG1WOcDg@={ml(*2^BNc!m>L@x8kkrZTSSTT8XjN?rvBY=YQMRKuAJRO$F&7v$3OVnzc89ta_Yo{keA2e z`77qma!f!mNNh*Jt^yXWhK4aLSy*%>fBq$A#xwu(vU-c)21|O=7}o zpAG)XtF8Q7ZrndN|6ykS?(0t1vt6DXU;8fCF}^6Oki&d)W$Kd11yjtwGwAaknpAY_ zf<@77krl2-Vzp|wZ@G5l-I}w)F56FP-*skC{gt>gGb=(_aQkxO{;<->uV*!{yKU#v zPTh9dDPMHcj_TjoB)8i0+;|888*y*(5iaqn(`z5X>60`I?kALv!$}}rd+fLyRuno|Argo zyVp48xF<9RZun_r`~1&0HR0n8VoUc&{b@g6QQ4_D>yZyTSDWdcPxk_kT$;B1PMzPo zg1<7BuX&gPMRfc2iaj`Z->vky%Gx(yoVK)1E)YoHHJ8oQOX9M2)tNx2g;mKHpY2|F zRQr(GK4IQxdz0%btCw@~I;_sR+uwEg+qWJmC)U6#u3zUbPg2YfRdBYM`MhSY%8iv1 z#fv-a!=w!@B(@at&pLa8Z(g6C^uwMR&8OuJKf3rFV=4A5OW;5LW3PBaw%*G#K73KL eD)|+-^sdfJR+9^zH04H-r&SM1-GKayyVQ{_g#FXG*A45?CVUPeb5092!QesMRagJVcey)L>IIoeRp@E^Hk%57+sYw)&YmCID ziyN94m5{y8$jZRn#K_NJ(8S2a)WpchaERs2kDhldhc@2`xLOc#X0pWVm=zhG8s7wjAJZ*tX$k(aAQZpD~WH!rJwPN4)pEoT3+CzARlPvFDqk z(^0p}V(selOI~x$6%0~SEY4~1sSJH`G5ZCZt%qpP+RYlbSN+fXpD@*{Jlpc4%0ao9 z)%OnbyO+c`Dm|R_Q+o2VwMuN7dK-6n%J8VqTl~BGyF(Gfnn@ftd%|0S8(B|3`g7=vcj_j_XH9u4AZFLT>ta&|I%BSt(sa7UtMh3>kzz71T23cViCIbco zWngT|3bM!;NHw9vXcHGw?4l+pV5l-O=rLDSOFp|WxkA}9r+_ixkHLzqtQLoaH6AYz zudWeVJZru~yQi(MnVz!U$*{E%0S|kx8c9R#2aVOQuOWr#B z;nVk2!8!MtJ7@UlGuW|bMk^;6ovJCkdO>^(U+v9TmfzEpU&R+6+tb%P+kNAa%%_Dd zv+ZK`v2biCIy^D(*Xbuo46_|lJDN6|7D}bXzFhkv{eK*zuYK~}oqMu)+|{E(&P+b& z#2WT>Zos9EA2+;Y)T0jfZIV{x6lmyZ@aI^3{UQ$+kCvKKNOr>^{nBfb&IAT){NhbB l*vILu>2dS)+jtL!>Q`DJL9^a(R+_mjWWvhpLhq*e0|0FkL-POt diff --git a/Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/untrusted.badssl.com-leaf.cer b/Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/untrusted.badssl.com-leaf.cer deleted file mode 100644 index cec0bc3a5939395a2b082868a7f0480b75185ee4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1181 zcmXqLVwq{s#MHQenTe5!iId^Y25y!w4|Qz}c-c6$+C196^D;7WvoaVoHX3ppaI!In zvaks=g$5f68wi3p96Vgki8+~R`9*n|iH2eZA|OF_9^T-@JO#I+#JuFp;^cfoexPYU zaV{P`#h10#?CGmkbzKp`}*q^PvGBsE1LC_leM!8x_4Br`2DIk6;F!LhU? zBfluKq|!i6oY&CO(7?db$k@Qb)F4Wn*T@jawSaOBlBnrKQ3GL!6M3}sk`hyji*xjn z^K%WF7?qHNhLM$lxrvdV0VvMJ)WpchaERs2kDhldhc@2`xLOc#X0pWVm=zhG8s7wjAJZ*tX$k(aAQZpD~WH!rJwPN4)pEoT3+CzARlPvFDqk z(^0p}V(selOI~x$6%0~SEY4~1sSJH`G5ZCZt%qpP+RYlbSN+fXpD@*{Jlpc4%0ao9 z)%OnbyO+c`Dm|R_Q+o2VwMuN7dK-6n%J8VqTl~BGyF(Gfnn@ftd%|0S8(B|3`g7=vcj_j_XH9u4AZFLT>ta&|I%BSt(sa7UtMh3>kMg|7p#3C!q!eqc; zpbQL2SwR*V1F0sI&}`yD3R%%!p|EyYusxa}!o1ynB81e{D$SSsBIrEcu;zQ+#FE zHP_~)u6N(f=w|TGJ|g7#0TCU>O-2F*U51U4R_*rLf%&U=bmA9rJ1XDs$hfsKL-UqI z#>J#PV*Ey18z&!QlvwU%+5Pp#sv@S;g%jmBK6K+($T=VD&U!v?&l6wiW38U6{U`gD zEjnd<&-wbLrlTJ#a?0QFr#g2u?40&>)%i1>ON*~wvCb`A>&y1i%jvP&71xheikE+J z+CH|G$l|d(qg9Y_S@@4ui|yjAnw9_mr!@VT8lu!~x-^Y1h3xe@p&X zbOdMMo#)4yM1QDpv&B!3JejUu{v!Oq6qcI|xl?~na{1S>`V|Mi`S-7BXSW_yxz{&) z`2@4xm1%if+m_CnVcK?T&-*356aVB+c!KcfIubUv906z^+f#`wg#__3}mXov>W8JT$bvuta%+8~~}B*zy1X diff --git a/Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/untrusted.badssl.com-root.cer b/Tests/CertificateUtilitiesTests/Certificates/expired.badssl.com/untrusted.badssl.com-root.cer deleted file mode 100644 index d9d5f6c24589efbe9e72f961f83d1cc45518ba31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1666 zcmXqLVyiP~Vo6)T%*4pV#K|yy!Jl8(pKAOy;AP{~YV&CO&dbQi&B|cV*l5UYz{$oO z%EBhh6dG(OY#<2YaPV+BC+1|P;@bG7uTxl3nFKfM3x1m(R zyDzT}-fEtGbIpExwF2eoHB}7yqyM$}(z7ZaJfKLDG75 zRItPTRWZi)?F9iT({}Q+++j4E^_TO>%f{RNpQXHKe&*Dic(HQ*=D&w_g-CQxDxa*C zzBy>_s=#%J+#hjhFrQ2+dUMfs<&9s*h3>Alym!N3(~e({MZRpAm8LXn&XUH>&$~AA zv&4Ew7j_oknco_Eg!4RK^2xCCVw-s`M^~yiFJYX``f!qC;bO*}Ng{I!^c6R!KWF=q zuUh=((%y^q)m5%3lW+}4W*tEf({mRFVecPwp=*g&QT$umj=95jWKU)3F8vc3h zGgD}=3jA@Umm@_eqWmVKf&|~3TtfuXnmfJjCCT2zk#>I`_3>rTh$O7|#EFX&)i%9vS z%ai|?-pkGV_xAVn#X7(&w+$q(%+k2dpmB8rRy7M7+m|%942Fc?gj96!fZQ$2!fL?G z$oSuY8^q&hVF4B}Y{*#ySgHWC1|vg2@Ku{%CKvq|{$*%Pd#zWpwdBEnm5nYp=h+7u zeVzWJ*U{?K-V2`v^1rbh)l)NFnY*ga;nK{~ST@gkOW%J-_XSwwB}VTNT@*Uu#1fbA ziTZ(4Zysy>tsBTw=CH zebal(c2-B$gl)T`?rZJL>aCc2Xk%;hndpu*0hvGLTZ(TUYqpHYUB95O?cUk?4*SN3 zaZ3ZdAML$+|JAMC<)QWW*5>F>IJi z*-JY7{#-1}@G-wJ=a2Z=xIJ$s)=mG*!PT*unf0e#^P#`D4o~{qs`v$~zO z-I)G@^nPdcHi^t<65W>(?!*JJQngWACc@UcRNW$9%)|_9UZ^A#7q! z>jX5Wi1D#pDq$2mad%qm0#|#vm=BA@zE9Nqs1YXYD;lEvg1LR!!nw=W8-(j^6)IA{ v@iJ^ilBeK}w`;wY+PLXncfFx$^5NR@W8RN1Elfz$xGVqU`w4Y}iW$!U-rlqP diff --git a/Tests/CertificateUtilitiesTests/Certificates/intermediate.cer b/Tests/CertificateUtilitiesTests/Certificates/intermediate.cer deleted file mode 100644 index 03de1b4d5215144b4aaecad68de4a322ebf28644..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1334 zcmXqLVl^^oVv$V(_(zpKZfCvL#HcqWJkGAi;jEvl@3k4t8HrgLghiGpWdvZ17bI7paVSfISTTrUMEl?;^9 zOU}`5W#@bq@2uT zkS)QfMP;c)3PHh+K)V!-6m%8b4Go$Ym5}|+$jZRn#K_M86z5`UVq|1^#PE0N{rH_0 zg`Tf>O}=s1*Z5A1$g2YVgRg?EPtOZH5V9$SZP|3gC*m!qD)iG2U+R4LMWXQ3Tgl^j zOo!UF)Nj?-!S5PhtKQywA!~r1_}Z?fpGppE>B9`oI{VnDkNa zM4ZoIB?~_VR4UEI&-R*`onbM3lcUpnquHqK|3>5W>tQC%x} zb+qu0M{o46^YeWOn#_K32~SA!xpztN-ToKyRQaPh52<`(Sn(^LHR2wdtkhnIwv4|s zw@x=xt2X66QCqpm?dRNE2X8R0WWK?lWLK|$i|yuRE01)R*-4*l60chLhOZ0ysy!$5 zITJG@1LNW*#+Seld1@dF3|d(}7BLo)fY8;>3)ipo(2hE|sJ)ZrNfqCzTL$tVX=N4( z1F;5?jdq7KnSbneXS^~)O^So*eR7KSVFNyp0)9ru|12!P^w$P4PnAW?K!lA$n~jl` zm7SRp&SEkU0x1^;Dd#X?15!+k3g93WJJym zz#;&c9T*vw#pMKSOO4U?vtReB;KiM{=GFcW{t4H;^h;vi`nN=InX~MnH#hlBSW9Ml zNll8Dsc7(U{&wiy>)2+_OPZF?o|K1nO%OV*meO>5VzBqW^|IjHi zVV6&pq-f@SS1~VJJ9n#{=58IQ(B$GdZrKqgWj+6*mnX|cI=_`ayz$PH`5)bPKXudI zWdHo+x957i-7EEYzu&Rg>Aopa@#po&>2u;8?p|h++Y#|aM&kI(vpYq-^#6qIOtbnD V^5fYo<|?}?9;U65F_r?=#Q?UH(Y^ox diff --git a/Tests/CertificateUtilitiesTests/Certificates/root.cer b/Tests/CertificateUtilitiesTests/Certificates/root.cer deleted file mode 100644 index dae0196507d9166bbf9de6ed93ed5bd91d2a6f7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 969 zcmXqLVm@ln#I$w+GZP~dlK|727_}^JhuQtym7mtRsg@h?vTHrk&& ztLnL}Q0ws3DJJQQxMViOXD)Ntcx3yg{tGjftaOaDx_oEKHJjTd7E8R&_^LL2_gWe( zWby8^XKvxdy5w!Em&G4m((=PUDRAG9qi=3oOnS`rlw%^#5e>)C->0KGMe7P*nC|y2 z;KS3U1MC5c*hY*|CU8utq&? zd&l;QPp7z6SghtsIkZ15c52JPg{pGxu~Grt3PNWbcjs{jTI}#X&BV;ez_>WsAklyi z7{#*ujEw(TSb)i@&43@o7Y6ZJ4VZzHfh)yU1V#fRgNzj0^gGw*%(v-CW8(-{yshZ|Lo0pNehH&82Z8W| zU7xFN3a0j%{+jp4b?dyFK8D^qCcbdi>Dyd!?)Ky0%ED_;6{j%X3T>aIlJc!9?aaM7 z=4V%!Y|Nakd}3aOl6Haf<4||QM9KFNM_cv%48AQe6jMI&{86fy@#+0i(hF>VoQmr< z>`vh5)a7A|3EF<-C)bRNqVe;E_SKjrTkLgNQvXCvo9oE*Ox~A0j}8cg?>JE=G%@s( zN9K(^T1?UWE>{!`Z-3cUpcXqJVcs5ZaZdd$r{$b8PO_^XycF~OmEz6}p*c2l_Rss% i5HmaZ>>Kx0s_N+r%s(?U)rMSO`QxRY@Z$0p@?HQubYDLJ diff --git a/Tests/GrowthTests/DAUTests.swift b/Tests/GrowthTests/DAUTests.swift index 5e4cf17f043d..28fd5dd491b8 100644 --- a/Tests/GrowthTests/DAUTests.swift +++ b/Tests/GrowthTests/DAUTests.swift @@ -39,7 +39,7 @@ class DAUTests: XCTestCase { let devExpected = URLQueryItem(name: "channel", value: "developer") XCTAssertEqual(dau.channelParam(for: .dev), devExpected) - let debugExpected = URLQueryItem(name: "channel", value: "invalid") + let debugExpected = URLQueryItem(name: "channel", value: "developer") XCTAssertEqual(dau.channelParam(for: .debug), debugExpected) let enterpriseExpected = URLQueryItem(name: "channel", value: "invalid") @@ -322,12 +322,6 @@ class DAUTests: XCTestCase { XCTAssertEqual(singleDigitTest.mondayOfCurrentWeekFormatted, "2019-02-04") } - func testNoPingOnDevelopmentBuild() { - XCTAssertTrue(AppConstants.buildChannel == .debug) - - let dau = DAU() - XCTAssertFalse(dau.sendPingToServer()) - } func testMigratingInvalidWeekOfInstallPref() throws { // (stored, fixed)