File: fido2.dox

package info (click to toggle)
libssh 0.12.0-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 7,804 kB
  • sloc: ansic: 124,224; cpp: 421; xml: 226; sh: 206; makefile: 26; python: 9
file content (601 lines) | stat: -rw-r--r-- 23,887 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
/**

@page libssh_tutor_fido2 Chapter 11: FIDO2/U2F Keys Support

@section fido2_intro Introduction

The traditional SSH public key model stores the private key on disk
and anyone who obtains that file (and possibly its passphrase) can impersonate
the user. FIDO2 authenticators, such as USB security keys, are hardware tokens
that generate or securely store private key material within a secure element
and may require explicit user interaction such as a touch, PIN, or biometric
verification for use. Hence, security keys are far safer from theft or
exfiltration than traditional file-based SSH keys. libssh provides support
for FIDO2/U2F security keys as hardware-backed SSH authentication credentials.

This chapter explains the concepts, build prerequisites, the API, and
usage patterns for enrolling (creating) and using security key-backed SSH
keys, including resident (discoverable) credentials.

@subsection fido2_resident_keys Resident Keys

Two credential storage modes exist for security keys:

 - Non-resident (default): A credential ID (key handle) and metadata are
	 stored on the client-side in a key file. This key handle must be
	 presented to the FIDO2/U2F device while signing. This is somewhat
	 similar to traditional SSH keys, except that the key handle is not the
	 private key itself, but used in combination with the device's master key
	 to derive the actual private key.

 - Resident (discoverable): The credential (and metadata like user id) is
	 stored on the device. No local file is needed; the device can enumerate or
	 locate the credential internally when queried.

Advantages of resident keys include portability (using the same device
across hosts) and resilience (no loss if the local machine is destroyed).
Although, they may be limited by the storage of the authenticator.

@subsection fido2_presence_verification User Presence vs. User Verification

FIDO2 distinguishes between:

 - User Presence (UP): A simple physical interaction (touch) to confirm a
	 human is present.

 - User Verification (UV): Verification of the user’s identity through
     biometric authentication or a PIN.

Requiring UV provides additional protection if the device is stolen
and used without the PIN/biometric.

libssh exposes flags controlling these requirements (see below).

@subsection fido2_callbacks The Callback Abstraction

Different environments may need to access security keys through different
transport layers (e.g., USB-HID, NFC, Bluetooth, etc.). To accommodate
this variability, libssh does not hard-code a single implementation.

Instead, it defines a small callback interface (`ssh_sk_callbacks`) used for all
security key operations. Any implementation of this callback interface can be used
by higher-level PKI functions to perform enroll/sign/load_resident_keys
operations without needing to know the transport specifics. Hence, users can
define their own implementations for these callbacks to support different
transport protocols or custom hardware. Refer @ref fido2_custom_callbacks
for additional details.

The callback interface is defined in `libssh/callbacks.h` and the behaviour
and return values are specified by `libssh/sk_api.h`, which is the same
interface defined by OpenSSH for its security key support. This means that
any callback implementations (also called "middleware" in OpenSSH terminology)
developed for OpenSSH can be adapted to libssh with minimal changes.

The following operations are abstracted by the callback interface:

 - api_version(): Report the version of the SK API that the callback implementation
   is based on, so that libssh can check whether this implementation would be
   compatible with the SK API version that it supports.
   Refer @ref fido2_custom_callbacks_version for additional details.
 - enroll(): Create (enroll) a new credential, returning public key, key
	 handle, attestation data.
 - sign(): Produce a signature for supplied inputs using an existing key
	 handle.
 - load_resident_keys(): Enumerate resident (discoverable) credentials stored
	 on the authenticator.

libssh provides a default implementation of the `ssh_sk_callbacks` using
the libfido2 library for the USB-HID transport protocol. Hence, by default,
libssh can interact with any FIDO2/U2F device that supports USB-HID and is
compatible with libfido2, without requiring any additional modifications.

@subsection fido2_build Building with FIDO2 Support

To enable FIDO2/U2F support, libssh must be built with the WITH_FIDO2
build option as follows:

@verbatim
	cmake -DWITH_FIDO2=ON <other options> ..
@endverbatim

libssh will also build the default USB-HID `ssh_sk_callbacks`, if the
libfido2 library and headers are installed on your system.

@warning If built without libfido2, support for interacting with FIDO2/U2F
devices over USB-HID will not be available.

@subsection fido2_api_overview API Overview

Security key operations are configured through the `ssh_pki_ctx`
which allows to specify both general PKI options and FIDO2-specific
options such as the sk_callbacks, challenge data, application string, flags, etc.

The following sections describe the options that can be configured and how
the `ssh_pki_ctx` is used in conjunction with `ssh_key` to perform
enrollment, signing, and resident key loading operations.

@subsection fido2_key_objects Security Key Objects & Metadata

Security keys are surfaced as `ssh_key` objects of type
`SSH_KEYTYPE_SK_ECDSA` and `SSH_KEYTYPE_SK_ED25519` (corresponding to the
OpenSSH public key algorithm names `sk-ecdsa-sha2-nistp256@openssh.com` and
`sk-ssh-ed25519@openssh.com`). In addition to standard key handling, libssh
exposes the following helper functions to retrieve embedded SK metadata:

 - ssh_key_get_sk_application(): Returns the relying party / application
	 (RP ID) string. The Relying Party ID (RP ID) is a string
	 that identifies the application or service requesting key enrollment. It
	 ensures that a credential is bound to a specific origin, preventing
	 phishing across sites. During registration, the authenticator associates
	 the credential with this RP ID so that it can later only be used for
	 authentication requests from the same relying party. For SSH keys, the
	 common format is "ssh:user@host".

 - ssh_key_get_sk_user_id(): Returns a copy of the user ID associated with a key
	 which represents a unique identifier for the user within the relying
	 party (application) context. It is typically a string (such as an
	 email, or a random identifier) that helps distinguish credentials
	 belonging to different users for the same application.

   Though the user ID can be binary data according to the FIDO2 spec, libssh only
   supports NUL-terminated strings for enrolling new keys in order to remain compatible
   with the OpenSSH's sk-api interface.

   However, libssh does support loading existing resident keys with user IDs containing
   arbitrary binary data. It does so by using an `ssh_string` to store the loaded key's
   user_id, and an `ssh_string` can contain arbitrary binary data that can not be stored
   in a traditional NUL-terminated string (like null bytes).

   @note The user_id is NOT stored in the key file for non-resident keys. It is only
   available for resident (discoverable) keys loaded from the authenticator via
   ssh_sk_resident_keys_load(). For keys imported from files, this function returns
   NULL.

 - ssh_key_get_sk_flags(): Returns the flags associated with the key. The
	 following are the supported flags and they can be combined using
	 bitwise OR:
     - SSH_SK_USER_PRESENCE_REQD : Require user presence (touch).
     - SSH_SK_USER_VERIFICATION_REQD : Require user verification
	   (PIN/biometric).
     - SSH_SK_RESIDENT_KEY : Request a resident discoverable credential.
     - SSH_SK_FORCE_OPERATION : Force resident (discoverable) credential
	   creation even if one with same application and user_id already
	   exists.

These functions perform no additional communication with the
authenticator, this metadata is captured during enrollment/loading and
cached in the `ssh_key`.

@subsection fido2_options Setting Security Key Context Options

Options are set via ssh_pki_ctx_options_set().

Representative security key options:
 - SSH_PKI_OPTION_SK_APPLICATION (const char *): Required relying party ID
   If not set, a default value of "ssh:" is used.
 - SSH_PKI_OPTION_SK_FLAGS (uint8_t *): Flags described above. If not set,
   defaults to SSH_SK_USER_PRESENCE_REQD. This is because OpenSSH `sshd`
   requires user presence for security key authentication by default.
 - SSH_PKI_OPTION_SK_USER_ID (const char *): Represents a unique identifier
   for the user within the relying party (application) context.
   It is typically a string (such as an email, or a random identifier) that
   helps distinguish credentials belonging to different users for the same
   application. If not set, defaults to 64 zeros.
 - SSH_PKI_OPTION_SK_CHALLENGE (ssh_buffer): Custom challenge; if omitted a
   random 32-byte challenge is generated.
 - SSH_PKI_OPTION_SK_CALLBACKS (ssh_sk_callbacks): Replace the default
   callbacks with custom callbacks.

PIN callback: Use ssh_pki_ctx_set_sk_pin_callback() to register a function
matching `ssh_auth_callback` to prompt for and supply a PIN. The callback may
be called multiple times to ask for the pin depending on the authenticator policy.

Callback options: Callback implementations may accept additional configuration
name/value options such as the path to the fido device. These options can be provided via
`ssh_pki_ctx_sk_callbacks_option_set()`. Refer @ref fido2_custom_callbacks_options
for additional details.

The built-in callback implementation provided by libssh supports additional options,
with their names defined in `libssh.h` prefixed with `SSH_SK_OPTION_NAME_*`, such as:

SSH_SK_OPTION_NAME_DEVICE_PATH: Used for specifying a device path.
If the device path is not specified and multiple devices are connected, then
depending upon the operation and the flags set, the callback implementation may
automatically select a suitable device, or the user may be prompted to touch the
device they want to use.

SSH_SK_OPTION_NAME_USER_ID: Used for setting the user ID.
Note that the user ID can also be set using the ssh_pki_ctx_options_set() API.

@subsection fido2_enrollment Enrollment Example

An enrollment operation creates a new credential on the authenticator and
returns an ssh_key object representing it. The application and user_id
fields are required for creating the credential. The other options are
optional. A successful enrollment returns the public key, key handle, and
metadata which are stored in the ssh_key object, and may optionally return
attestation data which is used for verifying the authenticator model and
firmware version.

Below is a simple example enrolling an Ed25519 security key (non-resident)
requiring user presence only:

@code
#include <libssh/libssh.h>
#include <string.h>

static int pin_cb(const char *prompt,
                  char *buf,
                  size_t len,
                  int echo,
                  int verify,
                  void *userdata)
{
    (void)prompt;
    (void)echo;
    (void)verify;
    (void)userdata;

    /* In a real application, the user would be prompted to enter the PIN */
    const char *pin = "4242";
    size_t l = strlen(pin);
    if (l + 1 > len) {
        return SSH_ERROR;
    }

    memcpy(buf, pin, l + 1);
    return SSH_OK;
}

int enroll_sk_key()
{
    const char *app = "ssh:user@host";
    const char *user_id = "alice";
    uint8_t flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD;
    const char *device_path = "/dev/hidraw6"; /* Optional device path */

    ssh_pki_ctx pki_ctx = ssh_pki_ctx_new();
    ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_APPLICATION, app);
    ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_USER_ID, user_id);
    ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_FLAGS, &flags);

    ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL);

    ssh_pki_ctx_sk_callbacks_option_set(pki_ctx,
                                        SSH_SK_OPTION_NAME_DEVICE_PATH,
                                        device_path,
                                        true);

    ssh_key enrolled = NULL;
    int rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ED25519,
                                  pki_ctx,
                                  &enrolled); /* produces sk-ed25519 key */

    /* Save enrolled key using ssh_pki_export_privkey_file, retrieve attestation
     * buffer etc. */

    /* Free context and key when done */
}
@endcode

After a successful enrollment, you can retrieve the attestation buffer
(if provided by the authenticator) from the PKI context:

@code
ssh_buffer att_buf = NULL;
rc = ssh_pki_ctx_get_sk_attestation_buffer(pki_ctx, &att_buf);
if (rc == SSH_OK && att_buf != NULL) {
    /* att_buf now contains the serialized attestation
     * ("ssh-sk-attest-v01"). You can inspect, save, or
     * parse the buffer as needed
     */
    ssh_buffer_free(att_buf);
}
@endcode

Notes:
- The attestation buffer is only populated if the enrollment operation
  succeeds and the authenticator provides attestation data.
- `ssh_pki_ctx_get_sk_attestation_buffer()` returns a copy of the attestation
  buffer; the caller must free it with `ssh_buffer_free()`.

@subsection fido2_signing Authenticating with a Stored Security Key Public Key

To authenticate using a security key, the application typically loads the
previously enrolled sk-* private key, establishes an SSH connection, and
calls `ssh_userauth_publickey()`. libssh automatically recognizes security
key types and transparently handles the required hardware-backed
authentication steps such as prompting for a touch or PIN using the
configured security key callbacks.

Example:
@code
#include <libssh/libssh.h>
#include <stdio.h>

int auth_with_sk_file(const char *host,
                      const char *user,
                      const char *privkey_path)
{
    ssh_session session = NULL;
    ssh_key privkey = NULL;
    int rc = SSH_ERROR;

    session = ssh_new();
    ssh_options_set(session, SSH_OPTIONS_HOST, host);
    ssh_options_set(session, SSH_OPTIONS_USER, user);
    ssh_connect(session);

    ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, &privkey);

    ssh_pki_ctx pki_ctx = ssh_pki_ctx_new();
    /* Optionally set PIN callback, device path, etc. */
    /* ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); */

    ssh_options_set(session, SSH_OPTIONS_PKI_CONTEXT, pki_ctx);

    rc = ssh_userauth_publickey(session, user, privkey);
    if (rc == SSH_AUTH_SUCCESS) {
        printf("Authenticated with security key.\n");
        rc = SSH_OK;
    } else {
        fprintf(stderr,
                "Authentication failed rc=%d err=%s\n",
                rc,
                ssh_get_error(session));
        rc = SSH_ERROR;
    }

    /* Free resources */
}
@endcode

@subsection fido2_resident Resident Key Enumeration

Resident keys stored on the device can be discovered and loaded with
ssh_sk_resident_keys_load() which takes a PKI context (configured with
a PIN callback) and returns each key as an ssh_key and the number of keys loaded.

Example:

@code
#include <libssh/libssh.h>
#include <stdio.h>
#include <string.h>

static int pin_cb(const char *prompt,
                  char *buf,
                  size_t len,
                  int echo,
                  int verify,
                  void *userdata)
{
    (void)prompt;
    (void)echo;
    (void)verify;
    (void)userdata;
    const char *pin = "4242";
    size_t l = strlen(pin);

    if (l + 1 > len) {
        return SSH_ERROR;
    }

    memcpy(buf, pin, l + 1);
    return SSH_OK;
}

int auth_with_resident(const char *host,
                       const char *user,
                       const char *application,
                       const char *user_id)
{
    ssh_pki_ctx pki_ctx = NULL;
    size_t num_found = 0;
    ssh_key *keys = NULL;
    ssh_key final_key = NULL;
    int rc = SSH_ERROR;

    ssh_string cur_application = NULL;
    ssh_string cur_user_id = NULL;
    ssh_string expected_application = NULL;
    ssh_string expected_user_id = NULL;

    pki_ctx = ssh_pki_ctx_new();
    ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL);

    expected_application = ssh_string_from_char(application);
    expected_user_id = ssh_string_from_char(user_id);

    rc = ssh_sk_resident_keys_load(pki_ctx, &keys, &num_found);
    for (size_t i = 0; i < num_found; i++) {
        cur_application = ssh_key_get_sk_application(keys[i]);
        cur_user_id = ssh_key_get_sk_user_id(keys[i]);

        if (ssh_string_cmp(cur_application, expected_application) == 0 &&
            ssh_string_cmp(cur_user_id, expected_user_id) == 0) {
            SSH_STRING_FREE(cur_application);
            SSH_STRING_FREE(cur_user_id);
            final_key = keys[i];
            break;
        }

        SSH_STRING_FREE(cur_application);
        SSH_STRING_FREE(cur_user_id);
    }

    SSH_STRING_FREE(expected_application);
    SSH_STRING_FREE(expected_user_id);

    /* Continue with authentication using the ssh_key with
     * ssh_userauth_publickey as usual, and free resources when done. */
}
@endcode

@subsection fido2_sshsig Signing using the sshsig API

Security keys can also be used for general-purpose signing of arbitrary data
(without SSH authentication) using the existing `sshsig_sign()` and `sshsig_verify()`
functions. These functions work seamlessly with security key types
(`SSH_KEYTYPE_SK_ECDSA` and `SSH_KEYTYPE_SK_ED25519`) and will automatically
invoke the configured security key callbacks to perform hardware-backed signing
operations.

@subsection fido2_custom_callbacks Implementing Custom Callback Implementations

Users may need to implement custom callback implementations to support
different transport protocols (e.g., NFC, Bluetooth) beyond the default USB-HID
support. This section describes how to implement and integrate custom callback
implementations.

To implement custom callbacks, you must include the following headers:

@code
#include <libssh/callbacks.h>  /* For ssh_sk_callbacks_struct */
#include <libssh/sk_api.h>     /* For SK API constants and data structures */
@endcode

The `libssh/sk_api.h` header provides the complete interface specification including
request/response structures, flags, and version macros.

@subsubsection fido2_custom_callbacks_version API Version Compatibility

libssh validates callback implementations by checking the API version returned by
the `api_version()` callback. To ensure compatibility, libssh compares the major
version (upper 16 bits) of the returned value with `LIBSSH_SK_API_VERSION_MAJOR`.
If they don't match, libssh will reject the callback implementation.
This ensures that the callbacks' SK API matches the major version expected by libssh,
while allowing minor version differences.

@subsubsection fido2_custom_callbacks_implementation Implementation Example

Here's a minimal example of defining and using custom callbacks:

@code
#include <libssh/libssh.h>
#include <libssh/callbacks.h>
#include <libssh/sk_api.h>

/* Your custom API version callback */
static uint32_t my_sk_api_version(void)
{
    /* Match the major version, set your own minor version */
    return SSH_SK_VERSION_MAJOR | 0x0001;
}

/* Your custom enroll callback */
static int my_sk_enroll(uint32_t alg,
                        const uint8_t *challenge,
                        size_t challenge_len,
                        const char *application,
                        uint8_t flags,
                        const char *pin,
                        struct sk_option **options,
                        struct sk_enroll_response **enroll_response)
{
    /* Parse options array to extract custom parameters */
    if (options != NULL) {
        for (size_t i = 0; options[i] != NULL; i++) {
            if (strcmp(options[i]->name, "my_custom_option") == 0) {
                /* Use options[i]->value */
            }
        }
    }

    /* Implement your enroll logic here */
    /* ... */

    return SSH_SK_ERR_GENERAL; /* Return appropriate error code */
}

/* Implement other required callbacks: sign, load_resident_keys */
/* ... */

/* Define your callback structure */
static struct ssh_sk_callbacks_struct my_sk_callbacks = {
    .size = sizeof(struct ssh_sk_callbacks_struct),
    .api_version = my_sk_api_version,
    .enroll = my_sk_enroll,
    .sign = my_sk_sign,              /* Your implementation */
    .load_resident_keys = my_sk_load_resident_keys,  /* Your implementation */
};

/* Usage example */
void use_custom_callbacks(void)
{
    ssh_pki_ctx pki_ctx = ssh_pki_ctx_new();

    /* Set your custom callbacks */
    ssh_pki_ctx_options_set(pki_ctx,
                            SSH_PKI_OPTION_SK_CALLBACKS,
                            &my_sk_callbacks);

    /* Pass custom options to your callbacks */
    ssh_pki_ctx_sk_callbacks_option_set(pki_ctx,
                                        "my_custom_option",
                                        "my_custom_value",
                                        false);

    /* Use the context for enrollment, signing, etc. */
}
@endcode

@subsubsection fido2_custom_callbacks_options Passing Custom Options

The `ssh_pki_ctx_sk_callbacks_option_set()` function allows you to pass
implementation-specific options as name/value string pairs:

@code
ssh_pki_ctx_sk_callbacks_option_set(pki_ctx,
                                    "option_name",
                                    "option_value",
                                    required);
@endcode

Parameters:
- `option_name`: The name of the option (e.g., "device_path", "my_custom_param")
- `option_value`: The string value for this option
- `required`: If true, this option must be processed by the callback implementation
  and cannot be ignored. If false, the option is advisory and can be skipped if the
  callback implementation does not support it.

These options are passed to your callbacks in the `struct sk_option **options`
parameter as a NULL-terminated array. Each `sk_option` has the following fields:
- `name`: The option name (char *)
- `value`: The option value (char *)
- `required`: Whether the option must be processed (uint8_t, non-zero = required)

@subsubsection fido2_custom_callbacks_openssh OpenSSH Middleware Compatibility

Since libssh uses the same SK API as OpenSSH, middleware implementations developed
for OpenSSH can be adapted with minimal changes.
To adapt an OpenSSH middleware for libssh, create a wrapper that populates
`ssh_sk_callbacks_struct` with pointers to the middleware's functions.

@subsection fido2_testing Testing and Environment Variables

Unit tests covering USB-HID enroll/sign/load_resident_keys operations can be found
in the `tests/unittests/torture_sk_usbhid.c` file. To run these tests you
must have libfido2 installed and the WITH_FIDO2=ON build option set.
Additionally, you must ensure the following:

 - An actual FIDO2 device must be connected to the test machine.
 - The TORTURE_SK_USBHID environment variable must be set.
 - The environment variable TORTURE_SK_PIN=<device PIN> must be set.

If these are not set, the tests are skipped.

The higher level PKI integration tests can be found in
`tests/unittests/torture_pki_sk.c` and the tests related to the sshsig API
can be found in `tests/unittests/torture_pki_sshsig.c`.
These use the callback implementation provided by OpenSSH's sk-dummy.so,
which simulates an authenticator without requiring any hardware. Hence, these tests
can be run in the CI environment.
However, these tests can also be configured to use the default USB-HID callbacks
by setting the same environment variables as described above.

The following devices were tested during development:

- Yubico Security Key NFC - USB-A

*/