The crypto-licensing module implements Ed25519-signed license checking and automatic issuance after cryptocurrency payment
Project description
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CRYPTOGRAPHICALLY SIGNED LICENSE ISSUANCE WITH
PAYMENT IN CRYPTOCURRENCY
Perry Kundert
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2022-01-25 12:32:00
Licensing software and getting paid for it has become extremely
difficult, due to government, regulatory and banking interference.
The `crypto-licensing' Python module allows you automatically and
securely issue licenses, and get paid in various cryptocurrencies.
Table of Contents
─────────────────
1. Software Licensing Using Ed25519 Signatures
.. 1. Issuing Licenses: Your Authoring (Signing) Key
..... 1. `crypto_licensing.authoring': Create an Authoring Keypair in Python
..... 2. `crypto_licensing registered': Load or Create an Authoring Keypair
.. 2. Creating a License: Grants
..... 1. Verifiable Data
..... 2. `issue': Signing a License
..... 3. `verify': Confirm License (and sub-License) Validity
.. 3. Using Licenses
..... 1. `load_keys': Find all Ed25519 Signing Keys
..... 2. `load': Find all Licenses
..... 3. `check': Find all Keys and Valid Licenses
.. 4. Running A `crypto_licensing.licensing' Server
2. Payment with Cryptocurrencies
3. Issuance via Web API
1 Software Licensing Using Ed25519 Signatures
═════════════════════════════════════════════
Providing software to a client usually includes more than just the
code; you need a way to configure the software for each client's
licensed set of capabilities.
The `crypto_licensing' module provides a way to securely transmit some
authorizations or configurations specific to each client.
These configurations are signed using Ed25519 Public/Private keys, and
are shipped to the client either with the software, or separately, for
example via email, after a standard online purchase.
Your organization's or product's Authoring public key is online (using
the same model as for email's DKIM signature checking), so your
software can *verify* the License in the field, by securely accessing
DNS `TXT' records from your organization's domain, and checking the
License' signature.
Your software can also sign and save this verification for later runs,
so it can be assured that the License has been verified – even if the
software is not normally "online". Your software just has to have
access to the Internet /once/, after the new License is installed, to
verify the License and remember its decision.
1.1 Issuing Licenses: Your Authoring (Signing) Key
──────────────────────────────────────────────────
To begin authoring Licenses, you need to be able to sign them; you
need to create and save an encrypted Ed25519 keypair, so you (and only
you) can obtain it later to sign new Licenses.
The public key `dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fU=' (related
to the private key consisting of all `1' bits) may be created via the
API or CLI. It should be stored securely, so a `KeypairEncrypted'
might be appropriate. Both the `KeypairPlaintext' and
`KeypairEncrypted' contain the public "verifying" key `.vk'. The
`KeypairEncrypted' also contains a `.vk_signature', proving that the
`.vk' was signed by the corresponding private key at the time of
creation.
1.1.1 `crypto_licensing.authoring': Create an Authoring Keypair in Python
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
The raw `ed25519.Keypair' from `authoring' isn't serializable, so get
a `crypto_licensing' `KeypairEncrypted' or `KeypairPlaintext' and save
its `str( <Keypair...> )' output to a file. Here's how they are
related:
┌────
│ import crypto_licensing as cl
│
│ username = 'admin@awesome-inc.com'
│ password = 'password'
│
│ auth_keypair = cl.authoring( seed=b'\xff' * 32 ) # Supply None, unless you really have a random seed!
│ encr_keypair = cl.KeypairEncrypted( auth_keypair, username=username, password=password )
│ decr_keypair = cl.KeypairPlaintext( encr_keypair.into_keypair( username=username, password=password ))
│
│ # How can I know that the KeypairEncrypted holds a real private key? Because the public key was signed by it!
│ from crypto_licensing import ed25519
│ try:
│ ed25519.crypto_sign_open( encr_keypair.vk_signature + encr_keypair.vk, encr_keypair.vk )
│ valid = True
│ except Exception as exc:
│ valid = exc
│
│ [
│ [ "Encrypted:", "" ],
│ [ "Public Key", encr_keypair['vk'] ],
│ [ "Salt", encr_keypair['salt'] ],
│ [ "Ciphertext", encr_keypair['ciphertext'] ],
│ [ "Signature", encr_keypair['vk_signature'] ],
│ [ "Valid?", repr(valid) ],
│ [],
│ [ "Plaintext:", "" ],
│ [ "Public Key", decr_keypair['vk'] ],
│ [ "Private Key", decr_keypair['sk'] ],
│ ]
└────
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Encrypted:
Public Key dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fU=
Salt 85f6c6c07df3d74fecd82600
Ciphertext 7ed47bd73f8e261b7398f059bd76319915ac7b8923a2568b092f8b1e5277a7a55df6fea031f1bd3cf84d860956a095ef
Signature h44cyYJvofemshmvizrN0+LVisMSTcPD1BGBVkwHVbEKbz+zHsNMjczQh91mLgwv8A6mzlbF7jQqznJOQwcxDA==
Valid? True
Plaintext:
Public Key dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fU=
Private Key //////////////////////////////////////////92oVkgRKbk9REmW8pzpgTZCwUp0d9gK+MKGakldmDR9Q==
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Of course, to create a completely random `KeypairEncrypted' (using
`secrets.token_bytes'), just pass the default None for the `sk'. All
`Serializable' objects in `crypto-licensing' are also "mapping" types
with, so can be converted to a `dict' (values are serialized to their
default human-readable representations when accessed as keys):
┌────
│ keypair_enc = cl.KeypairEncrypted( username=username, password=password )
│ [[k,v] for k,v in dict(keypair_enc).items() ]
└────
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
salt 26df4b08e5002ed3dbad7918
ciphertext 33a6d24f2b9472af4831abe4de180460582b9d97b50e89a632fd754d9ab6b47d1361dcf99d674b1868ae29f86f3128bf
vk V9fMRtLBkcd43dfEma7agr83jGu+AJXRi9ee8WMlcK8=
vk_signature tK05r3x+OqYThBlRU1XnEDyxeabtrU8SRGdgnCbywLJysdAUH2+WIGuvr36ASEF6pAiB2/VLpIjdtFOIgo2xAg==
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
accessed as an `object' attribute (values are raw when accessed as
attributes) or item name:
┌────
│ [['.ciphertext', keypair_enc.ciphertext.hex()],
│ ['[ciphertext]', keypair_enc['ciphertext']]]
└────
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
.ciphertext 33a6d24f2b9472af4831abe4de180460582b9d97b50e89a632fd754d9ab6b47d1361dcf99d674b1868ae29f86f3128bf
[ciphertext] 33a6d24f2b9472af4831abe4de180460582b9d97b50e89a632fd754d9ab6b47d1361dcf99d674b1868ae29f86f3128bf
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
or converted to their `JSON' format using `str':
┌────
│ print(keypair_enc)
└────
┌────
│ {
│ "ciphertext":"33a6d24f2b9472af4831abe4de180460582b9d97b50e89a632fd754d9ab6b47d1361dcf99d674b1868ae29f86f3128bf",
│ "salt":"26df4b08e5002ed3dbad7918",
│ "vk":"V9fMRtLBkcd43dfEma7agr83jGu+AJXRi9ee8WMlcK8=",
│ "vk_signature":"tK05r3x+OqYThBlRU1XnEDyxeabtrU8SRGdgnCbywLJysdAUH2+WIGuvr36ASEF6pAiB2/VLpIjdtFOIgo2xAg=="
│ }
└────
1.1.2 `crypto_licensing registered': Load or Create an Authoring Keypair
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
But the simplest way to manage creating and then (later) obtaining
your Authoring Keypair is to use the CLI to check if one is already
`registered' and saved in your `~/.crypto-licensing' directory under a
given name, using some encryption credentials.
The first time you do this, one will be created for you; subsequently,
the existing one will be opened, displaying the file path, the public
key, and (with `-v') the `KeypairEncrypted':
┌────
│ python3 -m crypto_licensing -v --name "Awesome-Inc" registered --username admin@awesome-inc.com --password password \
│ --seed "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
└────
┌────
│ [
│ "/Users/perry/.crypto-licensing/Awesome-Inc.crypto-keypair",
│ "dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fU=",
│ {
│ "ciphertext":"aef7cf9884bc13bd7b4ee0cde402d2b666a084335f5d7b2bb6d2c31a8910499b5b19d450b2ccab03b83e9bb586612fb2",
│ "salt":"a84d6df719af9f50dcdc1416",
│ "vk":"dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fU=",
│ "vk_signature":"h44cyYJvofemshmvizrN0+LVisMSTcPD1BGBVkwHVbEKbz+zHsNMjczQh91mLgwv8A6mzlbF7jQqznJOQwcxDA=="
│ }
│ ]
└────
You may instead obtain the decrypted private signing key instead of
the public key, using the `--private' option, for you to use in
toolchains requiring it
┌────
│ python3 -m crypto_licensing -v --private --name "Awesome-Inc" registered --username admin@awesome-inc.com --password password
└────
┌────
│ [
│ "/Users/perry/.crypto-licensing/Awesome-Inc.crypto-keypair",
│ "//////////////////////////////////////////92oVkgRKbk9REmW8pzpgTZCwUp0d9gK+MKGakldmDR9Q==",
│ {
│ "ciphertext":"aef7cf9884bc13bd7b4ee0cde402d2b666a084335f5d7b2bb6d2c31a8910499b5b19d450b2ccab03b83e9bb586612fb2",
│ "salt":"a84d6df719af9f50dcdc1416",
│ "vk":"dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fU=",
│ "vk_signature":"h44cyYJvofemshmvizrN0+LVisMSTcPD1BGBVkwHVbEKbz+zHsNMjczQh91mLgwv8A6mzlbF7jQqznJOQwcxDA=="
│ }
│ ]
└────
Use `jq' to process the JSON output:
┌────
│ python3 -m crypto_licensing -v --private --name "Awesome-Inc" registered --username admin@awesome-inc.com --password password \
│ | jq '.[1]'
└────
┌────
│ "//////////////////////////////////////////92oVkgRKbk9REmW8pzpgTZCwUp0d9gK+MKGakldmDR9Q=="
└────
Of course, if you get the password wrong, then you'll get an error
(we'll never over-write existing files):
┌────
│ python3 -m crypto_licensing -v --name "Awesome-Inc" registered --username admin@awesome-inc.com --password wrong 2>&1
└────
┌────
│ 2024-12-30 16:40:56 WARNING licensing load_keypa Cannot load Keypair(s) from /Users/perry/.crypto-licensing/Awesome-Inc.crypto-keypair: Failed to decrypt ChaCha20Poly1305-encrypted Keypair w/ admin@awesome-inc.com's credentials
│ 2024-12-30 16:40:56 WARNING doh.cli <module> Failed: '/Users/perry/.crypto-licensing/Awesome-Inc.crypto-keypair'
└────
We've provided the (very poor) `--seed 0xff...' option above for
consistency with the API calls in the example above, but you
shouldn't; a random seed will be used to create it, unless you specify
`--no-registering' to prevent creation:
┌────
│ python3 -m crypto_licensing -v --name "Awesome-Again" registered --username admin@awesome-inc.com --password password \
│ --no-registering 2>&1
└────
┌────
│ 2024-12-30 16:40:56 WARNING doh.cli <module> Failed: Failed to find a admin@awesome-inc.com Keypair; registering a new one was declined
└────
But don't worry; if an existing `KeypairEncrypted' file with the
specified name `Awesome-Inc.crypto-keypair' exists anywhere in your
`crypto_licensing' search paths, we won't re-create it if you specify
the wrong password, but will instead report a failure.
It is not recommended to use the `--password ...' command-line option;
specify the password in the `CRYPTO_LIC_PASSWORD' environment
variable, or specify `-' to read it securely from standard input.
`CRYPTO_LIC_USERNAME' may be used instead of `--username'.
1.2 Creating a License: Grants
──────────────────────────────
A License carries a verifiable payload from your organization that
will "grant" some capabilities, privileges or simply data to your
client while they use your software.
1.2.1 Verifiable Data
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
The simplest kind of license just grants some data, in such a way that
your application can load the License, verify that it was issued by
your organization, and remember for the future so it doesn't have to
re-verify the License and associated data.
For example, you might need to issue an API or license key for some
component of your software, and you just want to make sure it's valid
data issued by your organization. Let's create a License using our
`Awesome-Inc' authoring keypair, granting a Python `pysimplegui'
Distribution key:
┌────
│ python3 -m crypto_licensing -v --name "Awesome-Inc" \
│ --why "AwesomePyApp: PySimpleGUI Distribution Key" \
│ license \
│ --username admin@awesome-inc.com --password password \
│ --product "AwesomePyApp" \
│ --domain "awesome-py-app.dominionrnd.com" --no-confirm \
│ --grant '{ "PySimpleGUI": { "License": "ebyzJLMp...20c3" }}' 2>&1
└────
┌────
│ 2024-12-30 16:40:56 NORMAL licensing registered Found AwesomePyApp: PySimpleGUI Distribution Key Keypair at /Users/perry/.crypto-licensing/Awesome-Inc.crypto-keypair: dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fU=
│ 2024-12-30 16:40:56 DETAIL crypto_lic license Authoring Agent ID Pubkey: dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fU=, from /Users/perry/.crypto-licensing/Awesome-Inc.crypto-keypair
│ 2024-12-30 16:40:56 NORMAL crypto_lic license Issued License <LicenseSigned (from '/Users/perry/.crypto-licensing/AwesomePyApp.crypto-license')> in /Users/perry/.crypto-licensing/AwesomePyApp.crypto-license
│ [
│ "/Users/perry/.crypto-licensing/AwesomePyApp.crypto-license",
│ {
│ "license":{
│ "author":{
│ "domain":"awesome-py-app.dominionrnd.com",
│ "name":"Awesome-Inc",
│ "product":"AwesomePyApp",
│ "pubkey":"dqFZIESm5PURJlvKc6YE2QsFKdHfYCvjChmpJXZg0fU="
│ },
│ "dependencies":[],
│ "grant":{
│ "PySimpleGUI":{
│ "License":"ebyzJLMp...20c3"
│ }
│ }
│ },
│ "signature":"tKnpz97zmfg7IlrWOjcO+Row3ZAv/gQRHIAj8I9r8vhNYMygbYz5asCUr5tPBlXmf2QPPYpudeBlnOBLp2hUAg=="
│ }
│ ]
└────
1.2.2 `issue': Signing a License
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
A License can be as simple, free-standing authorization with no other
License dependencies, or it may have a tree of sub-Licenses that must
also be confirmed as valid.
1.2.3 `verify': Confirm License (and sub-License) Validity
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
1.3 Using Licenses
──────────────────
1.3.1 `load_keys': Find all Ed25519 Signing Keys
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
1.3.2 `load': Find all Licenses
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
1.3.3 `check': Find all Keys and Valid Licenses
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
Loads every available Ed25519 Keypairs (with the provided
credentials), and all available Licenses, yielding all
<Keypair>,<LicenseSigned> that are valid in the current environment.
If no valid License is available for some key found, then
<Keypair>,None is yielded, allowing the caller to use the Key to issue
a License if desired.
If nothing at all is yielded, then this indicates that *no* Keypairs
were found; either you need to "register" (create and save) one, or
provide different credentials.
1.4 Running A `crypto_licensing.licensing' Server
─────────────────────────────────────────────────
Supply the `username' and `password' to the `KeypairEncrypted' via
environment variables `CRYPTO_LIC_USERNAME' and `CRYPTO_LIC_PASSWORD'.
2 Payment with Cryptocurrencies
═══════════════════════════════
3 Issuance via Web API
══════════════════════
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file crypto_licensing-6.0.1.tar.gz.
File metadata
- Download URL: crypto_licensing-6.0.1.tar.gz
- Upload date:
- Size: 151.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44beb6811fab410b75a8267afd90f56cb00fa9670eda5dc7f1130b20e5bcf287
|
|
| MD5 |
914d223d0fe42cf2f7578669e207def6
|
|
| BLAKE2b-256 |
49f91cbafde5eca701ba6ff30733fab922b3c04e9fe0b6fb0d20fec22a823a1f
|
Provenance
The following attestation bundles were made for crypto_licensing-6.0.1.tar.gz:
Publisher:
release.yml on pjkundert/crypto-licensing
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
crypto_licensing-6.0.1.tar.gz -
Subject digest:
44beb6811fab410b75a8267afd90f56cb00fa9670eda5dc7f1130b20e5bcf287 - Sigstore transparency entry: 814689292
- Sigstore integration time:
-
Permalink:
pjkundert/crypto-licensing@158ca4fe4ea01800a1bef9f7046baa46bdc74256 -
Branch / Tag:
refs/tags/v6.0.1 - Owner: https://github.com/pjkundert
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@158ca4fe4ea01800a1bef9f7046baa46bdc74256 -
Trigger Event:
push
-
Statement type:
File details
Details for the file crypto_licensing-6.0.1-py3-none-any.whl.
File metadata
- Download URL: crypto_licensing-6.0.1-py3-none-any.whl
- Upload date:
- Size: 156.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e1eb913d20194b76db880192cdbf3b7967408074339abe70ee2ca0aa53c16f9
|
|
| MD5 |
0f2fc0c480e8b46e37e64c702f1577ea
|
|
| BLAKE2b-256 |
9efb53c6387edb3b5860e45e3846559706405dfbe53a33eb8df68e3f0aa80ce5
|
Provenance
The following attestation bundles were made for crypto_licensing-6.0.1-py3-none-any.whl:
Publisher:
release.yml on pjkundert/crypto-licensing
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
crypto_licensing-6.0.1-py3-none-any.whl -
Subject digest:
4e1eb913d20194b76db880192cdbf3b7967408074339abe70ee2ca0aa53c16f9 - Sigstore transparency entry: 814689298
- Sigstore integration time:
-
Permalink:
pjkundert/crypto-licensing@158ca4fe4ea01800a1bef9f7046baa46bdc74256 -
Branch / Tag:
refs/tags/v6.0.1 - Owner: https://github.com/pjkundert
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@158ca4fe4ea01800a1bef9f7046baa46bdc74256 -
Trigger Event:
push
-
Statement type: