130 lines
4.5 KiB
Swift
130 lines
4.5 KiB
Swift
//
|
|
// 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<Bool, String>) -> 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
|
|
}
|
|
}
|