Skip to content

felixjulianheitmann/licensecxx

Repository files navigation

Licensecxx

Build & Test Status Ubuntu

Build & Test Status Windows

Build & Test Status Mac

Copy protection library targeting Linux, Windows and Mac (currently only Linux supported)

This project is inspired by the licensecc project and similar to hyperboloide/lk.

Protect your software by generating and checking against license files. Create a set of key value pairs as content for your file and sign them with a RSA private key. The licensed software can then read this license file and verify the signature with the respective public key.

Additionally to providing key value pairs, you can include hash values of machine or user dependant data. Features such as name of the operating system, the machine hostname, the maximum CPU frequency can be hashed and included in the license. This enables machine/user specific license distribution.

Licensecxx provides a modern C++ interface with the main focus on ease of use and minimal impact on the software that it's protecting.

A list of the available features and the roadmap can be seen at License Features.

This repository is still under development. If you have experience errors or bugs, please file an issue on the GitHub issues page. If you want to contribute, pull requests are very welcome.

How to use licensecxx

Prerequisites

The library requires C++20 support.

Licensecxx relies on nlohmann/json and OpenSSL. nlohmann/json is fetched through CMake-Fetchcontent during configure time and made available the library. Whether this is the preferrable way is still up to debate.

OpenSSL needs to be made available by the parent project/user. Licensecxx only calls FindPackage(OpenSSL). If that cannot find the OpenSSL >3.0 library, the licensecxx cannot be built.

Building

This project is supposed to be used as a library addition to your software. Licensecxx is CMake based and can be included as a submodule in your project. Including it as a subdirectory makes the licensecxx targets available to your project

add_subdirectory(lcxx)

target_link_libraries(your-executable PUBLIC
    lcxx::lcxx
    # lcxx::identifiers
)

The lcxx::identifiers library is optional to produce/verify identifying hash codes of the target machine.

Generate a license

A simple license generator looks like this:

#include <lcxx/lcxx.hpp>

int main()
{
    lcxx::license license;

    // Push optional data into the license file
    license.push_content( "some key", "some value" );
    license.push_content( "hardware", lcxx::identifiers::hardware().hash );
    license.push_content( "os", lcxx::identifiers::os().hash );

    auto key = lcxx::crypto::load_key( some_pem_private_key_rsa_string, lcxx::crypto::key_type::private_key );
    // alternatively, load key from file by providing the PEM file path
    // auto key = lcxx::crypto::load_key( some_pem_private_key_rsa_path, lcxx::crypto::key_type::private_key );
    lcxx::to_json(license, "my-project-license.lic", key);
}

It prints a signed license json file to the file my-project-license.lic. This is similar to the sample license_generator.

Verify a license

A generated license file or string can be loaded and verified provided a public key. Additional hardware identifiers can be checked against the currently running hardware. Note, that the identifiers are still experimental as they are not supported by all platforms, yet.

int main()
{
    auto key                  = lcxx::crypto::load_key( some_pem_pem_key_rsa_string, lcxx::crypto::key_type::public_key );
    auto [license, signature] = lcxx::from_json( std::filesystem::path("my-project-license.lic") );

    if ( !lcxx::verify_license( license, signature, key ) ) {
        std::cout << "This program is not licensed!" << std::endl;
        return -1;
    }

    if ( lcxx::identifiers::verify( hw_ident_strat::all, license.get( "hardware" ) ) ) {
        std::cout << "The hardware does not match the one licensed!" << std::endl;
        return -2;
    }

    return 0;
}

A similar sample is given in license_verifier

Further samples are given in the samples folder.

Generating a key pair

To generate an RSA key pair that can be processed by licensecxx the following openssl commands can be used:

openssl genrsa -out /path/to/private_key.rsa 1024
openssl rsa -in /path/to/private_key.rsa -outform PEM -pubout -out /path/to/public_key

These commands generate a private-public key pair. Enabling the CMake option LCXX_GENERATE_KEYS will add an additional CMake interface library called lcxx::key. Linking it will have CMake invoke a simple Python script that generates a key-pair in two header files:

  • public_key.hpp
  • private_key.hpp

For that, location of the generated private/public keys can be specified with the variables LCXX_PRIVATE_KEY/LCXX_PUBLIC_KEY. The key size defaults to 1024 but can be configured through LCXX_KEY_SIZE.

Finally, LCXX_KEY_HEADER_DIR defines the path where the generated header files should be located. It will automatically be part of the lcxx::key include directories.

Enabling LCXX_GENERATE_KEYS will require Python as a dependency for the header generation.

License Features (and roadmap)

Building and testing on machines other than Linux is not as easy. That's why all features are most stable on Linux. Whenever a bug occurs, I will try to catch it with another unit test.

The roadmap for what I still have in mind with this library is as follows:

  • include CPU features
  • include OS/user features
  • Provide proper online documentation
  • Make CD/CI run through on Windows / Mac / Ubuntu
  • provide hardware/os identifiers for Windows
  • provide hardware/os identifiers for Mac
  • Provide a conan binary
  • Provide a vcpkg binary
  • Provide CMake fetchcontent option
  • Find a way to test hardware identifiers in CD/CI

License

The project is donated to the community. It uses the MIT license.