72 lines
2.7 KiB
Swift
Executable File
72 lines
2.7 KiB
Swift
Executable File
#!/usr/bin/env swift
|
|
import Foundation
|
|
import CryptoKit
|
|
|
|
struct Payload: Codable {
|
|
let licenseID: String
|
|
let customerName: String?
|
|
let customerEmail: String?
|
|
let edition: String
|
|
let installationID: String
|
|
let issuedAt: Date
|
|
let expiresAt: Date?
|
|
let maxMajorVersion: Int?
|
|
}
|
|
|
|
func base64URL(_ data: Data) -> String {
|
|
data.base64EncodedString()
|
|
.replacingOccurrences(of: "+", with: "-")
|
|
.replacingOccurrences(of: "/", with: "_")
|
|
.replacingOccurrences(of: "=", with: "")
|
|
}
|
|
|
|
let args = CommandLine.arguments
|
|
func value(_ flag: String) -> String? {
|
|
guard let index = args.firstIndex(of: flag), index + 1 < args.count else { return nil }
|
|
return args[index + 1]
|
|
}
|
|
|
|
guard let installationID = value("--installation-id"), UUID(uuidString: installationID) != nil else {
|
|
fputs("Usage: swift Scripts/license-sign.swift --installation-id UUID [--name NAME] [--email EMAIL] [--edition Professional] [--license-id ID] [--max-major 1] [--expires YYYY-MM-DD]\n", stderr)
|
|
exit(2)
|
|
}
|
|
|
|
let fm = FileManager.default
|
|
let defaultPrivate = fm.homeDirectoryForCurrentUser.appendingPathComponent("Library/Application Support/Rsync Studio Licensing/license-private-key.txt")
|
|
let privateURL = value("--private-key").map { URL(fileURLWithPath: $0) } ?? defaultPrivate
|
|
let privateText = try String(contentsOf: privateURL, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard let privateData = Data(base64Encoded: privateText) else { throw NSError(domain: "RsyncStudioLicensing", code: 1, userInfo: [NSLocalizedDescriptionKey: "Private key is not Base64."]) }
|
|
let privateKey = try Curve25519.Signing.PrivateKey(rawRepresentation: privateData)
|
|
|
|
var expiry: Date?
|
|
if let text = value("--expires") {
|
|
let formatter = DateFormatter()
|
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
formatter.dateFormat = "yyyy-MM-dd"
|
|
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
|
guard let date = formatter.date(from: text) else {
|
|
fputs("Invalid --expires date; use YYYY-MM-DD.\n", stderr)
|
|
exit(2)
|
|
}
|
|
expiry = date
|
|
}
|
|
|
|
let payload = Payload(
|
|
licenseID: value("--license-id") ?? UUID().uuidString.lowercased(),
|
|
customerName: value("--name"),
|
|
customerEmail: value("--email"),
|
|
edition: value("--edition") ?? "Professional",
|
|
installationID: installationID.lowercased(),
|
|
issuedAt: Date(),
|
|
expiresAt: expiry,
|
|
maxMajorVersion: value("--max-major").flatMap(Int.init) ?? 1
|
|
)
|
|
|
|
let encoder = JSONEncoder()
|
|
encoder.outputFormatting = [.sortedKeys]
|
|
encoder.dateEncodingStrategy = .iso8601
|
|
let data = try encoder.encode(payload)
|
|
let signature = try privateKey.signature(for: data)
|
|
let token = "RS3.\(base64URL(data)).\(base64URL(signature))"
|
|
print(token)
|