Giter Club home page Giter Club logo

software-licensor-rust's Introduction

API Endpoints

  • public keys are formatted with the Protobuf scheme found in proto/src/request_protos/pubkeys.proto at this URL:

Limitations

This licensing API is currently limited to the following license types:

  • Trial
  • Subscription
  • Perpetual

Offline perpetual license limitiations

There isn't a way to remove/deactivate machines on a license (yet), but when there is a way to do that, there will be a conflict with Offline licenses. Offline licenses are licenses where a client computer will have an activated license indefinitely without needing to contact the server periodically to renew the license's expiration. That means that if someone had an offline computer with an offline license activated, then they could "deactivate" this offline computer, and since the offline computer won't talk to the server any more, the offline computer's license will remain activated forever. This could also happen if someone set up a firewall that blocked communication to the licensing service on the client's network or local machine.

There are a few ways to address this issue:

  • use a physical device for offline licenses
    • we aren't like other competitors. we don't want to put the "plug in" in "plugin"
  • disable deactivation for offline computers
    • this might be an inconvenience, and stores would have to inform users that this is a policy for the software licensing
  • disallow offline licenses
    • it is possible to not have any Offline licenses for a product. It is an option in the create_product API method

There is also another policy that tries to limit this problem with malicious actors. In order to activate an offline license, the user needs to put -offline-[4-hexadigit offline code] at the end of their license code. If a legitimate customer shared a license code with someone, and that someone activated an offline license that could not be removed/deactivated, then the legitimate customer would permanently lose a machine slot on their license... or more, if someone were to do this with multiple computers.

Subscription license limitations

Currently, subscription licenses can only have the base amount of machines using a license. This is because it is difficult to determine whether a subscription license create_license request is meant to purchase a new license, or if it is meant to extend an existing license. This can probably be broken up into 2 separate API methods: one for purchasing licenses, and one for extending subscriptions. However... IMO, if a person was willing to pay for a subscription for some amount of time and stopped paying... you have already earned as much as you might earn from this customer... why not let them keep their license, giving subscription license customers a perpetual license instead of a subscription license?

Building

There are at least two ways to build this for the Graviton 2 processor using cross or cargo-lambda. cargo-lambda compiles a bit faster, but the files are a bit larger and the binaries run a little bit slower. cargo-lambda build scripts are saved as build-cargo-lambda.sh and the cross build scripts are saved as build.sh. You can also pass an argument that specifies the features to use. Available features are currently only zeroize and logging. Here's an example of how to call them:

./build.sh
# or
./build.sh "zeroize"
# or
./build.sh "zeroize,logging"
# or
./build-cargo-lambda.sh "zeroize,logging"

Note: build-cargo-lambda.sh requires provided.al2023 for the Lambda function's runtime, not just provided.al2. The runtime is set to provided.al2023 in the deployment scripts.

To build these API methods, you will need to install a few packages if you don't already have them installed:

  • Install rust on Ubuntu:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  • Install openssl on Ubuntu:
sudo apt update
sudo apt install build-essential pkg-config libssl-dev
  • Install cargo lambda (optional, and its built binaries do not work at the moment)
rustup update
cargo install cargo-lambda
  • Install Docker Desktop on your Host OS and enable its use in WSL if you are using WSL (highly recommended if you're on Windows). Or you can install zig using Homebrew on Linux/WSL.

  • Install cross

cargo install cross
  • Install aarch64 build target:
rustup target add aarch64-unknown-linux-musl
  • Install aws-cli:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

Setting up local environment variables:

Several environment variables need to be set for this to work:

  1. KEY_MANAGER_PRIVATE_KEY - needs to be a minimum length of the key manager's hash function's output, and a maximum of the hash function's internal buffer. This is currently using sha3_512 since it is faster on aarch64 with inline assembly. So for this, ideally the length should be between 64-72 bytes (inclusive).

  2. STORE_TABLE_SALT - a salt for the stores table

  3. LICENSE_TABLE_SALT - a slat for the licenses table

Running the code on AWS requires setting each Lambda's environment variables.

Getting this to work

There are a few things that you would need to do to get this to work, besides building it.

Here is a non-comprehensive list of what you would need to do to get the refactored version of the code to work:

  1. make an AWS account if you do not have one

  2. create some DynamoDB tables with table names, primary keys, and global secondary keys specified in utils/src/tables/, or change the names in those files to use different table names. You can generate some table names with cargo test --features local -- --nocapture. The long and random table names provide a little entropy when generating resource encryption keys when encrypting some of the data in the tables. It isn't completely necessary to have long table names, but AWS does not charge by the character in the table names. Yes, AWS supposedly encrypts the tables at rest, but why not add an extra layer of encryption? And, believe it or not, the encrypted protobuf messages can actually be smaller in size than putting it in plaintext NoSQL due to the potentially long keys since Protobuf keys are just binary numbers. The downside is that analytics tools such as AWS Athena likely will not be able to analyze any Protobuf/encrypted data.

  3. Create an IAM Role that can read and write to these tables. This is best done by first creating an IAM Policy with DynamoDB permissions.

Permissions for the DynamoDB tables:

  • BatchGetItem
  • BatchWriteItem
  • DeleteItem - only required for Licenses table
  • GetItem
  • Query - only required for Licenses table
  • PutItem
  • UpdateItem

The Query permission also needs to be added for the secondary index of the Licenses table.

Permissions for the Public Keys S3 bucket:

  • PutObject

The public keys bucket needs to be specified in the environment variables for the publish_rotating_keys Lambda Function.

  1. Create an IAM user with permissions for Lambda>Create Function and IAM>Pass Role, then make an access key for this user to sign into the AWS CLI with. Consider using aws configure sso instead, but it isn't very intuitive.

  2. Deploy the lambda functions. First, call create_deployment_scripts, specifying the IAM Role that can access the tables. Then call build.sh and deploy.sh to deploy the functions to the cloud. If anything changes, you need to call update_func.sh to update the functions.

  3. Navigate to API Gateway and create an HTTP API or REST API. Do some research on the two, but you'll probably want a REST API. The differences are explained here

  4. Add the lambda functions to this API, and ensure that these API endpoints are accessible from the public internet.

  5. Optionally, configure AWS WAF to restrict the (ab)usage of the API. You don't want to get DOS-ed.

  6. Optionally, take AWS Lambda Power Tuning for a spin to potentially increase the speed and lower the cost of the lambda functions.

software-licensor-rust's People

Contributors

nstilt1 avatar

Watchers

 avatar

software-licensor-rust's Issues

Allow multiple product licenses to be activated at once during License Activation

Right now, to activate a license, there is a batch_get_item for 3 DynamoDB tables: the store table, the products table, and the license table.

For multiple licenses to be activated at once (for one store), this would require 2 + [number of products being activated] items to be fetched from the database. batch_get_item has a limit of 100 items that can be retrieved at once, and batch_write_item has a limit of about 25 or so, which is also performed in the request.

These will need to be completed:

  • Remove License Activation method's dependency on the Products Table
  • Update the License Activation method to be able to handle multiple products being activated
  • Decrease the size of Product IDs to make these changes lighter on the database

Get item requests and write item requests are billed by the size of the items, as well as how many requests are made. There is a bit of a tradeoff here. We will be increasing the size of items in the Stores Table, but if we don't increase the size, then we would be reading and writing for up to n product items as well... I would say that the tradeoff will likely be worth it in the long run.

Optimize license activation by falling back to an online license if a product does not support offline licenses, instead of returning an error

The plan for license activation is to use a single license code for every "product" a store has, and to activate the licenses for multiple products at a time. The plan for reactivation is to simply repeat the original license activation request regularly. However, if the user wishes to activate an offline license for one product, but one of the other products from a store does not support offline licenses, then it will currently return an error for any such products that do not support offline licenses.

To optimize this process where only one request is required to be made for multiple offline and online products to be activated, the program could fall back to an online activation when an offline license type is not supported by the product, instead of returning an error. This should be as simple as adding a few && product_allows_offline to some conditions.

The "is_offline_allowed" field in the products table is not used

The purpose of this field is to prevent offline activations for people who don't want there to be offline activations. This is a concern to some because from what I've read, products primarily begin to get pirated once the developer adds a method for performing offline activations.

Migrate to official aws-rust-sdk

Hopefully this won't be too bad, but I made a publish_public_keys that uses rusoto_s3, and it did not work correctly. Everything is set up to have it triggerable from EventBridge, but it doesn't work

Revising Offline License Activation

Offline license activation probably shouldn't involve a 4 character code for the end user to append to their license code because it's kind of silly and could be susceptible to brute force, should someone find out someone else's license code.

The revision I have in mind is requiring the end user to click to enter an "Offline License Activation" menu, where there are two buttons: one for copying their machine ID to the clipboard (or saving a file containing their machine ID to their machine), and a button to activate the license using a key file.

The key file would need to be generated by the store's website, where they will need a form that accepts either a file or a machine ID, and then the website will need to send a request to the license_activation_refactor function using the user's machine ID, and the server will need to append -offline-[4 character offline code] to the end of the license code that is stored for the user. Don't worry, the store should already have access to this value if the user has a license, and if the store has saved their license data in the store's database. The store will also need to sign this request in the same manner that all of the other requests are signed with the store's private key.

Add more validation checks to requests

  • Restrict maximum machine limits to 10 or so, and instruct stores to limit how many licenses a customer can purchase
  • limit the maximum length for custom responses
  • limit length of computer names
  • Ensure that the frequency of client callbacks to the server is less than the license type's expiration period, and set a minimum value for these fields.

V1 requirements

This code hasn't been touched in a while. The license_auth API method was my first ever Rust project, so I was a complete beginner and didn't really know how Rust worked, especially the ? symbol used for automatically returning on Error Results. create_license was the most recent one I created, about a year ago.

There's quite a bit of work to be done here, starting off with collecting all of the utilities spread throughout here and making new utilities to go in the utils crate.

Also, I will be migrating away from the JUCE authentication scheme because it is slow and it is not a constant-time implementation, meaning that it is vulnerable to timing attacks. Even if it was implemented in constant time, it would be about 10x slower, which is why I have created an entirely new cryptographic protocol called crypto-on-the-edge. This protocol is wickedly fast and wickedly secure, using Protocol Buffers and Elliptic Curve Cryptography, and it doesn't even need to save any private keys anywhere—not even the private keys for plugins.

Here's a general list of things I need to do:

  • migrate existing utils to utils
  • make universal error type
  • make database hashmap util method for extracting a piece of data with the error handled within the method, rather than manually checking that it is there
  • do the same with any other frequently accessed options
  • change debug mode to use debug assertions to determine if debug mode is active
  • migrate to crypto-on-the-edge and delete all the cryptography code here... there's no reason to use OpenSSL when I'm also using RustCrypto
  • add open source license after removing JUCE cryptography code, since JUCE might use a different license than the one that I will use
  • migrate to 100% Protocol Buffers

Refactor progress:

  • Register Store
  • Create Plugin/Product
  • Create License
  • Get License
  • License Auth

Change license schema to have an offline machines hashmap, as well as an online machines hashmap instead of string sets

I was writing get_license_refactor and noticed that if there were over 100 different machines using one license from one store, this program would not be able to fetch all of them in one go. I will probably enforce some limits in the validations to ensure that there can only be up to maybe 10 machines per license to prevent this.

Anyway, the change from string sets to hashmaps allows there to be more than just machine IDs in the license items. I think I will just put the computer names and OS names in there for now. This will make it so that a get license request only uses 2 database reads

Build scripts don't work the way they used to

Apparently after adding Protocol Buffers into the mix, cross compiling might be a little more complicated. cross uses docker under the hood, and does not have protoc by default. This can be amended using a Cross.toml file in the workspace directory, as well as a Dockerfile. Some issues have arisen with that regard, as some base images don't have GCC-13. Will likely be using an alpine image

Missing API Methods

  • Update Product - #39
  • [ ] Recover Store - in case someone switches installations of WordPress, they could invoke this method with their signature on it
  • Recover product public key - #39
  • Get product IDs - in case someone didn't save their product IDs to the database
  • Deactivate Machines - #37
  • Regenerate License Code - #37
  • Increase Machine Limit

Change `LanguageSupport` responses to be able to share the responses between different plugins/products

When creating a product, there is a Map<String, LanguageSupport> field on the request. A LanguageSupport field looks like this:

message LanguageSupport {
    string incorrect_offline_code = 1;
    string license_no_longer_active = 2;
    string no_license_found = 3;
    string over_max_machines = 4;
    string trial_ended = 5;
    string success = 6;
}

The fields here are used to contain responses in a specific language, and these are mapped by the language name or ID. The user will provide the ID as a string when creating a product that is associated with the LanguageSupport fields.

The problem

If a user sets up language support for multiple languages for multiple plugins, then they might have to re-enter a bunch of sentences in a bunch of languages for each product.

Solutions

Store-specific responses

There could be store-specific responses kept in the database, that allows a store to reuse their responses across different products.

Global/default responses

There could be some default responses, but default responses might give away the fact that a given store is using this service. I also don't feel like translating responses into languages that I won't be using.

Client-side response translation fetching

The client side code could receive a response index from the server, and then fetch a translation from a GitHub site or a static website. This would be the best option for people being able to share translated messages with each other, and it could also be integrated with AI a bit better through the client side. It would also be extremely light on the database. I think I will likely go with this option.

*Note: Just because these responses might be able to be translated into languages does not mean that the software's GUI is automatically translated. Both would need to be translated for any of this to work, and there would need to be a way for a user to select the language within the software's installer or the GUI, and then the client would need to send the user's language to the server, or have it fetch the responses for the specific language from an online or maybe offline resource.

Move some parameters from products to the stores table

Some configuration details should probably be entered only once, if at all. Also, it would make things a bit easier on the back end if some of the details were solely in the Stores table.

Fields such as:

  • license type expiration periods
  • license type callback periods
  • custom responses from the server

Consider being able to activate licenses for multiple products during activation

This could save quite a bit of requests if any clients were to use this licensing service for multiple products. The only problem is the initial license activation after a user installs multiple products.

The client-side problem

If a user installs multiple products and is only able to activate each product after they open it for the first time, then they will need to submit as many license_activation requests as the amount of products they installed (initially). Ideally, they would only need to activate them once initially, and then at some fixed rate later on.

Solutions

The main solution would be to use a standalone licensing application that runs in the background, but there is another solution:

Upon installing via a .exe installer (or MSI or pkg), for each package that is installed, a licensing file could be placed in a standard location (such as the AppData folder). The files would primarily contain the product_ids of the installed products. After opening any of the installed applications, they could be hit with a licensing screen, and after entering the license code (which is the same for all of the vendor's applications), the licensing program searches for the file(s) containing the product_ids of the installed products, and uses those IDs to submit a single license_activation request.

This setup would require the vendor to:

  • know which directory they need to install the initial licensing file in, and
  • know what to put in the licensing file, how to format it, and either
  • use a single installer for multiple programs
  • use the same licensing file info across multiple installers

The problem with activating in the product's installer

There could be a license activation screen in the installer itself, but then it would run into the same problem if it was only installing one application.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.