From dae58b3d76aeedc8534af7fbc819e6f1a64b6834 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 4 Dec 2023 16:06:02 +0530 Subject: [PATCH 1/4] Extend to machine license file type Previously the code only worked for license type license files --- main.cpp | 269 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 240 insertions(+), 29 deletions(-) diff --git a/main.cpp b/main.cpp index 30c414e..b1f5146 100644 --- a/main.cpp +++ b/main.cpp @@ -91,9 +91,17 @@ std::time_t strtotime(const std::string s) { return timegm(&t); } +// file_type represents a Keygen license file type - license or machine +enum file_type +{ + license_type, + machine_type +}; + // license_file represents a Keygen license file resource. struct license_file { + file_type typ; std::string enc; std::string sig; std::string alg; @@ -148,6 +156,18 @@ struct license struct user user; }; +// machine represents a Keygen machine resource. +struct machine +{ + std::string id; + std::string name; + std::vector entitlements; + struct license lcs; + struct product product; + struct policy policy; + struct user user; +}; + // is_empty checks if the provided type is empty, used for structs. template bool is_empty(T data) { @@ -186,6 +206,13 @@ license_file import_license_file(const std::string path) buf << f.rdbuf(); auto enc = buf.str(); + std::string machine_prefix = "-----BEGIN MACHINE FILE-----\n"; + lic.typ = enc.find(machine_prefix) == 0 ? machine_type : license_type; + std::cout << colorize("[INFO]", 34) << " " + << "File Type is " + << (lic.typ == machine_type ? "machine" : "license") + << std::endl; + // Decode contents auto dec = decode_license_file(enc); if (dec.empty()) @@ -230,7 +257,7 @@ bool verify_license_file(const std::string pubkey, license_file lic) } // Convert signing data into bytes - auto data = "license/" + lic.enc; + auto data = (lic.typ == machine_type ? "machine/" : "license/") + lic.enc; auto data_bytes = reinterpret_cast(data.c_str()); auto data_size = data.size(); @@ -420,6 +447,127 @@ license parse_license(const std::string dec) return lcs; } +// parse_machine parses a JSON string into a machine struct. +machine parse_machine(const std::string dec) +{ + picojson::value value; + machine mach {}; + + auto err = picojson::parse(value, dec); + if (!err.empty()) + { + std::cerr << colorize("[ERROR]", 31) << " " + << "Failed to parse machine: " << err + << std::endl; + + return mach; + } + + auto meta = value.get("meta"); + auto issued_at = strtotime(meta.get("issued").to_str()); + auto now = time(0); + + // Assert that current system time is not in the past. + if (now < issued_at) + { + std::cerr << colorize("[ERROR]", 31) << " " + << "System clock is desynced!" + << std::endl; + + return mach; + } + + auto ttl = meta.get("ttl"); + if (ttl.is()) + { + auto expires_at = strtotime(meta.get("expiry").to_str()); + + // Assert that license file has not expired. + if (now > expires_at) + { + std::cerr << colorize("[ERROR]", 31) << " " + << "License file has expired!" + << std::endl; + + return mach; + } + } + + auto data = value.get("data"); + auto attrs = data.get("attributes"); + + mach.id = data.get("id").to_str(); + mach.name = attrs.get("name").to_str(); + + auto included = value.get("included"); + if (included.is()) + { + for (const auto &incl: included.get()) + { + auto type = incl.get("type").to_str(); + auto id = incl.get("id").to_str(); + auto attrs = incl.get("attributes"); + + if (type == "entitlements") + { + entitlement entl {id}; + + entl.name = attrs.get("name").to_str(); + entl.code = attrs.get("code").to_str(); + + mach.entitlements.push_back(entl); + } + + if (type == "licenses") + { + license lcs {id}; + + lcs.name = attrs.get("name").to_str(); + lcs.key = attrs.get("key").to_str(); + lcs.status = attrs.get("status").to_str(); + lcs.last_validated_at = strtotime(attrs.get("lastValidated").to_str()); + lcs.expires_at = strtotime(attrs.get("expiry").to_str()); + lcs.created_at = strtotime(attrs.get("created").to_str()); + lcs.updated_at = strtotime(attrs.get("updated").to_str()); + + mach.lcs = lcs; + } + + if (type == "products") + { + product prod {id}; + + prod.name = attrs.get("name").to_str(); + + mach.product = prod; + } + + if (type == "policies") + { + policy pol {id}; + + pol.name = attrs.get("name").to_str(); + + mach.policy = pol; + } + + if (type == "users") + { + user usr {id}; + + usr.first_name = attrs.get("firstName").to_str(); + usr.last_name = attrs.get("lastName").to_str(); + usr.email = attrs.get("email").to_str(); + usr.status = attrs.get("status").to_str(); + + mach.user = usr; + } + } + } + + return mach; +} + // main runs the example program. int main(int argc, char* argv[]) { @@ -487,6 +635,19 @@ int main(int argc, char* argv[]) << "Decrypting..." << std::endl; + if (lic.typ == machine_type) + { + if (!getenv("KEYGEN_MACHINE_FINGERPRINT")) + { + std::cerr << colorize("[ERROR]", 31) << " " + << "Environment variable KEYGEN_MACHINE_FINGERPRINT is missing" + << std::endl; + + return 1; + } + secret += getenv("KEYGEN_MACHINE_FINGERPRINT"); + } + // Decrypt the license file auto dec = decrypt_license_file(secret, lic); if (dec.empty() || dec.at(0) != '{') @@ -506,44 +667,94 @@ int main(int argc, char* argv[]) << "Parsing..." << std::endl; - auto lcs = parse_license(dec); - if (is_empty(lcs)) + if (lic.typ == license_type) { - std::cerr << colorize("[ERROR]", 31) << " " - << "Failed to parse license!" - << std::endl; + auto lcs = parse_license(dec); + if (is_empty(lcs)) + { + std::cerr << colorize("[ERROR]", 31) << " " + << "Failed to parse license!" + << std::endl; - return 1; - } + return 1; + } - std::cout << colorize("[OK]", 32) << " " - << "License successfully parsed!" - << std::endl; + std::cout << colorize("[OK]", 32) << " " + << "License successfully parsed!" + << std::endl; - std::cout << "name=" << colorize(lcs.name, 34) << std::endl - << "key=" << colorize(lcs.key, 34) << std::endl - << "status=" << colorize(lcs.status, 34) << std::endl - << "last_validated_at=" << colorize(timetostr(lcs.last_validated_at), 34) << std::endl - << "expires_at=" << colorize(timetostr(lcs.expires_at), 34) << std::endl - << "created_at=" << colorize(timetostr(lcs.created_at), 34) << std::endl - << "updated_at=" << colorize(timetostr(lcs.updated_at), 34) << std::endl - << "entitlements=["; + std::cout << "type=license" + << std::endl; + std::cout << "name=" << colorize(lcs.name, 34) << std::endl + << "key=" << colorize(lcs.key, 34) << std::endl + << "status=" << colorize(lcs.status, 34) << std::endl + << "last_validated_at=" << colorize(timetostr(lcs.last_validated_at), 34) << std::endl + << "expires_at=" << colorize(timetostr(lcs.expires_at), 34) << std::endl + << "created_at=" << colorize(timetostr(lcs.created_at), 34) << std::endl + << "updated_at=" << colorize(timetostr(lcs.updated_at), 34) << std::endl + << "entitlements=["; + + for (auto i = 0; i < lcs.entitlements.size(); i++) + { + auto entitlement = lcs.entitlements.at(i); + std::cout << colorize(entitlement.code, 34); - for (auto i = 0; i < lcs.entitlements.size(); i++) + if (i < lcs.entitlements.size() - 1) + { + std::cout << ","; + } + } + + std::cout << "]" << std::endl + << "product=" << colorize(lcs.product.id, 34) << std::endl + << "policy=" << colorize(lcs.policy.id, 34) << std::endl + << "user=" << colorize(lcs.user.id, 34) << std::endl; + } + else if (lic.typ == machine_type) { - auto entitlement = lcs.entitlements.at(i); - std::cout << colorize(entitlement.code, 34); + auto mach = parse_machine(dec); + if (is_empty(mach)) + { + std::cerr << colorize("[ERROR]", 31) << " " + << "Failed to parse license!" + << std::endl; - if (i < lcs.entitlements.size() - 1) + return 1; + } + + std::cout << colorize("[OK]", 32) << " " + << "License successfully parsed!" + << std::endl; + + std::cout << "type=machine" + << std::endl; + std::cout << "name=" << colorize(mach.name, 34) << std::endl; + + std::cout << "lic.name=" << colorize(mach.lcs.name, 34) << std::endl + << "lic.key=" << colorize(mach.lcs.key, 34) << std::endl + << "lic.status=" << colorize(mach.lcs.status, 34) << std::endl + << "lic.last_validated_at=" << colorize(timetostr(mach.lcs.last_validated_at), 34) << std::endl + << "lic.expires_at=" << colorize(timetostr(mach.lcs.expires_at), 34) << std::endl + << "lic.created_at=" << colorize(timetostr(mach.lcs.created_at), 34) << std::endl + << "lic.updated_at=" << colorize(timetostr(mach.lcs.updated_at), 34) << std::endl + << "lic.entitlements=["; + + for (auto i = 0; i < mach.entitlements.size(); i++) { - std::cout << ","; + auto entitlement = mach.entitlements.at(i); + std::cout << colorize(entitlement.code, 34); + + if (i < mach.entitlements.size() - 1) + { + std::cout << ","; + } } - } - std::cout << "]" << std::endl - << "product=" << colorize(lcs.product.id, 34) << std::endl - << "policy=" << colorize(lcs.policy.id, 34) << std::endl - << "user=" << colorize(lcs.user.id, 34) << std::endl; + std::cout << "]" << std::endl + << "product=" << colorize(mach.product.id, 34) << std::endl + << "policy=" << colorize(mach.policy.id, 34) << std::endl + << "user=" << colorize(mach.user.id, 34) << std::endl; + } return 0; } From d99325daa0c7839db6387bb786d3aaf4921d07b8 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 4 Dec 2023 18:29:36 +0530 Subject: [PATCH 2/4] Update README to include instructions for machine license files --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a9768b3..9209f91 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ export KEYGEN_PUBLIC_KEY="e8601e48b69383ba520245fd07971e983d06d22c4257cfd8230460 # A license key. export KEYGEN_LICENSE_KEY="E1FBA2-5488D8-8AC81A-53157E-01939A-V3" + +# A machine fingerprint (ONLY required for machine license files) +export KEYGEN_MACHINE_FINGERPRINT="88f5e16d235431f29ed16ceef1835cce2ff82e06df91644dd" ``` You can either run each line above within your terminal session before @@ -51,6 +54,7 @@ Alternatively, you can prefix the below command with env variables, e.g.: ```bash KEYGEN_PUBLIC_KEY="e8601e48b69383ba520245fd07971e983d06d22c4257cfd82304601479cee788" \ KEYGEN_LICENSE_KEY="E1FBA2-5488D8-8AC81A-53157E-01939A-V3" \ + KEYGEN_MACHINE_FINGERPRINT="88f5e16d235431f29ed16ceef1835cce2ff82e06df91644dd" \ ./bin.out examples/license.lic ``` @@ -81,7 +85,8 @@ user=2068992b-f98f-4efc-95fd-687dbd0c868c If the license file fails to decrypt, ensure that you're providing the correct license key via `KEYGEN_LICENSE_KEY`. License files are encrypted with their -license's key, so an incorrect license key will fail to decrypt. +license's key, so an incorrect license key will fail to decrypt. Machine +license files are encrypted with both license key and machine fingerprint. ## Questions? From 03f511139d2d41411ec53972ee44de20cb3918d4 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Tue, 5 Dec 2023 12:53:34 +0530 Subject: [PATCH 3/4] Fix is_empty for license structs As currently defined, is_empty is not correct since it assumes an empty std::string will be stored in memory as all zeroes - which is not true --- main.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/main.cpp b/main.cpp index b1f5146..4362782 100644 --- a/main.cpp +++ b/main.cpp @@ -175,6 +175,21 @@ bool is_empty(T data) { return (*mm == 0) && memcmp(mm, mm + 1, sizeof(T) - 1) == 0; } +template <> +bool is_empty(license_file data) { + return data.enc.empty(); +} + +template <> +bool is_empty(license data) { + return data.id.empty(); +} + +template <> +bool is_empty(machine data) { + return data.id.empty(); +} + // decode_license_file decodes a license file certificate into a JSON string. std::string decode_license_file(const std::string cert) { From 91513fc4e6716d9d30f261dfb6d850b863becf58 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 6 Dec 2023 16:05:08 +0530 Subject: [PATCH 4/4] Check for minimum file size of license file Without this check, the code throws an exception --- main.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/main.cpp b/main.cpp index 4362782..ed526bb 100644 --- a/main.cpp +++ b/main.cpp @@ -221,6 +221,15 @@ license_file import_license_file(const std::string path) buf << f.rdbuf(); auto enc = buf.str(); + if (enc.size() <= 54) // size of license file prefix+suffix + { + std::cerr << colorize("[ERROR]", 31) << " " + << "Non-existent or invalid license file " + << path + << std::endl; + return lic; + } + std::string machine_prefix = "-----BEGIN MACHINE FILE-----\n"; lic.typ = enc.find(machine_prefix) == 0 ? machine_type : license_type; std::cout << colorize("[INFO]", 34) << " "