license

CLI

There’s a CLI tool to create and parse licenses called confirm-license included in this package:

usage: confirm-license [-h] [-d] [-k KEY] {create,parse} ...

The CLI tool to create a confirm license.

positional arguments:
  {create,parse}

optional arguments:
  -h, --help         show this help message and exit
  -d, --debug        activate the debug logging
  -k KEY, --key KEY  the key for the license hashing (defaults to confirm)

Classes

The classes for handling confirm licenses.

exception confirm.utils.license.ExpiredLicense

Error which is thrown when the license is expired.

exception confirm.utils.license.InvalidLicense

Error which is thrown when the license is invalid.

class confirm.utils.license.License(key)

The license class which can be used to create and parse licenses.

Parameters:

key (str) – The encryption key

Note

The data format of the license is properitary and is designed like this:

{Header}
{Base64-Data}
{Footer}

The Header & Footer are fixed, while the Base64-Data is a Base64 encoded string of the following data structure:

{Optional-Data}
{Expiry}
{Hash}

The Optional-Data is unencoded/unencrypted data block which can include any custom data.

The Expiry is an ISO 8601 expiration timestamp of the license in form of a Expiry: YYYY-MM-DDTHH:MM:SS key-value pair. The Hash is a SHA-2 hash of the Optional-Data & Expiry strings.

Hint

A new license can be created like this:

>>> from datetime import datetime
>>> lic_str = License(key='test').create(expiry=datetime(2021, 8, 5))
>>> print(lic_str)
-----START LICENSE-----
RXhwaXJ5OiAyMDIxLTA4LTA1VDAwOjAwOjAwCmQ4YWEzYjJjOWNlNjVhYTk1YmQ1
MTE2NzliZjFhMTI4NjU3NzY5N2M4NjNkYjk5NGEwZDYxMGZmNjk5OTI4NmE=
-----END LICENSE-----

Additional data can be added like this:

>>> from datetime import datetime
>>> lic_str = License(key='test').create(expiry=datetime(2021, 8, 5), data='spam')
>>> print(lic_str)
-----START LICENSE-----
c3BhbQpFeHBpcnk6IDIwMjEtMDgtMDVUMDA6MDA6MDAKYTllNWIyMDVhMzljNzQ0
M2U3MGY1ZTcyZDAyMWZiNjRmZTg3NWFjYWQ3MDc1NGM2YTMwZTU2OWE2OTEzNTcy
Nw==
-----END LICENSE-----

A license can then be parsed like this:

>>> lic_str = '''
... -----START LICENSE-----
... RXhwaXJ5OiAyMDk5LTA4LTA1VDAwOjAwOjAwCjA2ZGFhYWRlYjM3YjUzOGE0YmVj
... Yzg2NzVlYmI5NjJlYjhkZjQ0MDRjMmVmNjA4YTBhNWZlNmE0NmIxODcxZDU=
... -----END LICENSE-----
... '''
>>> License('test').parse(lic_str)
'Expiry: 2099-08-05T00:00:00'

An expired license will raise a ExpiredLicense exception:

>>> lic_str = '''
... -----START LICENSE-----
... RXhwaXJ5OiAyMDIxLTA4LTA1VDAwOjAwOjAwCmQ4YWEzYjJjOWNlNjVhYTk1YmQ1
... MTE2NzliZjFhMTI4NjU3NzY5N2M4NjNkYjk5NGEwZDYxMGZmNjk5OTI4NmE=
... -----END LICENSE-----
... '''
>>> License('test').parse(lic_str)
Traceback (most recent call last):
    ...
confirm.utils.license.ExpiredLicense: License is expired since 2021-08-05 00:00:00

If the expiry timestamp is missing, a InvalidLicense exception is raised:

>>> lic_str = '''
... -----START LICENSE-----
... CmFkNzExNDhjNzlmMjFhYjllZWM1MWVhNWM3ZGQyYjY2ODc5MmY3YzBkMzUzNGFl
... NjZiMjJmNzFjNjE1MjNmYjM=
... -----END LICENSE-----
... '''
>>> License('test').parse(lic_str)
Traceback (most recent call last):
    ...
confirm.utils.license.InvalidLicense: No expiry timestamp found

An invalid hash will also result in a raised InvalidLicense exception:

>>> lic_str = '''
... -----START LICENSE-----
... RXhwaXJ5OiAyMDk5LTA4LTA1VDAwOjAwOjAwCjAwZGFhYWRlYjM3YjUzOGE0YmVj
... YzgwNzVlYmI5MDJlYjhkZjQ0MDRjMmVmMDA4YTBhNWZlMGE0MGIxODcxZDU=
... -----END LICENSE-----
... '''
>>> License('test').parse(lic_str)
Traceback (most recent call last):
    ...
confirm.utils.license.InvalidLicense: License hash is invalid

And finally, a completly missing license will also raise a InvalidLicense exception:

>>> License('test').parse('no valid license')
Traceback (most recent call last):
    ...
confirm.utils.license.InvalidLicense: No valid license string found
create(expiry, data=None)

Create a new license.

Parameters:
  • expiry (datetime.datetime) – The expiration date

  • data (str) – Additional license data

Returns:

The license string

Return type:

str

create_hash(data)

Create the hash for the data string.

Parameters:

data (str) – The data string

Returns:

The hash

Return type:

str

parse(license_string, verify=True)

Parse a license and unpack the data.

Parameters:
  • license_string (str) – The license string

  • verify (bool) – Verify the license & expiry

Returns:

The license data

Return type:

confirm.utils.license.LicenseData

Raises:
class confirm.utils.license.LicenseData

License data which acts like a string but will add an additional helper method which can be used to fetch values of colon-separated (:) key-value pairs.

Hint

This string class works like this:

>>> input_str = '''
... spam: eggs 1
... spam: eggs 2
... foo: bar 1
... foo: bar 2
... '''
>>> lic_str = LicenseData(input_str)
>>> lic_str.get_values('spam')
['eggs 1', 'eggs 2']
>>> lic_str.get_values('FOO')
['bar 1', 'bar 2']
>>> lic_str.get_values('unavailable')
[]

The white space after the colon (:) is optional and superfluous white spaces will automatically be stripped:

>>> input_str = '''
... spam:eggs 1
... spam: eggs 2
... spam:  eggs 3
... '''
>>> lic_str = LicenseData(input_str)
>>> lic_str.get_values('spam')
['eggs 1', 'eggs 2', 'eggs 3']
get_values(key)

The values.

Parameters:

key (str) – The key

Returns:

The values matching the key

Return type:

list

exception confirm.utils.license.LicenseError

Error which is thrown when a license error occurs.