Add obfuscated trial storage to prevent perpetual trial

This commit is contained in:
Beingpax 2025-08-26 09:41:42 +05:45
parent 560781c7d0
commit 71fdbdfc75
3 changed files with 94 additions and 33 deletions

View File

@ -0,0 +1,62 @@
import Foundation
import IOKit
/// Simple utility to obfuscate sensitive data stored in UserDefaults
struct Obfuscator {
/// Encodes a string using Base64 with a device-specific salt
static func encode(_ string: String, salt: String) -> String {
let salted = salt + string + salt
let data = Data(salted.utf8)
return data.base64EncodedString()
}
/// Decodes a Base64 string using a device-specific salt
static func decode(_ base64: String, salt: String) -> String? {
guard let data = Data(base64Encoded: base64),
let salted = String(data: data, encoding: .utf8) else {
return nil
}
// Remove the salt from both ends
guard salted.hasPrefix(salt), salted.hasSuffix(salt) else {
return nil
}
return String(salted.dropFirst(salt.count).dropLast(salt.count))
}
/// Gets a device-specific identifier to use as salt
/// Uses the same logic as PolarService for consistency
static func getDeviceIdentifier() -> String {
// Try to get Mac serial number first
if let serialNumber = getMacSerialNumber() {
return serialNumber
}
// Fallback to stored UUID
let defaults = UserDefaults.standard
if let storedId = defaults.string(forKey: "VoiceInkDeviceIdentifier") {
return storedId
}
// Create and store new UUID
let newId = UUID().uuidString
defaults.set(newId, forKey: "VoiceInkDeviceIdentifier")
return newId
}
/// Try to get the Mac serial number
private static func getMacSerialNumber() -> String? {
let platformExpert = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
if platformExpert == 0 { return nil }
defer { IOObjectRelease(platformExpert) }
if let serialNumber = IORegistryEntryCreateCFProperty(platformExpert, "IOPlatformSerialNumber" as CFString, kCFAllocatorDefault, 0) {
return (serialNumber.takeRetainedValue() as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
}
return nil
}
}

View File

@ -36,37 +36,9 @@ class PolarService {
let status: String
}
// Generate a unique device identifier
// Generate a unique device identifier using shared logic
private func getDeviceIdentifier() -> String {
// Use the macOS serial number or a generated UUID that persists
if let serialNumber = getMacSerialNumber() {
return serialNumber
}
// Fallback to a stored UUID if we can't get the serial number
let defaults = UserDefaults.standard
if let storedId = defaults.string(forKey: "VoiceInkDeviceIdentifier") {
return storedId
}
// Create and store a new UUID if none exists
let newId = UUID().uuidString
defaults.set(newId, forKey: "VoiceInkDeviceIdentifier")
return newId
}
// Try to get the Mac serial number
private func getMacSerialNumber() -> String? {
let platformExpert = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
if platformExpert == 0 { return nil }
defer { IOObjectRelease(platformExpert) }
if let serialNumber = IORegistryEntryCreateCFProperty(platformExpert, "IOPlatformSerialNumber" as CFString, kCFAllocatorDefault, 0) {
return (serialNumber.takeRetainedValue() as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
}
return nil
return Obfuscator.getDeviceIdentifier()
}
// Check if a license key requires activation

View File

@ -8,6 +8,11 @@ extension UserDefaults {
static let audioInputMode = "audioInputMode"
static let selectedAudioDeviceUID = "selectedAudioDeviceUID"
static let prioritizedDevices = "prioritizedDevices"
// Obfuscated keys for license-related data
enum License {
static let trialStartDate = "VoiceInkTrialStartDate"
}
}
// MARK: - AI Provider API Key
@ -22,10 +27,32 @@ extension UserDefaults {
set { setValue(newValue, forKey: Keys.licenseKey) }
}
// MARK: - Trial Start Date
// MARK: - Trial Start Date (Obfuscated)
var trialStartDate: Date? {
get { object(forKey: Keys.trialStartDate) as? Date }
set { setValue(newValue, forKey: Keys.trialStartDate) }
get {
let salt = Obfuscator.getDeviceIdentifier()
let obfuscatedKey = Obfuscator.encode(Keys.License.trialStartDate, salt: salt)
guard let obfuscatedValue = string(forKey: obfuscatedKey),
let decodedValue = Obfuscator.decode(obfuscatedValue, salt: salt),
let timestamp = Double(decodedValue) else {
return nil
}
return Date(timeIntervalSince1970: timestamp)
}
set {
let salt = Obfuscator.getDeviceIdentifier()
let obfuscatedKey = Obfuscator.encode(Keys.License.trialStartDate, salt: salt)
if let date = newValue {
let timestamp = String(date.timeIntervalSince1970)
let obfuscatedValue = Obfuscator.encode(timestamp, salt: salt)
setValue(obfuscatedValue, forKey: obfuscatedKey)
} else {
removeObject(forKey: obfuscatedKey)
}
}
}
// MARK: - Audio Input Mode