mirror of
https://github.com/seemoo-lab/openhaystack.git
synced 2026-03-05 02:30:25 +00:00
113 lines
3.6 KiB
Swift
113 lines
3.6 KiB
Swift
//
|
||
// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
|
||
//
|
||
// Copyright © 2021 Secure Mobile Networking Lab (SEEMOO)
|
||
// Copyright © 2021 The Open Wireless Link Project
|
||
//
|
||
// SPDX-License-Identifier: AGPL-3.0-only
|
||
//
|
||
|
||
import CryptoKit
|
||
import Foundation
|
||
|
||
/// Decode key files found in newer macOS versions.
|
||
class FindMyKeyDecoder {
|
||
/// Key files can be in different format.
|
||
///
|
||
/// The old <= 10.15.3 have been using normal plists. Newer once use a binary format which needs different parsing.
|
||
enum KeyFileFormat {
|
||
/// Catalina > 10.15.4 key file format | Big Sur 11.0 Beta 1 uses a similar key file format that can be parsed identically.
|
||
/// macOS 10.15.7 uses a new key file format that has not been reversed yet.
|
||
/// (The key files are protected by sandboxing and only usable from a SIP disabled)
|
||
case catalina_10_15_4
|
||
}
|
||
|
||
var fileFormat: KeyFileFormat?
|
||
|
||
func parse(keyFile: Data) throws -> [FindMyKey] {
|
||
// Detect the format at first
|
||
if fileFormat == nil {
|
||
try self.checkFormat(for: keyFile)
|
||
}
|
||
guard let format = self.fileFormat else {
|
||
throw ParsingError.unsupportedFormat
|
||
}
|
||
|
||
switch format {
|
||
case .catalina_10_15_4:
|
||
let keys = try self.parseBinaryKeyFiles(from: keyFile)
|
||
return keys
|
||
}
|
||
}
|
||
|
||
func checkFormat(for keyFile: Data) throws {
|
||
// Key files need to start with KEY = 0x4B 45 59
|
||
let magicBytes = keyFile.subdata(in: 0..<3)
|
||
guard magicBytes == Data([0x4b, 0x45, 0x59]) else {
|
||
throw ParsingError.wrongMagicBytes
|
||
}
|
||
|
||
// Detect zeros
|
||
let potentialZeros = keyFile[15..<31]
|
||
guard potentialZeros == Data(repeating: 0x00, count: 16) else {
|
||
throw ParsingError.wrongFormat
|
||
}
|
||
// Should be big sur
|
||
self.fileFormat = .catalina_10_15_4
|
||
}
|
||
|
||
fileprivate func parseBinaryKeyFiles(from keyFile: Data) throws -> [FindMyKey] {
|
||
var keys = [FindMyKey]()
|
||
// First key starts at 32
|
||
var i = 32
|
||
|
||
while i + 117 < keyFile.count {
|
||
// We could not identify what those keys were
|
||
_ = keyFile.subdata(in: i..<i + 32)
|
||
i += 32
|
||
if keyFile[i] == 0x00 {
|
||
// Public key only.
|
||
// No need to parse it. Just skip to the next key
|
||
i += 86
|
||
continue
|
||
}
|
||
|
||
guard keyFile[i] == 0x01 else {
|
||
throw ParsingError.wrongFormat
|
||
}
|
||
// Step over 0x01
|
||
i += 1
|
||
// Read the key (starting with 0x04)
|
||
let fullKey = keyFile.subdata(in: i..<i + 85)
|
||
i += 85
|
||
// Create the sub keys. No actual need, but we do that to put them into a similar format as used before 10.15.4
|
||
let advertisedKey = fullKey.subdata(in: 1..<29)
|
||
let yCoordinate = fullKey.subdata(in: 29..<57)
|
||
|
||
var shaDigest = SHA256()
|
||
shaDigest.update(data: advertisedKey)
|
||
let hashedKey = Data(shaDigest.finalize())
|
||
|
||
let fmKey = FindMyKey(
|
||
advertisedKey: advertisedKey,
|
||
hashedKey: hashedKey,
|
||
privateKey: fullKey,
|
||
startTime: nil,
|
||
duration: nil,
|
||
pu: nil,
|
||
yCoordinate: yCoordinate,
|
||
fullKey: fullKey)
|
||
|
||
keys.append(fmKey)
|
||
}
|
||
|
||
return keys
|
||
}
|
||
|
||
enum ParsingError: Error {
|
||
case wrongMagicBytes
|
||
case wrongFormat
|
||
case unsupportedFormat
|
||
}
|
||
}
|