NAME
KeyNub::LicDongle - Perl binding for the KeyNub USB-C license dongle
SYNOPSIS
use KeyNub::LicDongle;
my $dongle = KeyNub::LicDongle->open; # first dongle, or ->open($serial)
$dongle->verify_genuine; # dies unless genuine
$dongle->session_open;
my $data = $dongle->app_decrypt($blob); # <- build the licence check on this
$dongle->session_close;
$dongle->close;
DESIGN
This binding calls the SDK's flat companion API (keynub_licdongle_flat) rather than the core ABI, which is what COBOL and Fortran do and for the same reason. The flat API has no structs, no library-allocated buffers and no callbacks, so nothing here hand-writes a struct layout with computed padding — the one class of mistake in an FFI binding that produces plausible wrong values instead of a crash.
The cost is that there is no progress reporting: the flat API has no callbacks. Records are read in one call.
Requires FFI::Platypus, the one dependency. Set KEYNUB_LICDONGLE_FLAT_LIBRARY to point at a specific library.
SECURITY
Read docs/integration-security.md before writing the check. exit unless $dongle->is_genuine is one line to delete, and Perl ships as source. What cannot be deleted is data the program needs and only the dongle can decrypt — put it through app_encrypt/app_decrypt.
Errors
Failures die with a KeyNub::LicDongle::Error object, which stringifies for a plain die handler and carries status, operation and detail for code that wants to branch. Compare $err->status against $STATUS{NO_DEVICE} and friends.
library_version
my ($major, $minor, $patch) = KeyNub::LicDongle::library_version();
device_count / device_serial
Enumerates without opening. device_count takes the snapshot that device_serial indexes into, so call them back to back.
open
my $dongle = KeyNub::LicDongle->open; # first dongle found
my $dongle = KeyNub::LicDongle->open($serial);
Dies with $STATUS{NO_DEVICE} when none is attached.
open_simulated
Opens a dongle backed by the in-process software simulator. Present only when the loaded library was built with the simulator compiled in, which the shipping library is not.
close
Releases the dongle. Safe to call more than once, and called from DESTROY. The library holds 32 handles at a time, so a loop that forgets will notice.
last_error_detail
The SDK's diagnostic detail for the most recent failure. Log it; do not parse it.
set_trust_root
Overrides the CA root that verify_genuine checks against. Applications do not need this: a release build embeds the KeyNub production root. It exists for dongles provisioned against a different CA, and for vendor tooling.
serial
The dongle serial as hex.
info
A hashref of the plaintext device info. watchdog_reboot means the dongle's previous boot ended in a watchdog timeout: the firmware hung and reset itself. It is the only trace a field hang leaves behind, and a power cycle clears it, so log it.
verify_genuine
Proves authenticity: the certificate chain to the trusted root plus a live ECDSA challenge-response. Dies unless the dongle is genuine; returns a hashref with genuine and serial.
is_genuine
The non-dying form, for a licence gate. Fails closed: a missing dongle, an I/O error and an invalid certificate all return false.
session_open / session_close
Opens and ends the encrypted session (P-256 ECDH, HKDF-SHA256, AES-256-GCM).
authorize_write
Elevates to the write role with the developer master key (a DER EC private key). This belongs in your licence-issuing tooling; never ship that key in the application your users run.
rotate_write_key($new_key_der)
Replaces the dongle's write-auth key with your own (a DER EC private key). Call authorize_write with the current key first. From the next session on, only the new key elevates.
records
An arrayref of { name, size } hashrefs.
read_record
my $data = $dongle->read_record('license');
Two calls under the hood: the flat API answers a zero-capacity read with RANGE and the size needed, so nothing has to guess a buffer size.
write_record
Atomically replaces a record. Requires the write role.
erase_record / erase_all_records
Erasing everything is a separate method on purpose: in the C API a null name means "erase every record", and an accidentally empty Perl variable must not do that.
read_counter / increment_counter
Hardware monotonic counters. Incrementing is irreversible and needs the write role.
app_encrypt / app_decrypt
my $blob = $dongle->app_encrypt($SCOPE_DEVELOPER, $plaintext);
my $data = $dongle->app_decrypt($blob);
The pair to build a licence check on: put something the program genuinely needs through it, so removing the check removes the data. $SCOPE_DEVELOPER lets any dongle you have issued decrypt, so one blob ships to every customer; $SCOPE_DEVICE locks it to one dongle.
LICENSE
Apache-2.0, like the rest of the SDK.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 32:
Non-ASCII character seen before =encoding in '—'. Assuming UTF-8