1:- module(keynub_licdongle,
2 [ library_environment_variable/1, 3 library_basename/1, 4 library_candidates/1, 5 set_library_path/1, 6 library_path/1, 7 loaded_library_path/1, 8 library_version/1, 9 status_name/2, 10 status_text/2, 11 devices/1, 12 is_dongle/1, 13 dongle_open/1, 14 dongle_open/2, 15 dongle_open_path/2, 16 dongle_close/1, 17 dongle_is_open/1, 18 with_dongle/2, 19 with_dongle/3, 20 dongle_serial/2, 21 dongle_info/2, 22 dongle_verify_genuine/1, 23 dongle_verify_genuine/3, 24 dongle_genuine/1, 25 set_dongle_trust_root/2, 26 dongle_last_error/2, 27 session_open/1, 28 session_close/1, 29 with_session/2, 30 authorize_write/2, 31 rotate_write_key/2, 32 dongle_records/2, 33 read_record/3, 34 write_record/3, 35 erase_record/2, 36 erase_all_records/1, 37 read_counter/3, 38 increment_counter/3, 39 app_encrypt/4, 40 app_decrypt/3 41 ]). 42:- use_module(library(error)). 43:- use_module(library(lists)). 44:- use_module(library(option)).
70:- use_foreign_library(foreign(keynub_licdongle4pl)). 71
72:- meta_predicate
73 with_dongle(-, 0),
74 with_dongle(+, -, 0),
75 with_session(+, 0). 76
77:- multifile
78 prolog:error_message//1. 79
80:- dynamic
81 chosen_path/1,
82 module_folder/1. 83
84:- prolog_load_context(directory, Dir),
85 retractall(module_folder(_)),
86 assertz(module_folder(Dir)). 87
88
96library_environment_variable('KEYNUB_LICDONGLE_FLAT_LIBRARY').
97
98native_folder('win-x64').
99native_folder('win-x86').
100native_folder('win-arm64').
101native_folder('linux-x64').
102native_folder('linux-arm64').
103native_folder('osx-x64').
104native_folder('osx-arm64').
110library_basename(File) :-
111 ( current_prolog_flag(windows, true)
112 -> File = 'keynub_licdongle_flat.dll'
113 ; apple
114 -> File = 'libkeynub_licdongle_flat.dylib'
115 ; File = 'libkeynub_licdongle_flat.so'
116 ).
117
118apple :-
119 current_prolog_flag(apple, true),
120 !.
121apple :-
122 current_prolog_flag(arch, Arch),
123 sub_atom(Arch, _, _, _, darwin),
124 !.
132set_library_path(Path) :-
133 must_be(text, Path),
134 atom_string(File, Path),
135 with_mutex(keynub_licdongle, set_library_path_(File)).
136
137set_library_path_(File) :-
138 ( licd_loaded_path(Loaded),
139 Loaded \== File
140 -> format(string(Message),
141 "the KeyNub library is already loaded from ~w; a process loads it once",
142 [Loaded]),
143 throw(error(keynub_library_error(Message), _))
144 ; retractall(chosen_path(_)),
145 assertz(chosen_path(File))
146 ).
153loaded_library_path(Path) :-
154 licd_loaded_path(Path).
160library_path(Path) :-
161 ( licd_loaded_path(Loaded)
162 -> Path = Loaded
163 ; library_candidates([Path|_])
164 ).
174library_candidates(Paths) :-
175 chosen_path(Path),
176 !,
177 Paths = [Path].
178library_candidates(Paths) :-
179 library_environment_variable(Variable),
180 getenv(Variable, Path),
181 Path \== '',
182 !,
183 Paths = [Path].
184library_candidates(Paths) :-
185 library_basename(Base),
186 start_folders(Starts),
187 findall(File,
188 ( member(Start, Starts),
189 folder_and_parents(Start, Dirs),
190 member(Dir, Dirs),
191 native_folder(Folder),
192 atomic_list_concat([natives, Folder, Base], /, Relative),
193 directory_file_path(Dir, Relative, File),
194 exists_file(File)
195 ),
196 Found0),
197 list_to_set(Found0, Found),
198 append(Found, [Base], Paths).
199
200start_folders(Folders) :-
201 findall(Folder,
202 ( start_folder(Folder0),
203 absolute_file_name(Folder0, Folder1),
204 no_trailing_slash(Folder1, Folder)
205 ),
206 Folders0),
207 list_to_set(Folders0, Folders).
208
209start_folder(Folder) :-
210 program_file(File),
211 file_directory_name(File, Folder).
212start_folder(Folder) :-
213 working_directory(Folder, Folder).
214start_folder(Folder) :-
215 module_folder(Folder).
216
217program_file(File) :-
218 current_prolog_flag(associated_file, File),
219 !.
220program_file(File) :-
221 current_prolog_flag(executable, File).
222
223no_trailing_slash(Dir0, Dir) :-
224 atom_concat(Dir1, '/', Dir0),
225 Dir1 \== '',
226 \+ sub_atom(Dir1, _, _, 0, ':'),
227 !,
228 Dir = Dir1.
229no_trailing_slash(Dir, Dir).
230
231folder_and_parents(Dir, [Dir|Parents]) :-
232 file_directory_name(Dir, Parent0),
233 drive_root(Parent0, Parent),
234 ( ( Parent == Dir ; Parent == '.' ; Parent == '' )
235 -> Parents = []
236 ; folder_and_parents(Parent, Parents)
237 ).
238
239drive_root(Dir, Root) :-
240 atom_length(Dir, 2),
241 sub_atom(Dir, 1, 1, 0, ':'),
242 !,
243 atom_concat(Dir, '/', Root).
244drive_root(Dir, Dir).
245
246ensure_library :-
247 licd_loaded_path(_),
248 !.
249ensure_library :-
250 with_mutex(keynub_licdongle, load_library).
251
252load_library :-
253 licd_loaded_path(_),
254 !.
255load_library :-
256 library_candidates(Candidates),
257 load_first(Candidates, []).
258
259load_first([], Reasons0) :-
260 reverse(Reasons0, Reasons),
261 atomic_list_concat(Reasons, ', ', Tried),
262 format(string(Message), "cannot load the KeyNub library; tried ~w", [Tried]),
263 throw(error(keynub_library_error(Message), _)).
264load_first([Path|Paths], Reasons) :-
265 os_path(Path, OsPath),
266 licd_load_library(OsPath, Path, Result),
267 ( Result == true
268 -> true
269 ; format(string(Reason), "~w (~w)", [Path, Result]),
270 load_first(Paths, [Reason|Reasons])
271 ).
272
273os_path(Path, OsPath) :-
274 ( sub_atom(Path, _, _, _, /)
275 -> prolog_to_os_filename(Path, OsPath)
276 ; OsPath = Path
277 ).
283library_version(version(Major, Minor, Patch)) :-
284 ensure_library,
285 licd_version(Rc, Major, Minor, Patch),
286 check(licdf_version, none, Rc).
287
288
297status_name(0, ok).
298status_name(-1, invalid_arg).
299status_name(-2, no_device).
300status_name(-3, access_denied).
301status_name(-4, io).
302status_name(-5, timeout).
303status_name(-6, protocol).
304status_name(-7, not_genuine).
305status_name(-8, cert_invalid).
306status_name(-9, session_expired).
307status_name(-10, tag_mismatch).
308status_name(-11, range).
309status_name(-12, storage_full).
310status_name(-13, busy).
311status_name(-14, not_found).
312status_name(-15, auth_required).
313status_name(-16, fw_incompatible).
314status_name(-17, sdk_too_old).
315status_name(-18, cancelled).
316status_name(-19, not_implemented).
317status_name(-20, internal).
323status_text(Code, Text) :-
324 must_be(integer, Code),
325 ensure_library,
326 licd_strerror(Code, Rc, Text0),
327 ( Rc == 0
328 -> Text = Text0
329 ; status_atom(Code, Name),
330 atom_string(Name, Text)
331 ).
332
333status_atom(Code, Name) :-
334 ( status_name(Code, Name0)
335 -> Name = Name0
336 ; Name = unknown
337 ).
338
342
343keynub_error(Operation, Code, Detail,
344 error(keynub_error(Status, Code, Operation, Detail), _)) :-
345 status_atom(Code, Status).
346
347check(_, _, 0) :-
348 !.
349check(Operation, Dongle, Rc) :-
350 detail_of(Dongle, Detail),
351 keynub_error(Operation, Rc, Detail, Error),
352 throw(Error).
353
354detail_of(Dongle, Detail) :-
355 ( Dongle \== none,
356 catch(dongle_last_error(Dongle, Detail0), _, fail)
357 -> Detail = Detail0
358 ; Detail = ""
359 ).
360
361prolog:error_message(keynub_error(Status, Code, Operation, Detail)) -->
362 [ '~w: ~w (~w)'-[Operation, Status, Code] ],
363 ( { Detail == "" }
364 -> []
365 ; [ ': ~w'-[Detail] ]
366 ).
367prolog:error_message(keynub_library_error(Message)) -->
368 [ '~w'-[Message] ].
369
370
378devices(Devices) :-
379 ensure_library,
380 licd_device_count(Rc, Count),
381 check(licdf_device_count, none, Rc),
382 Last is Count - 1,
383 findall(device(Serial, Path),
384 ( between(0, Last, Index),
385 licd_device_serial(Index, Rc1, Serial0),
386 check(licdf_device_serial, none, Rc1),
387 licd_device_path(Index, Rc2, Path0),
388 check(licdf_device_path, none, Rc2),
389 Serial = Serial0,
390 Path = Path0
391 ),
392 Devices).
399is_dongle(Term) :-
400 licd_is_dongle(Term).
401
402must_be_dongle(Dongle) :-
403 ( licd_is_dongle(Dongle)
404 -> true
405 ; var(Dongle)
406 -> instantiation_error(Dongle)
407 ; type_error(keynub_dongle, Dongle)
408 ).
417dongle_open(Dongle) :-
418 dongle_open("", Dongle).
419
420dongle_open(Serial, Dongle) :-
421 must_be(text, Serial),
422 ensure_library,
423 licd_open(Serial, Rc, Dongle0),
424 check(licdf_open, none, Rc),
425 Dongle = Dongle0.
431dongle_open_path(Path, Dongle) :-
432 must_be(text, Path),
433 ensure_library,
434 licd_open_path(Path, Rc, Dongle0),
435 check(licdf_open_path, none, Rc),
436 Dongle = Dongle0.
442dongle_is_open(Dongle) :-
443 must_be_dongle(Dongle),
444 licd_dongle_handle(Dongle, Handle),
445 Handle > 0.
452dongle_close(Dongle) :-
453 must_be_dongle(Dongle),
454 licd_close(Dongle, Rc),
455 check(licdf_close, none, Rc).
456
457close_quietly(Dongle) :-
458 catch(dongle_close(Dongle), _, true).
467with_dongle(Dongle, Goal) :-
468 with_dongle([], Dongle, Goal).
469
470with_dongle(Options, Dongle, Goal) :-
471 must_be(list, Options),
472 ( option(serial(Serial), Options),
473 option(path(Path), Options)
474 -> domain_error(serial_or_path, [serial(Serial), path(Path)])
475 ; true
476 ),
477 setup_call_cleanup(
478 open_by_options(Options, Dongle),
479 once(Goal),
480 close_quietly(Dongle)).
481
482open_by_options(Options, Dongle) :-
483 ( option(path(Path), Options)
484 -> dongle_open_path(Path, Dongle)
485 ; option(serial(Serial), Options, ""),
486 dongle_open(Serial, Dongle)
487 ).
488
489
497dongle_serial(Dongle, Serial) :-
498 must_be_dongle(Dongle),
499 ensure_library,
500 licd_get_serial(Dongle, Rc, Serial0),
501 check(licdf_get_serial, Dongle, Rc),
502 Serial = Serial0.
512dongle_info(Dongle, Info) :-
513 must_be_dongle(Dongle),
514 ensure_library,
515 licd_get_info(Dongle, Rc, Raw),
516 check(licdf_get_info, Dongle, Rc),
517 Raw = info(PMajor, PMinor, FMajor, FMinor, FPatch, Flags, Capacity, Free),
518 flag_value(Flags, 0x01, SecureElementReady),
519 flag_value(Flags, 0x02, Provisioned),
520 flag_value(Flags, 0x04, WatchdogReboot),
521 flag_value(Flags, 0x08, Isolated),
522 flag_value(Flags, 0x10, WriteAuthRotated),
523 dict_create(Info, dongle_info,
524 [ protocol_major-PMajor,
525 protocol_minor-PMinor,
526 firmware_major-FMajor,
527 firmware_minor-FMinor,
528 firmware_patch-FPatch,
529 secure_element_ready-SecureElementReady,
530 provisioned-Provisioned,
531 watchdog_reboot-WatchdogReboot,
532 isolated-Isolated,
533 write_auth_rotated-WriteAuthRotated,
534 data_capacity-Capacity,
535 data_free-Free
536 ]).
537
538flag_value(Flags, Bit, Value) :-
539 ( Flags /\ Bit =\= 0
540 -> Value = true
541 ; Value = false
542 ).
552dongle_verify_genuine(Dongle) :-
553 dongle_verify_genuine(Dongle, _, _).
554
555dongle_verify_genuine(Dongle, Serial, ProvisionedDate) :-
556 must_be_dongle(Dongle),
557 ensure_library,
558 licd_verify_genuine(Dongle, Rc, Genuine, Serial0, Date0),
559 check(licdf_verify_genuine, Dongle, Rc),
560 ( Genuine =:= 0
561 -> keynub_error(licdf_verify_genuine, -7, "", Error),
562 throw(Error)
563 ; Serial = Serial0,
564 ProvisionedDate = Date0
565 ).
572dongle_genuine(Dongle) :-
573 catch(dongle_verify_genuine(Dongle), Error, genuine_failure(Error)).
574
575genuine_failure(Error) :-
576 ( control_exception(Error)
577 -> throw(Error)
578 ; fail
579 ).
580
581control_exception(Error) :-
582 var(Error),
583 !,
584 fail.
585control_exception('$aborted').
586control_exception(unwind(_)).
587control_exception(time_limit_exceeded).
588control_exception(time_limit_exceeded(_)).
596set_dongle_trust_root(Dongle, Der) :-
597 must_be_dongle(Dongle),
598 must_be(text, Der),
599 ensure_library,
600 licd_set_trust_root(Dongle, Der, Rc),
601 check(licdf_set_trust_root, Dongle, Rc).
608dongle_last_error(Dongle, Detail) :-
609 must_be_dongle(Dongle),
610 ensure_library,
611 licd_last_error(Dongle, Rc, Detail0),
612 ( Rc == 0
613 -> Detail = Detail0
614 ; Detail = ""
615 ).
616
617
626session_open(Dongle) :-
627 must_be_dongle(Dongle),
628 ensure_library,
629 licd_session_open(Dongle, Rc),
630 check(licdf_session_open, Dongle, Rc).
636session_close(Dongle) :-
637 must_be_dongle(Dongle),
638 ensure_library,
639 licd_session_close(Dongle, Rc),
640 check(licdf_session_close, Dongle, Rc).
647with_session(Dongle, Goal) :-
648 setup_call_cleanup(
649 session_open(Dongle),
650 once(Goal),
651 catch(session_close(Dongle), _, true)).
658authorize_write(Dongle, Key) :-
659 must_be_dongle(Dongle),
660 must_be(text, Key),
661 ensure_library,
662 licd_write_auth(Dongle, Key, Rc),
663 check(licdf_write_auth, Dongle, Rc).
671rotate_write_key(Dongle, Key) :-
672 must_be_dongle(Dongle),
673 must_be(text, Key),
674 ensure_library,
675 licd_write_auth_rotate(Dongle, Key, Rc),
676 check(licdf_write_auth_rotate, Dongle, Rc).
677
678
686dongle_records(Dongle, Records) :-
687 must_be_dongle(Dongle),
688 ensure_library,
689 licd_record_count(Dongle, Rc, Count),
690 check(licdf_record_count, Dongle, Rc),
691 Last is Count - 1,
692 findall(record(Name, Size),
693 ( between(0, Last, Index),
694 licd_record_name(Dongle, Index, Rc1, Name0, Size0),
695 check(licdf_record_name, Dongle, Rc1),
696 Name = Name0,
697 Size = Size0
698 ),
699 Records).
705read_record(Dongle, Name, Bytes) :-
706 must_be_dongle(Dongle),
707 must_be(text, Name),
708 ensure_library,
709 licd_record_read(Dongle, Name, Rc, Bytes0),
710 check(licdf_record_read, Dongle, Rc),
711 Bytes = Bytes0.
717write_record(Dongle, Name, Bytes) :-
718 must_be_dongle(Dongle),
719 must_be(text, Name),
720 must_be(text, Bytes),
721 ensure_library,
722 licd_record_write(Dongle, Name, Bytes, Rc),
723 check(licdf_record_write, Dongle, Rc).
729erase_record(Dongle, Name) :-
730 must_be_dongle(Dongle),
731 must_be(text, Name),
732 ensure_library,
733 licd_record_erase(Dongle, Name, Rc),
734 check(licdf_record_erase, Dongle, Rc).
740erase_all_records(Dongle) :-
741 must_be_dongle(Dongle),
742 ensure_library,
743 licd_record_erase_all(Dongle, Rc),
744 check(licdf_record_erase_all, Dongle, Rc).
745
746
754read_counter(Dongle, CounterId, Value) :-
755 must_be_dongle(Dongle),
756 must_be(integer, CounterId),
757 ensure_library,
758 licd_counter_read(Dongle, CounterId, Rc, Value0),
759 check(licdf_counter_read, Dongle, Rc),
760 Value = Value0.
766increment_counter(Dongle, CounterId, Value) :-
767 must_be_dongle(Dongle),
768 must_be(integer, CounterId),
769 ensure_library,
770 licd_counter_increment(Dongle, CounterId, Rc, Value0),
771 check(licdf_counter_increment, Dongle, Rc),
772 Value = Value0.
773
774
784app_encrypt(Dongle, Scope, Plaintext, Sealed) :-
785 must_be_dongle(Dongle),
786 must_be(oneof([device, developer]), Scope),
787 must_be(text, Plaintext),
788 scope_value(Scope, Value),
789 ensure_library,
790 licd_app_encrypt(Dongle, Value, Plaintext, Rc, Sealed0),
791 check(licdf_app_encrypt, Dongle, Rc),
792 Sealed = Sealed0.
793
794scope_value(device, 0).
795scope_value(developer, 1).
802app_decrypt(Dongle, Sealed, Plaintext) :-
803 must_be_dongle(Dongle),
804 must_be(text, Sealed),
805 ensure_library,
806 licd_app_decrypt(Dongle, Sealed, Rc, Plaintext0),
807 check(licdf_app_decrypt, Dongle, Rc),
808 Plaintext = Plaintext0
KeyNub License Dongle
Verify that a KeyNub 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.
The pack calls the SDK's flat C API (
keynub_licdongle_flat) from the native library, which its foreign module loads at run time on the first call that needs it. Loading this module does not need the native library.error(keynub_error(Status, Code, Operation, Detail), _): Status is an atom such asno_device,not_genuineorauth_required(unknownfor a code this pack does not know), Code the raw status code, Operation the flat API function and Detail the library's diagnostic text, which may be empty. A library that cannot be loaded throwserror(keynub_library_error(Message), _).type(binary)returns it. Input may also be a string or atom whose characters are all below 256.