Skip to content

Commit

Permalink
Proof-of-concept for MAC logging anonymization
Browse files Browse the repository at this point in the history
  • Loading branch information
halo committed Jan 30, 2024
1 parent c92ee17 commit ac73a44
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 46 deletions.
12 changes: 12 additions & 0 deletions LinkLiar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
C23F0DD12B6977A800EE94F5 /* MACParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23F0DCF2B6977A800EE94F5 /* MACParser.swift */; };
C23F0DD22B6977A800EE94F5 /* MACParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23F0DCF2B6977A800EE94F5 /* MACParser.swift */; };
C23F0DD42B6979D000EE94F5 /* MACParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23F0DD32B6979D000EE94F5 /* MACParserTest.swift */; };
C23F0DD62B697FE300EE94F5 /* MACAnonymizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23F0DD52B697FE300EE94F5 /* MACAnonymizer.swift */; };
C23F0DD72B697FE300EE94F5 /* MACAnonymizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23F0DD52B697FE300EE94F5 /* MACAnonymizer.swift */; };
C23F0DD82B697FE300EE94F5 /* MACAnonymizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23F0DD52B697FE300EE94F5 /* MACAnonymizer.swift */; };
C23F0DDA2B69801900EE94F5 /* MACAnonymizerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23F0DD92B69801900EE94F5 /* MACAnonymizerTest.swift */; };
C2456FAB2B3AF888007D7AEA /* TroubleshootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2456FAA2B3AF888007D7AEA /* TroubleshootView.swift */; };
C2456FAD2B3AF976007D7AEA /* FallbackPolicyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2456FAC2B3AF976007D7AEA /* FallbackPolicyView.swift */; };
C2456FAF2B3AF99A007D7AEA /* InterfacePolicyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2456FAE2B3AF99A007D7AEA /* InterfacePolicyView.swift */; };
Expand Down Expand Up @@ -236,6 +240,8 @@
C23F0DCD2B66BD6900EE94F5 /* InterfaceTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterfaceTest.swift; sourceTree = "<group>"; };
C23F0DCF2B6977A800EE94F5 /* MACParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MACParser.swift; path = Models/MACParser.swift; sourceTree = "<group>"; };
C23F0DD32B6979D000EE94F5 /* MACParserTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MACParserTest.swift; sourceTree = "<group>"; };
C23F0DD52B697FE300EE94F5 /* MACAnonymizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MACAnonymizer.swift; path = Models/MACAnonymizer.swift; sourceTree = "<group>"; };
C23F0DD92B69801900EE94F5 /* MACAnonymizerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MACAnonymizerTest.swift; sourceTree = "<group>"; };
C2456FAA2B3AF888007D7AEA /* TroubleshootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TroubleshootView.swift; path = Sections/TroubleshootView.swift; sourceTree = "<group>"; };
C2456FAC2B3AF976007D7AEA /* FallbackPolicyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FallbackPolicyView.swift; path = Sections/FallbackPolicyView.swift; sourceTree = "<group>"; };
C2456FAE2B3AF99A007D7AEA /* InterfacePolicyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InterfacePolicyView.swift; path = Sections/InterfacePolicyView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -400,6 +406,7 @@
C23F0DC62B6692B100EE94F5 /* MACTest.swift */,
C23F0DCD2B66BD6900EE94F5 /* InterfaceTest.swift */,
C23F0DD32B6979D000EE94F5 /* MACParserTest.swift */,
C23F0DD92B69801900EE94F5 /* MACAnonymizerTest.swift */,
);
name = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -458,6 +465,7 @@
C2456FD02B3B16F7007D7AEA /* Vendor.swift */,
C2456FD12B3B16F7007D7AEA /* Version.swift */,
C23F0DCF2B6977A800EE94F5 /* MACParser.swift */,
C23F0DD52B697FE300EE94F5 /* MACAnonymizer.swift */,
);
name = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -860,6 +868,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C23F0DD62B697FE300EE94F5 /* MACAnonymizer.swift in Sources */,
C23F0DC82B66AE3800EE94F5 /* Command.swift in Sources */,
C2969F182B278E2A00A9BB72 /* DiagnoseInterfaceView.swift in Sources */,
C2969F152B278E2A00A9BB72 /* SettingsDetailView.swift in Sources */,
Expand Down Expand Up @@ -966,6 +975,7 @@
C2AA40AC2B59A0C4007CD6CB /* SSID.swift in Sources */,
C280C0452B5836E700C653F0 /* Command.swift in Sources */,
C24570102B3DB69E007D7AEA /* Reader.swift in Sources */,
C23F0DDA2B69801900EE94F5 /* MACAnonymizerTest.swift in Sources */,
C245700F2B3DB696007D7AEA /* Interface.swift in Sources */,
C245700C2B3DB59D007D7AEA /* Synchronization.swift in Sources */,
C245700B2B3DB516007D7AEA /* Identifiers.swift in Sources */,
Expand All @@ -977,6 +987,7 @@
C2ADC8532B22293B002A2905 /* ConfigTests.swift in Sources */,
C23F0DD22B6977A800EE94F5 /* MACParser.swift in Sources */,
C280C0422B58363200C653F0 /* Airport.Connection.swift in Sources */,
C23F0DD82B697FE300EE94F5 /* MACAnonymizer.swift in Sources */,
C288E2172AE7AF2D0098FF39 /* ListenerProtocol.swift in Sources */,
C245700E2B3DB5B4007D7AEA /* AdvisorTests.swift in Sources */,
C280C0472B584D6900C653F0 /* AirportConnectionTests.swift in Sources */,
Expand Down Expand Up @@ -1012,6 +1023,7 @@
C2456FCB2B3B16D4007D7AEA /* Interfaces.swift in Sources */,
C280C0412B58363200C653F0 /* Airport.Connection.swift in Sources */,
C2456FD92B3B16F7007D7AEA /* Version.swift in Sources */,
C23F0DD72B697FE300EE94F5 /* MACAnonymizer.swift in Sources */,
C280C04F2B58511800C653F0 /* Collection+safeSubscript.swift in Sources */,
C2456FF12B3B1809007D7AEA /* TimeObserver.swift in Sources */,
C280C04C2B58511800C653F0 /* CFArray+Sequence.swift in Sources */,
Expand Down
6 changes: 3 additions & 3 deletions LinkLiar/Views/Menu/InterfaceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct InterfaceView: View {
Button(action: {
copy(interface.softMAC?.address ?? "??:??:??:??:??:??")
}, label: {
Text(interface.softMAC?.address ?? "??:??:??:??:??:??")
Text(interface.hardMAC.anonymous(state.config.general.isAnonymized))
.font(.system(.body, design: .monospaced, weight: .light))
}).buttonStyle(.plain)
}
Expand All @@ -40,9 +40,9 @@ struct InterfaceView: View {
.opacity(0.5)
.font(.system(.footnote))
Button(action: {
copy(interface.hardMAC.humanReadable)
copy(interface.hardMAC.address)
}, label: {
Text(interface.hardMAC.humanReadable)
Text(interface.hardMAC.anonymous(state.config.general.isAnonymized))
.font(.system(.footnote, design: .monospaced))
.opacity(0.5)
}).buttonStyle(.plain)
Expand Down
6 changes: 3 additions & 3 deletions LinkLiar/Views/Settings/AccessPointsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct AccessPointsView: View {
TableColumn("SSID", value: \.ssid)
.width(250)
TableColumn("MAC") { accessPointPolicy in
Text(accessPointPolicy.softMAC.humanReadable)
Text(accessPointPolicy.softMAC.address)
.font(.system(.body, design: .monospaced, weight: .light))
}.width(150)
}.contextMenu(forSelectionType: Config.AccessPointPolicy.ID.self) { _ in
Expand Down Expand Up @@ -77,7 +77,7 @@ struct AccessPointsView: View {
}

private func addSsid() {
Log.debug("Adding SSID to Interface \(interface.hardMAC.humanReadable)")
Log.debug("Adding SSID to Interface \(interface.hardMAC.address)")
Config.Writer(state).addInterfaceSsid(interface: interface, ssid: newSsid, address: newMAC)
}

Expand All @@ -87,7 +87,7 @@ struct AccessPointsView: View {
Log.debug("SSID definition not found")
return
}
Log.debug("Removing `\(accessPointPolicy.ssid)` from Interface \(interface.hardMAC.humanReadable)")
Log.debug("Removing `\(accessPointPolicy.ssid)` from Interface \(interface.hardMAC.address)")
Config.Writer(state).removeInterfaceSsid(interface: interface, ssid: accessPointPolicy.ssid)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct SettingsInterfaceHeadlineView: View {
.font(.system(.body, design: .monospaced))
.opacity(0.5)
}
Text(interface.hardMAC.humanReadable(config: state.config))
Text(interface.hardMAC.address)
.font(.system(.body, design: .monospaced))
.opacity(0.5)
}
Expand Down
27 changes: 27 additions & 0 deletions LinkLiarTests/MACAnonymizerTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) halo https://github.com/halo/LinkLiar
// SPDX-License-Identifier: MIT

import XCTest
@testable import LinkLiar

class MACAnonymizerTest: XCTestCase {
let userDefaultsSuiteName = "io.github.halo.LinkLiar.MACAnonymizerTest"

func testAnonymization() {
let input = MAC("00:01:02:00:aa:ff")!
let userDefaults = UserDefaults(suiteName: userDefaultsSuiteName)!
userDefaults.set("00:03:05:00:01:01", forKey: "seed")
let output = MACAnonymizer.anonymize(input, stubUserDefaults: userDefaults)

XCTAssertEqual("00:04:07:00:ab:f0", output)
}

func testAnonymizationModulo() {
let input = MAC("ff:ff:ff:ff:ff:ff")!
let userDefaults = UserDefaults(suiteName: userDefaultsSuiteName)!
userDefaults.set("11:00:00:00:10:01", forKey: "seed")
let output = MACAnonymizer.anonymize(input, stubUserDefaults: userDefaults)

XCTAssertEqual("00:ff:ff:ff:0f:f0", output)
}
}
17 changes: 2 additions & 15 deletions LinkTools/Config/General.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,11 @@ extension Config {
return restriction != false
}

///
/// Queries whether MAC addresses should be anonymized in GUI and logs.
/// This is no by default. You can turn it on by adding the key.
///
var isAnonymized: Bool {
false
// anonymizationSeed.isValid
}

// TODO: How to solve this?
/**
* Queries a seed used for anonymizing MAC addresses shown in GUI and logs.
*/
var anonymizationSeed: MAC {
guard let seed = self.dictionary["anonymous"] as? String else {
return MAC(address: "")
}

return MAC(address: seed)
self.dictionary[Config.Key.anonymize.rawValue] as? Bool ?? false
}
}
}
1 change: 1 addition & 0 deletions LinkTools/Config/Reader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ extension Config {
case action
case apple
case address
case anonymize
case theDefault = "default"
case except
case skipRerandom = "skip_rerandom"
Expand Down
33 changes: 13 additions & 20 deletions LinkTools/Models/MAC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,24 @@ struct MAC: Equatable {

// MARK: Instance Properties

func humanReadable(config: Config.Reader) -> String {
guard config.general.isAnonymized else {
return humanReadable
}
let address = config.general.anonymizationSeed
let otherIntegers = address.integers
let newIntegers = integers.enumerated().map { ($1 + otherIntegers[$0]) % 16 }
let newAddress = newIntegers.map { String($0, radix: 16) }.joined()
return Self(newAddress)?.address ?? "??:??:??:??:??:??"
}

var humanReadable: String {
// if Config.instance.settings.anonymizationSeed.isValid {
// return add(Config.instance.settings.anonymizationSeed).formatted
// } else {
address
// }
}

var prefix: String {
address.components(separatedBy: ":").prefix(3).joined(separator: ":")
}

var integers: [UInt8] {
address.map { UInt8(String($0), radix: 16)! }
address.split(separator: ":")
.joined()
.map { UInt8(String($0), radix: 16)! }
}

// MARK: Instance Methods

func anonymous(_ anonymize: Bool) -> String {
if anonymize {
return MACAnonymizer.anonymize(self)
} else {
return address
}
}

// MARK: Private Instance Properties
Expand Down
70 changes: 70 additions & 0 deletions LinkTools/Models/MACAnonymizer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) halo https://github.com/halo/LinkLiar
// SPDX-License-Identifier: MIT

import Foundation

/// Checks a potential MAC address for validity and normalizes it.
///
struct MACAnonymizer {
// MARK: - Class Methods

static func anonymize(_ input: MAC, stubUserDefaults: UserDefaults = .standard) -> String {
self.init(input, userDefaults: stubUserDefaults).anonymous
}

// MARK: - Private Class Methods

private init(_ input: MAC, userDefaults: UserDefaults = .standard) {
self.input = input
self.userDefaults = userDefaults
}

// MARK: - Instance Properties

var anonymous: String {
guard let seedAddress = MAC(seed) else {
return "??:??:??:??:??:??"
}

let plaintext = input.integers
let key = seedAddress.integers
let ciphertext = plaintext.enumerated().map { ($1 + key[$0]) % 16 }
let anonmousAddress = ciphertext.map { String($0, radix: 16) }.joined()

guard let output = MAC(anonmousAddress) else {
return "??:??:??:??:??:??"
}

return output.address
}

/// Fetches the system-wide persisted anonymization seed.
/// Persists a new one if there is none.
///
private var seed: String {
if let knownSeed = userDefaults.string(forKey: seedKey) {
return knownSeed
} else {
let newSeed = generateSeed()
userDefaults.setValue(newSeed, forKey: seedKey)
return newSeed
}
}

/// Generates a completely random MAC address.
///
func generateSeed() -> String {
[
String(Int.random(in: 0..<256), radix: 16, uppercase: false),
String(Int.random(in: 0..<256), radix: 16, uppercase: false),
String(Int.random(in: 0..<256), radix: 16, uppercase: false),
String(Int.random(in: 0..<256), radix: 16, uppercase: false),
String(Int.random(in: 0..<256), radix: 16, uppercase: false),
String(Int.random(in: 0..<256), radix: 16, uppercase: false)
].joined()
}

private let input: MAC
private let userDefaults: UserDefaults
private let seedKey = "seed"
}
3 changes: 3 additions & 0 deletions LinkTools/Models/OUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

import Foundation

/// Organizationally unique identifier.
/// Also known as MAC prefix.
///
struct OUI: Equatable {
// MARK: Class Methods

Expand Down
6 changes: 3 additions & 3 deletions linkdaemon/Classes/Advisor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class Advisor {
}

if interface.softMAC == address {
Log.debug("Interface \(interface.bsd.name) is already set to softMAC \(address.humanReadable) - skipping")
Log.debug("Interface \(interface.bsd.name) is already set to softMAC \(address.address) - skipping")
return nil
}

Expand All @@ -95,11 +95,11 @@ class Advisor {
}

if interface.softMAC == undesiredAddress {
Log.debug("Randomizing \(interface.bsd.name) because it has undesired address \(undesiredAddress.humanReadable).")
Log.debug("Randomizing \(interface.bsd.name) because it has undesired address \(undesiredAddress.address).")
return arbiter.randomAddress()
}

Log.debug("\(interface.bsd.name) is already random but not undesired address \(undesiredAddress.humanReadable).")
Log.debug("\(interface.bsd.name) is already random but not undesired address \(undesiredAddress.address).")

return nil
}
Expand Down
2 changes: 1 addition & 1 deletion linkdaemon/Classes/Ifconfig.Setter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extension Ifconfig {
let state = WifiState(BSDName)
state.prepare()

Log.info("Setting MAC address of Interface \(BSDName) to <\(mac.humanReadable)>...")
Log.info("Setting MAC address of Interface \(BSDName) to <\(mac.address)>...")
let process = Process()
process.launchPath = "/sbin/ifconfig"
process.arguments = [BSDName, "ether", mac.address]
Expand Down

0 comments on commit ac73a44

Please sign in to comment.