File: torture_sk.c

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 (395 lines) | stat: -rw-r--r-- 12,778 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
/*
 * torture_sk.c - torture library for testing security keys
 *
 * This file is part of the SSH Library
 *
 * Copyright (c) 2025 Praneeth Sarode <praneethsarode@gmail.com>
 *
 * The SSH Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * The SSH Library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the SSH Library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 */

#include "torture_sk.h"
#include "libssh/pki.h"
#include "libssh/pki_priv.h"
#include "libssh/sk_api.h" /* For SSH_SK_* flag definitions */

void assert_sk_key_valid(ssh_key key,
                         enum ssh_keytypes_e expected_type,
                         bool private)
{
    char *app_str = NULL;
    const char *expected_type_str = NULL;

    assert_non_null(key);
    assert_true(is_sk_key_type(expected_type));
    assert_int_equal(key->type, expected_type);

    if (private) {
        assert_int_equal(key->flags,
                         SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC);
    } else {
        assert_int_equal(key->flags, SSH_KEY_FLAG_PUBLIC);
    }

    expected_type_str = ssh_key_type_to_char(expected_type);
    assert_non_null(expected_type_str);

    assert_non_null(key->type_c);
    assert_string_equal(key->type_c, expected_type_str);

    /* Validate security key specific fields */
    assert_non_null(key->sk_application);

    /* Validate application string format and content */
    app_str = ssh_string_to_char(key->sk_application);
    assert_non_null(app_str);

    assert_true(ssh_string_len(key->sk_application) >= 4);
    assert_true(strncmp(app_str, "ssh:", 4) == 0);
    ssh_string_free_char(app_str);

    if (private) {
        assert_non_null(key->sk_key_handle);
        assert_true(ssh_string_len(key->sk_key_handle) > 0);
    }

    const uint8_t allowed_flags = SSH_SK_USER_PRESENCE_REQD |
                                  SSH_SK_USER_VERIFICATION_REQD |
                                  SSH_SK_RESIDENT_KEY | SSH_SK_FORCE_OPERATION;

    /* Validate sk_flags contain only allowed bits */
    uint8_t flags = key->sk_flags;
    assert_int_equal(flags & ~allowed_flags, 0);

    /* Validate underlying cryptographic key exists based on type */
    switch (expected_type) {
    case SSH_KEYTYPE_SK_ECDSA:
#if defined(HAVE_LIBGCRYPT)
        assert_non_null(key->ecdsa);
#elif defined(HAVE_LIBMBEDCRYPTO)
        assert_non_null(key->ecdsa);
#elif defined(HAVE_LIBCRYPTO)
        assert_non_null(key->key);
#endif
        break;

    case SSH_KEYTYPE_SK_ED25519:
#if defined(HAVE_LIBCRYPTO)
        assert_non_null(key->key);
#elif !defined(HAVE_LIBCRYPTO)
        assert_non_null(key->ed25519_pubkey);
#endif
        break;

    default:
        /* Should not reach here */
        assert_true(0);
        break;
    }
}

void assert_sk_signature_valid(ssh_signature signature,
                               enum ssh_keytypes_e expected_type,
                               ssh_key signing_key,
                               const uint8_t *data,
                               size_t data_len)
{
    uint8_t valid_flags;
    const char *expected_type_str = NULL;
    ssh_string sig_blob = NULL;
    ssh_signature reconstructed = NULL;
    ssh_buffer sk_sig_buffer = NULL;
    int rc;

    /* Basic null and type validation */
    assert_non_null(signature);
    assert_int_equal(signature->type, expected_type);

    /* Validate hash type is appropriate for security keys */
    switch (expected_type) {
    case SSH_KEYTYPE_SK_ECDSA:
        assert_int_equal(signature->hash_type, SSH_DIGEST_SHA256);
        break;
    case SSH_KEYTYPE_SK_ED25519:
        assert_int_equal(signature->hash_type, SSH_DIGEST_AUTO);
        break;
    default:
        /* Should not reach here */
        assert_true(0);
        break;
    }

    expected_type_str = ssh_key_type_to_char(expected_type);
    assert_non_null(signature->type_c);
    assert_string_equal(signature->type_c, expected_type_str);

    /* Check that only valid SK flags are set */
    valid_flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD;
    assert_int_equal(signature->sk_flags & ~valid_flags, 0);

    assert_true(signature->sk_flags & SSH_SK_USER_PRESENCE_REQD);
    assert_true(signature->sk_counter > 0);

    assert_non_null(signature->raw_sig);
    assert_true(ssh_string_len(signature->raw_sig) > 0);

    rc = ssh_pki_export_signature_blob(signature, &sig_blob);
    assert_int_equal(rc, SSH_OK);
    assert_non_null(sig_blob);

    assert_non_null(signing_key);
    rc = ssh_pki_import_signature_blob(sig_blob, signing_key, &reconstructed);
    assert_int_equal(rc, SSH_OK);
    assert_non_null(reconstructed);

    rc = pki_sk_signature_buffer_prepare(signing_key,
                                         reconstructed,
                                         data,
                                         data_len,
                                         &sk_sig_buffer);
    assert_int_equal(rc, SSH_OK);
    assert_non_null(sk_sig_buffer);

    rc = pki_verify_data_signature(reconstructed,
                                   signing_key,
                                   ssh_buffer_get(sk_sig_buffer),
                                   ssh_buffer_get_len(sk_sig_buffer));
    assert_int_equal(rc, SSH_OK);

    SSH_BUFFER_FREE(sk_sig_buffer);

    ssh_signature_free(reconstructed);
    ssh_string_free(sig_blob);
}

ssh_pki_ctx
torture_create_sk_pki_ctx(const char *application,
                          uint8_t flags,
                          const void *challenge_data,
                          size_t challenge_len,
                          ssh_auth_callback pin_callback,
                          const char *device_path,
                          const char *user_id,
                          const struct ssh_sk_callbacks_struct *sk_callbacks)
{
    ssh_pki_ctx ctx = NULL;
    ssh_buffer challenge_buffer = NULL;
    int rc;

    ctx = ssh_pki_ctx_new();
    assert_non_null(ctx);

    rc = ssh_pki_ctx_options_set(ctx,
                                 SSH_PKI_OPTION_SK_APPLICATION,
                                 application);
    assert_int_equal(rc, SSH_OK);

    rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_SK_FLAGS, &flags);
    assert_int_equal(rc, SSH_OK);

    if (challenge_data != NULL && challenge_len > 0) {
        challenge_buffer = ssh_buffer_new();
        assert_non_null(challenge_buffer);

        rc = ssh_buffer_add_data(challenge_buffer,
                                 challenge_data,
                                 challenge_len);
        assert_int_equal(rc, SSH_OK);
    }

    rc = ssh_pki_ctx_options_set(ctx,
                                 SSH_PKI_OPTION_SK_CHALLENGE,
                                 challenge_buffer);
    assert_int_equal(rc, SSH_OK);

    SSH_BUFFER_FREE(challenge_buffer);

    rc = ssh_pki_ctx_set_sk_pin_callback(ctx, pin_callback, NULL);
    assert_int_equal(rc, SSH_OK);

    if (device_path != NULL) {
        rc = ssh_pki_ctx_sk_callbacks_option_set(ctx,
                                                 SSH_SK_OPTION_NAME_DEVICE_PATH,
                                                 device_path,
                                                 false);
        assert_int_equal(rc, SSH_OK);
    }
    if (user_id != NULL) {
        rc = ssh_pki_ctx_sk_callbacks_option_set(ctx,
                                                 SSH_SK_OPTION_NAME_USER_ID,
                                                 user_id,
                                                 false);
        assert_int_equal(rc, SSH_OK);
    }

    if (sk_callbacks != NULL) {
        rc = ssh_pki_ctx_options_set(ctx,
                                     SSH_PKI_OPTION_SK_CALLBACKS,
                                     sk_callbacks);
        assert_int_equal(rc, SSH_OK);
    }

    return ctx;
}

void assert_sk_enroll_response(struct sk_enroll_response *response, int flags)
{
    assert_non_null(response);

    assert_non_null(response->public_key);
    assert_true(response->public_key_len > 0);

    assert_non_null(response->key_handle);
    assert_true(response->key_handle_len > 0);

    assert_non_null(response->signature);
    assert_true(response->signature_len > 0);

    /*
     * This check might fail for some authenticators, as returning an
     * attestation certificate as part of the attestation statement is not
     * mandated by the FIDO2 standard.
     */
    assert_non_null(response->attestation_cert);
    assert_true(response->attestation_cert_len > 0);

    assert_non_null(response->authdata);
    assert_true(response->authdata_len > 0);

    assert_int_equal(response->flags, flags);
}

void assert_sk_sign_response(struct sk_sign_response *response,
                             enum ssh_keytypes_e key_type)
{
    assert_non_null(response);

    assert_non_null(response->sig_r);
    assert_true(response->sig_r_len > 0);

    /* sig_s is NULL for Ed25519, present for ECDSA */
    switch (key_type) {
    case SSH_SK_ECDSA:
        assert_non_null(response->sig_s);
        assert_true(response->sig_s_len > 0);
        break;
    case SSH_SK_ED25519:
        assert_null(response->sig_s);
        assert_int_equal(response->sig_s_len, 0);
        break;
    default:
        /* Should not reach here */
        assert_true(0);
        break;
    }
}

void assert_sk_resident_key(struct sk_resident_key *resident_key)
{
    assert_non_null(resident_key);

    assert_non_null(resident_key->application);
    assert_true(strlen(resident_key->application) > 0);

    assert_non_null(resident_key->user_id);
    assert_true(resident_key->user_id_len > 0);

    assert_non_null(resident_key->key.public_key);
    assert_true(resident_key->key.public_key_len > 0);

    assert_non_null(resident_key->key.key_handle);
    assert_true(resident_key->key.key_handle_len > 0);
}

const char *torture_get_sk_pin(void)
{
    const char *pin = getenv("TORTURE_SK_PIN");
    return (pin != NULL && pin[0] != '\0') ? pin : NULL;
}

#ifdef HAVE_SK_DUMMY

/* External declarations for sk-dummy library functions
 * These match the signatures in openssh sk-api.h */
extern uint32_t sk_api_version(void);

extern int 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);

extern int sk_sign(uint32_t alg,
                   const uint8_t *data,
                   size_t data_len,
                   const char *application,
                   const uint8_t *key_handle,
                   size_t key_handle_len,
                   uint8_t flags,
                   const char *pin,
                   struct sk_option **options,
                   struct sk_sign_response **sign_response);

extern int sk_load_resident_keys(const char *pin,
                                 struct sk_option **options,
                                 struct sk_resident_key ***resident_keys,
                                 size_t *num_keys_found);

static struct ssh_sk_callbacks_struct sk_dummy_callbacks = {
    .api_version = sk_api_version,
    .enroll = sk_enroll,
    .sign = sk_sign,
    .load_resident_keys = sk_load_resident_keys,
};

#endif /* HAVE_SK_DUMMY */

#ifdef WITH_FIDO2

const struct ssh_sk_callbacks_struct *torture_get_sk_dummy_callbacks(void)
{
#ifdef HAVE_SK_DUMMY
    ssh_callbacks_init(&sk_dummy_callbacks);
    return &sk_dummy_callbacks;
#else
    return NULL;
#endif /* HAVE_SK_DUMMY */
}

const struct ssh_sk_callbacks_struct *torture_get_sk_callbacks(void)
{
    const char *env = getenv("TORTURE_SK_USBHID");
    bool torture_sk_usbhid = (env != NULL && env[0] != '\0');

    if (torture_sk_usbhid) {
        return ssh_sk_get_default_callbacks();
    } else {
        return torture_get_sk_dummy_callbacks();
    }
}

#endif /* WITH_FIDO2 */

bool torture_sk_is_using_sk_dummy(void)
{
    const char *env = getenv("TORTURE_SK_USBHID");
    /* Return true if using sk-dummy callbacks (when TORTURE_SK_USBHID is NOT
     * set) */
    return (env == NULL || env[0] == '\0');
}