Add obfuscated trial storage to prevent perpetual trial
This commit is contained in:
parent
560781c7d0
commit
71fdbdfc75
62
VoiceInk/Services/Obfuscator.swift
Normal file
62
VoiceInk/Services/Obfuscator.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user