Aker.LicenceFile (Aker v0.1.0)

Copy Markdown View Source

Reads a signed licence file and checks every signature and field in it.

The file is one JSON object with four base64 fields:

  • licence: the licence JSON bytes, signed by the issuer's working key;
  • signature: that signature, 64 bytes of Ed25519;
  • certificate: the working key's certificate JSON bytes;
  • certificate_signature: the root key's signature of the certificate.

verify/2 checks the signature over the exact bytes it received before it parses them, and never re-encodes the JSON. Each refusal is its own atom and its own log line.

The checks run in this order, and the first failure answers:

  1. :bad_envelope: the file is over 64 KB, is not the four-field object, or a field has the wrong length or is not base64. Sizes are checked before anything is decoded.
  2. :unknown_root: no root key in :root_keys signed the certificate. An empty list refuses every file here.
  3. :bad_certificate: the certificate's fields are malformed, or it is not for a licence working key.
  4. :bad_signature: the certificate's key did not sign the licence.
  5. :bad_licence: the licence's fields are malformed.
  6. :wrong_working_key: the licence names another working key id.
  7. :certificate_out_of_date: the certificate was not in date when the licence was issued, or is not now.
  8. :wrong_product: the licence is for another product. One root key can sign for several products, so this check keeps one product's licence off another.
  9. :wrong_machine: the licence names another machine id.
  10. :wrong_install_key: the licence names another install key, or the install key's own error (:install_key_missing, :install_key_unreadable, :install_key_timeout) when this install has none to compare.
  11. :install_key_proof_failed: this install's key did not sign a fresh random value that verifies against the licence's key.
  12. :clock_behind: the clock reads more than a day before the latest time the app has seen, so setting it back cannot extend a term.
  13. :term_not_started or :term_ended.
  14. :older_serial: the licence is older than the one already stored.

Summary

Functions

What a working key's certificate says it is for. A root key's signature on any other JSON never passes as a certificate.

The four parts as the file's bytes. Aker.Issuer writes a licence through this, so the issuer and the reader share one shape.

Whether features is a list a licence may carry: at most 32 names, each 1 to 32 bytes of a-z, 0-9, _, . and -.

Whether licence JSON bytes fit the size this module reads.

The largest licence file verify/2 reads, in bytes.

The licence in bytes as Aker.Terms, when every check passes for this install.

Types

refusal()

@type refusal() ::
  :bad_envelope
  | :unknown_root
  | :bad_certificate
  | :bad_signature
  | :bad_licence
  | :wrong_working_key
  | :certificate_out_of_date
  | :wrong_product
  | :wrong_machine
  | :wrong_install_key
  | :install_key_missing
  | :install_key_unreadable
  | :install_key_timeout
  | :install_key_proof_failed
  | :clock_behind
  | :term_not_started
  | :term_ended
  | :older_serial

Functions

certificate_purpose()

@spec certificate_purpose() :: String.t()

What a working key's certificate says it is for. A root key's signature on any other JSON never passes as a certificate.

encode(map)

@spec encode(%{
  licence: binary(),
  signature: binary(),
  certificate: binary(),
  certificate_signature: binary()
}) :: binary()

The four parts as the file's bytes. Aker.Issuer writes a licence through this, so the issuer and the reader share one shape.

features?(features)

@spec features?(term()) :: boolean()

Whether features is a list a licence may carry: at most 32 names, each 1 to 32 bytes of a-z, 0-9, _, . and -.

licence_fits?(bytes)

@spec licence_fits?(binary()) :: boolean()

Whether licence JSON bytes fit the size this module reads.

max_bytes()

@spec max_bytes() :: pos_integer()

The largest licence file verify/2 reads, in bytes.

verify(bytes, opts)

@spec verify(binary(), keyword()) :: {:ok, Aker.Terms.t()} | {:error, refusal()}

The licence in bytes as Aker.Terms, when every check passes for this install.

Options:

  • :product: the product this app is (required);
  • :root_keys: the trusted Ed25519 root public keys, raw 32-byte binaries (required; see Aker.PublicKeys.decode/1);
  • :machine_id_hash: this host's machine id hash (required);
  • :install_key: {module, opts} of this install's key (required);
  • :now: the time to check against (default now);
  • :last_seen: the latest time the app has seen, or nil;
  • :stored_serial: the serial of the licence already stored, or nil.