Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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?

Expand Down
293 changes: 264 additions & 29 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -148,13 +156,40 @@ struct license
struct user user;
};

// machine represents a Keygen machine resource.
struct machine
{
std::string id;
std::string name;
std::vector<entitlement> 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 <typename T>
bool is_empty(T data) {
auto mm = (unsigned char*) &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)
{
Expand Down Expand Up @@ -186,6 +221,22 @@ 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) << " "
<< "File Type is "
<< (lic.typ == machine_type ? "machine" : "license")
<< std::endl;

// Decode contents
auto dec = decode_license_file(enc);
if (dec.empty())
Expand Down Expand Up @@ -230,7 +281,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<const unsigned char*>(data.c_str());
auto data_size = data.size();

Expand Down Expand Up @@ -420,6 +471,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<double>())
{
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<picojson::array>())
{
for (const auto &incl: included.get<picojson::array>())
{
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[])
{
Expand Down Expand Up @@ -487,6 +659,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) != '{')
Expand All @@ -506,44 +691,94 @@ int main(int argc, char* argv[])
<< "Parsing..."
<< std::endl;

auto lcs = parse_license(dec);
if (is_empty<license>(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<license>(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<machine>(mach))
{
std::cerr << colorize("[ERROR]", 31) << " "
<< "Failed to parse license!"
<< std::endl;

return 1;
}

std::cout << colorize("[OK]", 32) << " "
<< "License successfully parsed!"
<< std::endl;

if (i < lcs.entitlements.size() - 1)
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;
}
Expand Down