mirror of
https://github.com/seemoo-lab/openhaystack.git
synced 2026-05-21 16:02:47 +00:00
107 lines
3.5 KiB
Swift
Executable File
107 lines
3.5 KiB
Swift
Executable File
//
|
||
// 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
|
||
|
||
struct DecryptReports {
|
||
|
||
/// Decrypt a find my report with the according key
|
||
/// - Parameters:
|
||
/// - report: An encrypted FindMy Report
|
||
/// - key: A FindMyKey
|
||
/// - Throws: Errors if the decryption fails
|
||
/// - Returns: An decrypted location report
|
||
static func decrypt(report: FindMyReport, with key: FindMyKey) throws -> FindMyLocationReport {
|
||
let payloadData = report.payload
|
||
let keyData = key.privateKey
|
||
|
||
let privateKey = keyData
|
||
let ephemeralKey = payloadData.subdata(in: 5..<62)
|
||
|
||
guard
|
||
let sharedKey = BoringSSL.deriveSharedKey(
|
||
fromPrivateKey: privateKey,
|
||
andEphemeralKey: ephemeralKey)
|
||
else {
|
||
throw FindMyError.decryptionError(description: "Failed generating shared key")
|
||
}
|
||
|
||
let derivedKey = self.kdf(fromSharedSecret: sharedKey, andEphemeralKey: ephemeralKey)
|
||
|
||
print("Derived key \(derivedKey.base64EncodedString())")
|
||
|
||
let encData = payloadData.subdata(in: 62..<72)
|
||
let tag = payloadData.subdata(in: 72..<payloadData.endIndex)
|
||
|
||
let decryptedContent = try self.decryptPayload(
|
||
payload: encData, symmetricKey: derivedKey, tag: tag)
|
||
let locationReport = self.decode(content: decryptedContent, report: report)
|
||
print(locationReport)
|
||
return locationReport
|
||
}
|
||
|
||
/// Decrypt the payload
|
||
/// - Parameters:
|
||
/// - payload: Encrypted payload part
|
||
/// - symmetricKey: Symmetric key
|
||
/// - tag: AES GCM tag
|
||
/// - Throws: AES GCM error
|
||
/// - Returns: Decrypted error
|
||
static func decryptPayload(payload: Data, symmetricKey: Data, tag: Data) throws -> Data {
|
||
let decryptionKey = symmetricKey.subdata(in: 0..<16)
|
||
let iv = symmetricKey.subdata(in: 16..<symmetricKey.endIndex)
|
||
|
||
print("Decryption Key \(decryptionKey.base64EncodedString())")
|
||
print("IV \(iv.base64EncodedString())")
|
||
|
||
let sealedBox = try AES.GCM.SealedBox(
|
||
nonce: AES.GCM.Nonce(data: iv), ciphertext: payload, tag: tag)
|
||
let symKey = SymmetricKey(data: decryptionKey)
|
||
let decrypted = try AES.GCM.open(sealedBox, using: symKey)
|
||
|
||
return decrypted
|
||
}
|
||
|
||
static func decode(content: Data, report: FindMyReport) -> FindMyLocationReport {
|
||
var longitude: Int32 = 0
|
||
_ = withUnsafeMutableBytes(of: &longitude, { content.subdata(in: 4..<8).copyBytes(to: $0) })
|
||
longitude = Int32(bigEndian: longitude)
|
||
|
||
var latitude: Int32 = 0
|
||
_ = withUnsafeMutableBytes(of: &latitude, { content.subdata(in: 0..<4).copyBytes(to: $0) })
|
||
latitude = Int32(bigEndian: latitude)
|
||
|
||
var accuracy: UInt8 = 0
|
||
_ = withUnsafeMutableBytes(of: &accuracy, { content.subdata(in: 8..<9).copyBytes(to: $0) })
|
||
|
||
let latitudeDec = Double(latitude) / 10000000.0
|
||
let longitudeDec = Double(longitude) / 10000000.0
|
||
|
||
return FindMyLocationReport(
|
||
lat: latitudeDec, lng: longitudeDec, acc: accuracy, dP: report.datePublished,
|
||
t: report.timestamp, c: report.confidence)
|
||
}
|
||
|
||
static func kdf(fromSharedSecret secret: Data, andEphemeralKey ephKey: Data) -> Data {
|
||
|
||
var shaDigest = SHA256()
|
||
shaDigest.update(data: secret)
|
||
var counter: Int32 = 1
|
||
let counterData = Data(
|
||
Data(bytes: &counter, count: MemoryLayout.size(ofValue: counter)).reversed())
|
||
shaDigest.update(data: counterData)
|
||
shaDigest.update(data: ephKey)
|
||
|
||
let derivedKey = shaDigest.finalize()
|
||
|
||
return Data(derivedKey)
|
||
}
|
||
}
|