// // LicenseManager.swift // CmdBar // // Created by Igor Kolokolnikov on 8/26/23. // import Cocoa import CryptoKit fileprivate struct LicenseModel: Codable { let valid: Bool let error: String let email: String let key: String } // Converts string to a hash. fileprivate func hashString(_ string: String) -> String { return SHA256.hash(data: Data(string.utf8)).compactMap { String(format: "%02x", $0) }.joined() } // Converts passphrase to a SymmetricKey. fileprivate func passToKey(from passphrase: String) -> SymmetricKey { let passphraseData = Data(passphrase.utf8) let keyData = SHA256.hash(data: passphraseData).withUnsafeBytes { buffer in return Data(buffer) } return SymmetricKey(data: keyData) } final class LicenseManager { static let standard = LicenseManager() private let passkey = passToKey(from: "B2B67FB0E22B814CE5A6E7DA729A068BF21C9490919EA0B42FCEB322F9C9BA82") private let defaults = UserDefaults.standard private var timer: DispatchSourceTimer? private var quitAfterMinutes = 20.0 * 60.0 // quit after 20 minutes func isRegistered() -> Bool { guard let email = defaults.object(forKey: "Email") as? String, let key = defaults.object(forKey: "Key") as? String, let licenseData = defaults.object(forKey: "License") as? Data, let unsealedBox = try? AES.GCM.SealedBox(combined: licenseData), let licenseHashData = try? AES.GCM.open(unsealedBox, using: passkey), !email.isEmpty, !key.isEmpty else { return false } let currentHashData = Data(SHA256.hash(data: Data(hashString("\(email)\(key)\(NSUserName())").utf8)) .compactMap { String(format: "%02x", $0) }.joined().utf8) return licenseHashData == currentHashData } func activateLicense(email: String, key: String, completionHandler: @escaping (_ data: Resulting) -> Void) { guard let url = URL(string: EndpointConstants.validateURL)?.appending(component: key) else { completionHandler(.failure); return } URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in guard let data = data, error == nil, let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else { completionHandler(.failure); return } guard let jsonResponse = try? JSONDecoder().decode(LicenseModel.self, from: data) else { completionHandler(.failure); return } if jsonResponse.email == email && jsonResponse.key == key { self?.saveLicense(with: email, and: key) self?.stopTimer() completionHandler(.success) } else { completionHandler(.failure) } }.resume() } func deactivateLicense() { clearLicense() startTimer() } func getLicense() -> (email: String, key: String) { guard let email = defaults.object(forKey: "Email") as? String, let key = defaults.object(forKey: "Key") as? String else { return ("", "") } return (email, key) } private func saveLicense(with email: String, and key: String) { let defaults = UserDefaults.standard let licenseHashData = Data(SHA256.hash(data: Data(hashString("\(email)\(key)\(NSUserName())").utf8)) .compactMap { String(format: "%02x", $0) }.joined().utf8) guard let sealedBoxData = try? AES.GCM.seal(licenseHashData, using: passkey).combined else { return } defaults.set(email, forKey: "Email") defaults.set(key, forKey: "Key") defaults.set(sealedBoxData, forKey: "License") } func clearLicense() { defaults.set(nil, forKey: "Email") defaults.set(nil, forKey: "Key") defaults.set(nil, forKey: "License") } // MARK: - Timer func startTimer() { timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global()) timer?.schedule( deadline: .now() + quitAfterMinutes, repeating: DispatchTimeInterval.never ) timer?.setEventHandler { DispatchQueue.main.async { NSApplication.shared.terminate(self) } } timer?.resume() } func stopTimer() { timer?.cancel() timer = nil } }