Skip to content

Commit

Permalink
Malware protection 1: Rename PhishingDetection to MaliciousSiteProtec…
Browse files Browse the repository at this point in the history
  • Loading branch information
mallexxx authored Dec 3, 2024
1 parent c307b9e commit 1524c05
Show file tree
Hide file tree
Showing 61 changed files with 1,416 additions and 1,380 deletions.
196 changes: 103 additions & 93 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/BrowserServicesKit",
"state" : {
"revision" : "59f8fb2f850f8a0482c604d07396e01e8de59d21",
"version" : "216.0.1"
"revision" : "befc1f1094353d8d88a99ac08885684c978b2016",
"version" : "217.0.0"
}
},
{
Expand Down
25 changes: 25 additions & 0 deletions DuckDuckGo/Common/Extensions/FileManagerExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,31 @@ import os.log

extension FileManager {

#if !SANDBOX_TEST_TOOL
func configurationDirectory() -> URL {
let fm = FileManager.default

guard let dir = fm.containerURL(forSecurityApplicationGroupIdentifier: Bundle.main.appGroup(bundle: .appConfiguration)) else {
fatalError("Failed to get application group URL")
}
let subDir = dir.appendingPathComponent("Configuration")

var isDir: ObjCBool = false
if !fm.fileExists(atPath: subDir.path, isDirectory: &isDir) || !isDir.boolValue {
if !isDir.boolValue {
try? fm.removeItem(at: subDir)
}
do {
try fm.createDirectory(at: subDir, withIntermediateDirectories: true, attributes: nil)
isDir = true
} catch {
fatalError("Failed to create directory at \(subDir.path)")
}
}
return subDir
}
#endif

@discardableResult
func moveItem(at srcURL: URL, to destURL: URL, incrementingIndexIfExists flag: Bool, pathExtension: String? = nil) throws -> URL {
guard srcURL != destURL else { return destURL }
Expand Down
5 changes: 5 additions & 0 deletions DuckDuckGo/Common/Extensions/URLExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ extension URL {
var isSettingsURL: Bool {
isChild(of: .settings) && (pathComponents.isEmpty || PreferencePaneIdentifier(url: self) != nil)
}

var isErrorURL: Bool {
return navigationalScheme == .duck && host == URL.error.host
}

#endif

enum Invalid {
Expand Down
9 changes: 4 additions & 5 deletions DuckDuckGo/Common/Localizables/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,7 @@ struct UserText {
static let webProcessCrashPageHeader = NSLocalizedString("page.crash.header", value: "This webpage has crashed.", comment: "Error page heading text shown when a Web Page process had crashed")
static let webProcessCrashPageMessage = NSLocalizedString("page.crash.message", value: "Try reloading the page or come back later.", comment: "Error page message text shown when a Web Page process had crashed")
static let sslErrorPageTabTitle = NSLocalizedString("ssl.error.page.tab.title", value: "Warning: Site May Be Insecure", comment: "Title shown in an error page tab that warn users of security risks on a website due to SSL issues")
static let phishingErrorPageTabTitle = NSLocalizedString("phishing.error.page.tab.title", value: "Warning: Site May Be Deceptive", comment: "Title shown in an error page tab that warn users of security risks on a website that has been flagged as malicious.")

static let phishingErrorPageTabTitle = NSLocalizedString("phishing.error.page.tab.title", value: "Warning: Site May Be Deceptive", comment: "Title shown in an error page tab that warn users of security risks on a website that has been flagged as Phishing.")

static let openSystemPreferences = NSLocalizedString("open.preferences", value: "Open System Preferences", comment: "Open System Preferences (to re-enable permission for the App) (up to and including macOS 12")
static let openSystemSettings = NSLocalizedString("open.settings", value: "Open System Settings…", comment: "This string represents a prompt or button label prompting the user to open system settings")
Expand Down Expand Up @@ -446,9 +445,9 @@ struct UserText {

static let downloadsOpenPopupOnCompletion = NSLocalizedString("downloads.open.on.completion", value: "Automatically open the Downloads panel when downloads complete", comment: "Checkbox to open a Download Manager popover when downloads are completed")

static let phishingDetectionHeader = NSLocalizedString("phishing-detection.enabled.header", value: "Malicious Site Protection", comment: "Header for phishing site protection section in the settings page")
static let phishingDetectionIsEnabled = NSLocalizedString("phishing-detection.enabled.checkbox", value: "Allow DuckDuckGo to warn you before loading a webpage that has been flagged as malicious or fraudulent.", comment: "Checkbox that enables or disables the phishing detection feature in the browser")
static let phishingDetectionEnabledWarning = NSLocalizedString("phishing-detection.enabled.warning", value: "Disabling this feature can put your personal information at risk. Only do so if you fully understand the risk involved.", comment: "A description box to warn users away from disabling phishing protection")
static let maliciousSiteDetectionHeader = NSLocalizedString("phishing-detection.enabled.header", value: "Malicious Site Protection", comment: "Header for phishing site protection section in the settings page")
static let maliciousSiteDetectionIsEnabled = NSLocalizedString("phishing-detection.enabled.checkbox", value: "Allow DuckDuckGo to warn you before loading a webpage that has been flagged as malicious or fraudulent.", comment: "Checkbox that enables or disables the phishing detection feature in the browser")
static let maliciousDetectionEnabledWarning = NSLocalizedString("phishing-detection.enabled.warning", value: "Disabling this feature can put your personal information at risk. Only do so if you fully understand the risk involved.", comment: "A description box to warn users away from disabling malicious site protection")

// MARK: Password Manager
static let passwordManagementAllItems = NSLocalizedString("passsword.management.all-items", value: "All Items", comment: "Used as title for the Autofill All Items option")
Expand Down
50 changes: 45 additions & 5 deletions DuckDuckGo/Common/Utilities/URLTokenValidator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import Foundation
import CommonCrypto
import SpecialErrorPages

/**
`URLTokenValidator` is responsible for generating and validating URL signatures to securely pass URLs around in Special Pages.
Expand Down Expand Up @@ -60,10 +61,10 @@ public class URLTokenValidator {
let urlString = url.absoluteString
let timestamp = String(Int(Date().timeIntervalSince1970))
let dataToSign = urlString + timestamp
let data = dataToSign.data(using: .utf8)!
let data = dataToSign.utf8data

let hmacData = hmacSHA256(data: data, key: secretKey)
let signature = URLTokenValidator.base64URLEncode(data: hmacData)
let signature = URLTokenValidator.base64URLEncode(hmacData)
return "\(signature):\(timestamp)"
}

Expand Down Expand Up @@ -95,7 +96,7 @@ public class URLTokenValidator {
let data = dataToSign.data(using: .utf8)!

let expectedHmacData = hmacSHA256(data: data, key: secretKey)
let expectedSignature = URLTokenValidator.base64URLEncode(data: expectedHmacData)
let expectedSignature = URLTokenValidator.base64URLEncode(expectedHmacData)

return expectedSignature == signature
}
Expand Down Expand Up @@ -124,7 +125,7 @@ public class URLTokenValidator {
- Parameter data: The data to be encoded.
- Returns: A URL-safe base64-encoded string.
*/
public static func base64URLEncode(data: Data) -> String {
public static func base64URLEncode(_ data: Data) -> String {
let base64String = data.base64EncodedString()
let base64URLString = base64String
.replacingOccurrences(of: "+", with: "-")
Expand All @@ -133,13 +134,18 @@ public class URLTokenValidator {
return base64URLString
}

public static func base64URLEncode(_ url: URL) -> String {
let data = url.absoluteString.utf8data
return base64URLEncode(data)
}

/**
Decodes a URL-safe base64-encoded string to data.

- Parameter base64URLString: The URL-safe base64-encoded string to be decoded.
- Returns: The decoded data, or nil if the string is not a valid base64-encoded string.
*/
public static func base64URLDecode(base64URLString: String) -> Data? {
public static func base64URLDecode(_ base64URLString: String) -> Data? {
// Convert Base64URL string to Base64 string
var base64String = base64URLString
.replacingOccurrences(of: "-", with: "+")
Expand All @@ -155,3 +161,37 @@ public class URLTokenValidator {
return Data(base64Encoded: base64String)
}
}

extension URL {
private enum Parameter {
static let reason = "reason"
static let failingUrl = "url"
static let token = "token"
}

init?(base64UrlString: String) {
guard let decodedData = URLTokenValidator.base64URLDecode(base64UrlString),
let decodedString = String(data: decodedData, encoding: .utf8),
let url = URL(string: decodedString) else { return nil }
self = url
}

var specialErrorPageParameters: (failingUrl: URL, reason: SpecialErrorKind, token: String)? {
guard isErrorURL,
let reason = getParameter(named: Parameter.reason).flatMap(SpecialErrorKind.init(rawValue:)),
let failingUrl = getParameter(named: Parameter.failingUrl).flatMap(URL.init(base64UrlString:)),
let token = getParameter(named: Parameter.token) else { return nil }

return (failingUrl: failingUrl, reason: reason, token: token)
}

static func specialErrorPage(failingUrl: URL, kind: SpecialErrorKind) -> URL {
let encodedUrl = URLTokenValidator.base64URLEncode(failingUrl)
let token = URLTokenValidator.shared.generateToken(for: failingUrl)
return .error.appendingParameters([
Parameter.failingUrl: encodedUrl,
Parameter.reason: kind.rawValue,
Parameter.token: token
])
}
}
50 changes: 17 additions & 33 deletions DuckDuckGo/Configuration/ConfigurationStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,21 @@ import Configuration
import Persistence
import PixelKit

final class ConfigurationStore: ConfigurationStoring {
private extension Configuration {
var fileName: String {
switch self {
case .bloomFilterBinary: "smarterEncryption.bin"
case .bloomFilterExcludedDomains: "smarterEncryptionExclusions.json"
case .bloomFilterSpec: "smarterEncryptionSpec.json"
case .surrogates: "surrogates.txt"
case .privacyConfiguration: "macos-config.json"
case .trackerDataSet: "tracker-radar.json"
case .remoteMessagingConfig: "remote-messaging-config.json"
}
}
}

private static let fileLocations: [Configuration: String] = [
.bloomFilterBinary: "smarterEncryption.bin",
.bloomFilterExcludedDomains: "smarterEncryptionExclusions.json",
.bloomFilterSpec: "smarterEncryptionSpec.json",
.surrogates: "surrogates.txt",
.privacyConfiguration: "macos-config.json",
.trackerDataSet: "tracker-radar.json",
.remoteMessagingConfig: "remote-messaging-config.json"
]
final class ConfigurationStore: ConfigurationStoring {

private enum Etag {
static let configStorageTrackerRadarEtag = "config.storage.trackerradar.etag"
Expand Down Expand Up @@ -200,29 +204,9 @@ final class ConfigurationStore: ConfigurationStoring {
Logger.config.info("remoteMessagingConfig \(self.remoteMessagingConfigEtag ?? "", privacy: .public)")
}

func fileUrl(for config: Configuration) -> URL {
let fm = FileManager.default

guard let dir = fm.containerURL(forSecurityApplicationGroupIdentifier: Bundle.main.appGroup(bundle: .appConfiguration)) else {
fatalError("Failed to get application group URL")
}
let subDir = dir.appendingPathComponent("Configuration")

var isDir: ObjCBool = false
if !fm.fileExists(atPath: subDir.path, isDirectory: &isDir) {
do {
try fm.createDirectory(at: subDir, withIntermediateDirectories: true, attributes: nil)
isDir = true
} catch {
fatalError("Failed to create directory at \(subDir.path)")
}
}

if !isDir.boolValue {
fatalError("Configuration folder at \(subDir.path) is not a directory")
}

return subDir.appendingPathComponent(Self.fileLocations[config]!)
func fileUrl(for configuration: Configuration) -> URL {
let dir = FileManager.default.configurationDirectory()
return dir.appendingPathComponent(configuration.fileName)
}

}
2 changes: 1 addition & 1 deletion DuckDuckGo/ContentBlocker/AppTrackerDataSetProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import Foundation
import BrowserServicesKit

final class AppTrackerDataSetProvider: EmbeddedDataProvider {
struct AppTrackerDataSetProvider: EmbeddedDataProvider {

public struct Constants {
public static let embeddedDataETag = "\"79fa338d0a84c5b7d1fd67fd36cb7c39\""
Expand Down
28 changes: 15 additions & 13 deletions DuckDuckGo/ErrorPage/ErrorPageHTMLFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,27 @@
// limitations under the License.
//

import BrowserServicesKit
import ContentScopeScripts
import Foundation
import PhishingDetection
import MaliciousSiteProtection
import Navigation
import SpecialErrorPages

protocol ErrorPageHTMLTemplating {
static var htmlTemplatePath: String { get }
func makeHTMLFromTemplate() -> String
}
enum ErrorPageHTMLFactory {

static func html(for error: WKError, featureFlagger: FeatureFlagger, header: String? = nil) -> String {
switch error as NSError {
case is MaliciousSiteError:
return SpecialErrorPageHTMLTemplate.htmlFromTemplate

final class ErrorPageHTMLFactory {
static func html(for error: Error, url: URL, errorCode: Int? = nil, header: String? = nil) -> String {
let defaultHeader = UserText.errorPageHeader
let nsError = error as NSError
let wkError = WKError(_nsError: nsError)
switch wkError.code {
case WKError.Code(rawValue: PhishingDetectionError.detected.rawValue):
case is URLError where error.isServerCertificateUntrusted && featureFlagger.isFeatureOn(.sslCertificatesBypass):
return SpecialErrorPageHTMLTemplate.htmlFromTemplate

default:
return ErrorPageHTMLTemplate(error: wkError, header: header ?? defaultHeader).makeHTMLFromTemplate()
return ErrorPageHTMLTemplate(error: WKError(_nsError: error as NSError),
header: header ?? UserText.errorPageHeader).makeHTMLFromTemplate()
}
}

}
2 changes: 1 addition & 1 deletion DuckDuckGo/Fire/Model/TabCleanupPreparer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Foundation
import PixelKit

protocol TabDataClearing {
func prepareForDataClearing(caller: TabCleanupPreparer)
@MainActor func prepareForDataClearing(caller: TabCleanupPreparer)
}

/**
Expand Down
18 changes: 11 additions & 7 deletions DuckDuckGo/Fire/View/FireViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,18 @@ final class FireViewController: NSViewController {
fireViewModel.isAnimationPlaying = true

fireAnimationView?.currentProgress = 0
let completion = { [fireViewModel] in
fireViewModel.isAnimationPlaying = false
continuation.resume()
}
fireAnimationView?.play(fromProgress: fireAnimationBeginning, toProgress: fireAnimationEnd) { [weak self] _ in
defer { completion() }
guard let self = self else { return }

self.fireViewModel.isAnimationPlaying = false
self.progressIndicatorWrapper.isHidden = false
self.fakeFireButton.isHidden = false
continuation.resume()
} ?? continuation.resume() // Resume immediately if fireAnimationView is nil

} ?? completion() // Resume immediately if fireAnimationView is nil
}
}

Expand All @@ -188,12 +192,12 @@ final class FireViewController: NSViewController {

fireViewModel.fire.fireAnimationDidStart()
fireAnimationView?.currentProgress = 0
fireAnimationView?.play(fromProgress: fireAnimationBeginning, toProgress: fireAnimationEnd) { [weak self] _ in
guard let self = self else { return }

self.fireViewModel.isAnimationPlaying = false
fireAnimationView?.play(fromProgress: fireAnimationBeginning, toProgress: fireAnimationEnd) { [weak self, fireViewModel] _ in
fireViewModel.isAnimationPlaying = false
fireViewModel.fire.fireAnimationDidFinish()

guard let self = self else { return }

// If not finished yet, present the progress indicator
if self.fireViewModel.fire.burningData != nil {

Expand Down
6 changes: 3 additions & 3 deletions DuckDuckGo/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -45023,7 +45023,7 @@
}
},
"phishing-detection.enabled.warning" : {
"comment" : "A description box to warn users away from disabling phishing protection",
"comment" : "A description box to warn users away from disabling malicious site protection",
"extractionState" : "extracted_with_value",
"localizations" : {
"de" : {
Expand Down Expand Up @@ -45083,7 +45083,7 @@
}
},
"phishing.error.page.tab.title" : {
"comment" : "Title shown in an error page tab that warn users of security risks on a website that has been flagged as malicious.",
"comment" : "Title shown in an error page tab that warn users of security risks on a website that has been flagged as Phishing.",
"extractionState" : "extracted_with_value",
"localizations" : {
"de" : {
Expand Down Expand Up @@ -64606,4 +64606,4 @@
}
},
"version" : "1.0"
}
}
Loading

0 comments on commit 1524c05

Please sign in to comment.