Key  Nub License Dongle
1 Installation and the Native Library
library-environment-variable
library-basename
library-candidates
set-library-path!
library-path
loaded-library-path
lib-version
library-version
2 Errors
exn:  fail:  keynub
exn:  fail:  keynub:  library
status-symbol
status-code
status-text
3 Finding and Opening a Dongle
device
devices
dongle?
dongle-open
dongle-open-path
dongle-close
dongle-open?
call-with-dongle
with-dongle
4 Information and Authenticity
dongle-serial
device-info
dongle-info
verification
dongle-verify-genuine
dongle-genuine?
set-dongle-trust-root!
dongle-last-error
5 Sessions and the Write Role
session-open
session-close
call-with-session
with-session
authorize-write
rotate-write-key
6 Records
record
dongle-records
read-record
write-record!
erase-record!
erase-all-records!
7 Counters
read-counter
increment-counter!
8 App-Data Encryption
app-encrypt
app-decrypt
9.3

KeyNub License Dongle🔗ℹ

KeyNub

 (require keynub/licdongle) package: keynub-licdongle

Verify that a KeyNub License Dongle is genuine, read and write the license records it holds, use its hardware counters and seal data so that only a dongle can open it.

(require keynub/licdongle)
 
(define secret
  (with-dongle (d)                ; first dongle, or (d #:serial "...")
    (dongle-verify-genuine d)     ; raises unless genuine
    (with-session d               ; closed on every exit path
      (app-decrypt d sealed))))   ; build the licence check on this

The package calls the SDK’s flat C API from the native library keynub_licdongle_flat through ffi/unsafe. The library is loaded on the first call that needs it, so requiring the module and building this documentation work without it. Every function checks its arguments with a contract before it calls the library.

Read the SDK’s integration security notes before writing the check. (unless (dongle-genuine? d) (exit 1)) is one conditional branch, and patching one of those in a release binary is a beginner exercise. Route something the program needs through app-encrypt and app-decrypt, so removing the check removes the data.

    1 Installation and the Native Library

    2 Errors

    3 Finding and Opening a Dongle

    4 Information and Authenticity

    5 Sessions and the Write Role

    6 Records

    7 Counters

    8 App-Data Encryption

1 Installation and the Native Library🔗ℹ

  raco pkg install keynub-licdongle

The package does not contain the native library. Take the file for your platform from the SDK’s natives folder. The package looks for it in this order:

  1. the path given to set-library-path!;

  2. the path in the environment variable KEYNUB_LICDONGLE_FLAT_LIBRARY;

  3. natives/<platform>/<file name> in the folder of the running program, the current directory and the package’s own folder, and in each of their parent folders, where <platform> is one of win-x64, win-x86, win-arm64, linux-x64, linux-arm64, osx-x64 and osx-arm64;

  4. the bare file name, for the system loader.

A process loads the library once. On Linux, install the udev rule described in the natives folder’s notes so the dongle is accessible without root.

The name of the environment variable that names the library file: "KEYNUB_LICDONGLE_FLAT_LIBRARY".

procedure

(library-basename) → string?

The library’s file name on this operating system: keynub_licdongle_flat.dll, libkeynub_licdongle_flat.so or libkeynub_licdongle_flat.dylib.

procedure

(library-candidates) → (listof string?)

The paths the package tries, in order. The last one is the bare file name, unless a path was set with set-library-path! or in the environment.

procedure

(set-library-path! path) → void?

  path : path-string?
Names the library file to load. Call it before the first dongle call. Raises exn:fail:keynub:library when a different library is already loaded.

procedure

(library-path) → string?

The path of the loaded library, or the first candidate when nothing is loaded yet.

procedure

(loaded-library-path) → (or/c string? #f)

The path of the loaded library; #f before the first call.

struct

(struct lib-version (major minor patch)
    #:transparent)
  major : exact-integer?
  minor : exact-integer?
  patch : exact-integer?
A version of the native library.

procedure

(library-version) → lib-version?

The native library’s version.

2 Errors🔗ℹ

struct

(struct exn:fail:keynub exn:fail (status code operation detail))

  status : (or/c symbol? #f)
  code : exact-integer?
  operation : string?
  detail : string?
Raised by every failed dongle call. status is the name of the SDK status code, for example 'no-device, 'not-genuine or 'auth-required (see status-symbol), and #f for a code this package does not know. code is the raw code, operation the flat API function that failed, and detail the library’s diagnostic text, which may be empty. The message reads operation: status (code), followed by : detail when there is one.

Raised when the native library cannot be loaded or does not export every function of the flat API, and by set-library-path! once a different library is loaded. It is not an exn:fail:keynub.

procedure

(status-symbol code) → (or/c symbol? #f)

  code : exact-integer?
The name of a status code; #f for a code this package does not know. The names and their codes: 'ok 0, 'invalid-arg -1, 'no-device -2, 'access-denied -3, 'io -4, 'timeout -5, 'protocol -6, 'not-genuine -7, 'cert-invalid -8, 'session-expired -9, 'tag-mismatch -10, 'range -11, 'storage-full -12, 'busy -13, 'not-found -14, 'auth-required -15, 'fw-incompatible -16, 'sdk-too-old -17, 'cancelled -18, 'not-implemented -19 and 'internal -20.

procedure

(status-code name) → (or/c exact-integer? #f)

  name : symbol?
The code of a status name; #f for a name this package does not know.

procedure

(status-text code) → string?

  code : (integer-in -2147483648 2147483647)
Human-readable text for a status code, from the library; needs no dongle.

3 Finding and Opening a Dongle🔗ℹ

struct

(struct device (serial path)
    #:transparent)
  serial : string?
  path : string?
An attached dongle: its serial number and its device path.

procedure

(devices) → (listof device?)

The attached dongles.

procedure

(dongle? v) → boolean?

  v : any/c
Returns #t if v is a dongle opened by this package.

procedure

(dongle-open [serial]) → dongle?

  serial : (or/c string? #f) = #f
Opens the dongle with this serial number, or the first one found when serial is #f or empty. Close it with dongle-close; a dongle that becomes unreachable is closed when it is collected.

procedure

(dongle-open-path path) → dongle?

  path : string?
Opens the dongle at this device path (from devices).

procedure

(dongle-close d) → void?

  d : dongle?
Closes the dongle. Closing a closed dongle does nothing; every other call on it raises exn:fail:keynub with status 'invalid-arg.

procedure

(dongle-open? d) → boolean?

  d : dongle?
Whether dongle-close has not been called on d yet.

procedure

(call-with-dongle proc    
  [#:serial serial    
  #:path path]) → any
  proc : (-> dongle? any)
  serial : (or/c string? #f) = #f
  path : (or/c string? #f) = #f
Opens the first dongle, the one with serial number serial or the one at device path path, calls proc with it and closes it on every exit path, exceptions included. Returns what proc returns. Give serial or path, not both.

syntax

(with-dongle (id) body ...+)

(with-dongle (id #:serial serial-expr) body ...+)
(with-dongle (id #:path path-expr) body ...+)
Evaluates the body forms with id bound to an open dongle, as call-with-dongle does, and closes the dongle on every exit path.

4 Information and Authenticity🔗ℹ

procedure

(dongle-serial d) → string?

  d : dongle?
The dongle’s serial number (14 hex digits).

struct

(struct device-info (protocol-major
    protocol-minor
    firmware-major
    firmware-minor
    firmware-patch
    secure-element-ready?
    provisioned?
    watchdog-reboot?
    isolated?
    write-auth-rotated?
    data-capacity
    data-free)
    #:transparent)
  protocol-major : exact-integer?
  protocol-minor : exact-integer?
  firmware-major : exact-integer?
  firmware-minor : exact-integer?
  firmware-patch : exact-integer?
  secure-element-ready? : boolean?
  provisioned? : boolean?
  watchdog-reboot? : boolean?
  isolated? : boolean?
  write-auth-rotated? : boolean?
  data-capacity : exact-integer?
  data-free : exact-integer?
Plaintext device information: the protocol and firmware versions, whether the secure element is ready, whether the dongle is provisioned, whether its previous boot ended in a watchdog reset, whether it is isolated, whether its write-auth key has been rotated away from the factory one, and the size of its data area and the part of it that is free, in bytes.

procedure

(dongle-info d) → device-info?

  d : dongle?
Plaintext device information; needs no session.

struct

(struct verification (serial provisioned-date)
    #:transparent)
  serial : string?
  provisioned-date : string?
The result of a successful dongle-verify-genuine: the serial number and the day the dongle was provisioned, as YYYY-MM-DD, or "" when it reports none. The date is informational; no licensing decision should turn on it.

procedure

(dongle-verify-genuine d) → verification?

  d : dongle?
Proves the dongle is genuine: certificate chain to the trusted root plus a live challenge-response. Returns only when it is; raises exn:fail:keynub otherwise.

procedure

(dongle-genuine? d) → boolean?

  d : dongle?
The boolean form for a gate: #t only when dongle-verify-genuine succeeds. Fails closed: every failure gives #f.

procedure

(set-dongle-trust-root! d der) → void?

  d : dongle?
  der : bytes?
Overrides the CA root (DER) that dongle-verify-genuine checks against. Applications do not need this: the library embeds the KeyNub production root.

procedure

(dongle-last-error d) → string?

  d : dongle?
Diagnostic detail for the most recent failure on this dongle; may be empty.

5 Sessions and the Write Role🔗ℹ

Records, counters and app-data encryption need an authenticated session. Writing records, erasing them and incrementing counters also need the write role.

procedure

(session-open d) → void?

  d : dongle?
Opens an authenticated session.

procedure

(session-close d) → void?

  d : dongle?
Closes the session.

procedure

(call-with-session d thunk) → any

  d : dongle?
  thunk : (-> any)
Opens a session, calls thunk and closes the session on every exit path, exceptions included. Returns what thunk returns.

syntax

(with-session dongle-expr body ...+)

Evaluates the body forms inside a session on the dongle, as call-with-session does.

procedure

(authorize-write d key) → void?

  d : dongle?
  key : bytes?
Elevates the session to the write role with a write-auth key (P-256 PKCS#8 DER). Belongs in licence-issuing tooling, not in the application your users run.

procedure

(rotate-write-key d key) → void?

  d : dongle?
  key : bytes?
Replaces the dongle’s write-auth key with key (P-256 PKCS#8 DER). Call authorize-write first. From the next session on, only the new key elevates. A dongle ships holding the factory write-auth key, which is public; rotate it before shipping a dongle on.

6 Records🔗ℹ

struct

(struct record (name size)
    #:transparent)
  name : string?
  size : exact-integer?
A record on the dongle: its name and its size in bytes.

procedure

(dongle-records d) → (listof record?)

  d : dongle?
The records on the dongle.

procedure

(read-record d name) → bytes?

  d : dongle?
  name : string?
The content of a record. Raises exn:fail:keynub with status 'not-found when there is none of that name.

procedure

(write-record! d name data) → void?

  d : dongle?
  name : string?
  data : bytes?
Writes a record, replacing one of the same name. Needs the write role.

procedure

(erase-record! d name) → void?

  d : dongle?
  name : string?
Erases one record. Needs the write role.

procedure

(erase-all-records! d) → void?

  d : dongle?
Erases every record. Needs the write role. This is the only call that erases more than one record; erase-record! erases the one record it names.

7 Counters🔗ℹ

procedure

(read-counter d counter-id) → exact-integer?

  d : dongle?
  counter-id : (integer-in -2147483648 2147483647)
The value of a hardware monotonic counter.

procedure

(increment-counter! d counter-id) → exact-integer?

  d : dongle?
  counter-id : (integer-in -2147483648 2147483647)
Increments a counter and returns the new value. Needs the write role.

8 App-Data Encryption🔗ℹ

procedure

(app-encrypt d scope plaintext) → bytes?

  d : dongle?
  scope : (or/c 'device 'developer)
  plaintext : bytes?
Seals data so that only a dongle can open it: this one ('device) or any dongle issued by the same developer ('developer). Build the licence check on this pair: put something the program needs through it, so removing the check removes the data.

procedure

(app-decrypt d packed) → bytes?

  d : dongle?
  packed : bytes?
Opens data sealed with app-encrypt. Raises exn:fail:keynub with status 'tag-mismatch when the data was altered.