1 /* $NetBSD: ssh-pkcs11-client.c,v 1.22 2026/04/08 18:58:41 christos Exp $ */ 2 /* $OpenBSD: ssh-pkcs11-client.c,v 1.26 2026/02/09 22:11:39 dtucker Exp $ */ 3 4 /* 5 * Copyright (c) 2010 Markus Friedl. All rights reserved. 6 * Copyright (c) 2014 Pedro Martelletto. All rights reserved. 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 */ 20 #include "includes.h" 21 __RCSID("$NetBSD: ssh-pkcs11-client.c,v 1.22 2026/04/08 18:58:41 christos Exp $"); 22 23 #include <sys/types.h> 24 #include <sys/time.h> 25 #include <sys/socket.h> 26 27 #include <stdarg.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <unistd.h> 31 #include <errno.h> 32 #include <limits.h> 33 34 #include "pathnames.h" 35 #include "xmalloc.h" 36 #include "sshbuf.h" 37 #include "log.h" 38 #include "misc.h" 39 #include "sshkey.h" 40 #include "authfd.h" 41 #include "atomicio.h" 42 #include "ssh-pkcs11.h" 43 #include "ssherr.h" 44 45 /* borrows code from sftp-server and ssh-agent */ 46 47 /* 48 * Maintain a list of ssh-pkcs11-helper subprocesses. These may be looked up 49 * by provider path or their unique keyblobs. 50 */ 51 struct helper { 52 char *path; 53 pid_t pid; 54 int fd; 55 size_t nkeyblobs; 56 struct sshbuf **keyblobs; /* XXX use a tree or something faster */ 57 }; 58 static struct helper **helpers; 59 static size_t nhelpers; 60 61 static struct helper * 62 helper_by_provider(const char *path) 63 { 64 size_t i; 65 66 for (i = 0; i < nhelpers; i++) { 67 if (helpers[i] == NULL || helpers[i]->path == NULL || 68 helpers[i]->fd == -1) 69 continue; 70 if (strcmp(helpers[i]->path, path) == 0) 71 return helpers[i]; 72 } 73 return NULL; 74 } 75 76 static struct helper * 77 helper_by_key(const struct sshkey *key) 78 { 79 size_t i, j; 80 struct sshbuf *keyblob = NULL; 81 int r; 82 83 if ((keyblob = sshbuf_new()) == NULL) 84 fatal_f("sshbuf_new failed"); 85 if ((r = sshkey_putb(key, keyblob)) != 0) 86 fatal_fr(r, "serialise key"); 87 88 for (i = 0; i < nhelpers; i++) { 89 if (helpers[i] == NULL) 90 continue; 91 for (j = 0; j < helpers[i]->nkeyblobs; j++) { 92 if (sshbuf_equals(keyblob, 93 helpers[i]->keyblobs[j]) == 0) { 94 sshbuf_free(keyblob); 95 return helpers[i]; 96 } 97 } 98 } 99 sshbuf_free(keyblob); 100 return NULL; 101 102 } 103 104 static void 105 helper_add_key(struct helper *helper, struct sshkey *key) 106 { 107 int r; 108 109 helper->keyblobs = xrecallocarray(helper->keyblobs, helper->nkeyblobs, 110 helper->nkeyblobs + 1, sizeof(*helper->keyblobs)); 111 if ((helper->keyblobs[helper->nkeyblobs] = sshbuf_new()) == NULL) 112 fatal_f("sshbuf_new failed"); 113 if ((r = sshkey_putb(key, helper->keyblobs[helper->nkeyblobs])) != 0) 114 fatal_fr(r, "shkey_putb failed"); 115 helper->nkeyblobs++; 116 debug3_f("added %s key for provider %s, now has %zu keys", 117 sshkey_type(key), helper->path, helper->nkeyblobs); 118 } 119 120 static void 121 helper_terminate(struct helper *helper) 122 { 123 size_t i; 124 int found = 0; 125 126 if (helper == NULL) 127 return; 128 if (helper->path == NULL) 129 fatal_f("inconsistent helper"); 130 131 debug3_f("terminating helper for %s; remaining %zu keys", 132 helper->path, helper->nkeyblobs); 133 134 close(helper->fd); 135 /* XXX waitpid() */ 136 helper->fd = -1; 137 helper->pid = -1; 138 139 /* repack helpers */ 140 for (i = 0; i < nhelpers; i++) { 141 if (helpers[i] == helper) { 142 if (found) 143 fatal_f("helper recorded more than once"); 144 found = 1; 145 } else if (found) 146 helpers[i - 1] = helpers[i]; 147 } 148 if (found) { 149 helpers = xrecallocarray(helpers, nhelpers, 150 nhelpers - 1, sizeof(*helpers)); 151 nhelpers--; 152 } 153 for (i = 0; i < helper->nkeyblobs; i++) 154 sshbuf_free(helper->keyblobs[i]); 155 free(helper->path); 156 free(helper); 157 } 158 159 static void 160 send_msg(int fd, struct sshbuf *m) 161 { 162 u_char buf[4]; 163 size_t mlen = sshbuf_len(m); 164 int r; 165 166 if (fd == -1) 167 return; 168 POKE_U32(buf, mlen); 169 if (atomicio(vwrite, fd, buf, 4) != 4 || 170 atomicio(vwrite, fd, sshbuf_mutable_ptr(m), 171 sshbuf_len(m)) != sshbuf_len(m)) 172 error("write to helper failed"); 173 if ((r = sshbuf_consume(m, mlen)) != 0) 174 fatal_fr(r, "consume"); 175 } 176 177 static int 178 recv_msg(int fd, struct sshbuf *m) 179 { 180 u_int l, len; 181 u_char c, buf[1024]; 182 int r; 183 184 sshbuf_reset(m); 185 if (fd == -1) 186 return 0; /* XXX */ 187 if ((len = atomicio(read, fd, buf, 4)) != 4) { 188 error("read from helper failed: %u", len); 189 return (0); /* XXX */ 190 } 191 len = PEEK_U32(buf); 192 if (len > 256 * 1024) 193 fatal("response too long: %u", len); 194 /* read len bytes into m */ 195 while (len > 0) { 196 l = len; 197 if (l > sizeof(buf)) 198 l = sizeof(buf); 199 if (atomicio(read, fd, buf, l) != l) { 200 error("response from helper failed."); 201 return (0); /* XXX */ 202 } 203 if ((r = sshbuf_put(m, buf, l)) != 0) 204 fatal_fr(r, "sshbuf_put"); 205 len -= l; 206 } 207 if ((r = sshbuf_get_u8(m, &c)) != 0) 208 fatal_fr(r, "parse type"); 209 return c; 210 } 211 212 int 213 pkcs11_init(int interactive) 214 { 215 return 0; 216 } 217 218 void 219 pkcs11_terminate(void) 220 { 221 size_t i; 222 223 debug3_f("terminating %zu helpers", nhelpers); 224 for (i = 0; i < nhelpers; i++) 225 helper_terminate(helpers[i]); 226 } 227 228 int 229 pkcs11_sign(struct sshkey *key, 230 u_char **sigp, size_t *lenp, 231 const u_char *data, size_t datalen, 232 const char *alg, const char *sk_provider, 233 const char *sk_pin, u_int compat) 234 { 235 struct sshbuf *msg = NULL; 236 struct helper *helper; 237 int status, r; 238 u_char *signature = NULL; 239 size_t signature_len = 0; 240 int ret = SSH_ERR_INTERNAL_ERROR; 241 242 if (sigp != NULL) 243 *sigp = NULL; 244 if (lenp != NULL) 245 *lenp = 0; 246 247 if ((helper = helper_by_key(key)) == NULL || helper->fd == -1) 248 fatal_f("no helper for %s key", sshkey_type(key)); 249 250 if ((msg = sshbuf_new()) == NULL) 251 return SSH_ERR_ALLOC_FAIL; 252 if ((r = sshbuf_put_u8(msg, SSH2_AGENTC_SIGN_REQUEST)) != 0 || 253 (r = sshkey_puts_plain(key, msg)) != 0 || 254 (r = sshbuf_put_string(msg, data, datalen)) != 0 || 255 (r = sshbuf_put_cstring(msg, alg == NULL ? "" : alg)) != 0 || 256 (r = sshbuf_put_u32(msg, compat)) != 0) 257 fatal_fr(r, "compose"); 258 send_msg(helper->fd, msg); 259 sshbuf_reset(msg); 260 261 if ((status = recv_msg(helper->fd, msg)) != SSH2_AGENT_SIGN_RESPONSE) { 262 /* XXX translate status to something useful */ 263 debug_fr(r, "recv_msg"); 264 ret = SSH_ERR_AGENT_FAILURE; 265 goto fail; 266 } 267 268 if ((r = sshbuf_get_string(msg, &signature, &signature_len)) != 0) 269 fatal_fr(r, "parse"); 270 271 /* success */ 272 if (sigp != NULL) { 273 *sigp = signature; 274 signature = NULL; 275 } 276 if (lenp != NULL) 277 *lenp = signature_len; 278 ret = 0; 279 280 fail: 281 free(signature); 282 sshbuf_free(msg); 283 return ret; 284 } 285 286 /* 287 * Make a private PKCS#11-backed certificate by grafting a previously-loaded 288 * PKCS#11 private key and a public certificate key. 289 */ 290 int 291 pkcs11_make_cert(const struct sshkey *priv, 292 const struct sshkey *certpub, struct sshkey **certprivp) 293 { 294 struct helper *helper = NULL; 295 struct sshkey *ret; 296 int r; 297 298 if ((helper = helper_by_key(priv)) == NULL || helper->fd == -1) 299 fatal_f("no helper for %s key", sshkey_type(priv)); 300 301 debug3_f("private key type %s cert type %s on provider %s", 302 sshkey_type(priv), sshkey_type(certpub), helper->path); 303 304 *certprivp = NULL; 305 if (!sshkey_is_cert(certpub) || sshkey_is_cert(priv) || 306 !sshkey_equal_public(priv, certpub)) { 307 error_f("private key %s doesn't match cert %s", 308 sshkey_type(priv), sshkey_type(certpub)); 309 return SSH_ERR_INVALID_ARGUMENT; 310 } 311 *certprivp = NULL; 312 if ((r = sshkey_from_private(priv, &ret)) != 0) 313 fatal_fr(r, "copy key"); 314 315 ret->flags |= SSHKEY_FLAG_EXT; 316 if ((r = sshkey_to_certified(ret)) != 0 || 317 (r = sshkey_cert_copy(certpub, ret)) != 0) 318 fatal_fr(r, "graft certificate"); 319 320 helper_add_key(helper, ret); 321 322 debug3_f("provider %s: %zu remaining keys", 323 helper->path, helper->nkeyblobs); 324 325 /* success */ 326 *certprivp = ret; 327 return 0; 328 } 329 330 static struct helper * 331 pkcs11_start_helper(const char *path) 332 { 333 int pair[2]; 334 const char *prog, *verbosity = NULL; 335 struct helper *helper; 336 pid_t pid; 337 338 if (nhelpers >= INT_MAX) 339 fatal_f("too many helpers"); 340 debug3_f("start helper for %s", path); 341 if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) { 342 error_f("socketpair: %s", strerror(errno)); 343 return NULL; 344 } 345 helper = xcalloc(1, sizeof(*helper)); 346 if ((pid = fork()) == -1) { 347 error_f("fork: %s", strerror(errno)); 348 close(pair[0]); 349 close(pair[1]); 350 free(helper); 351 return NULL; 352 } else if (pid == 0) { 353 if ((dup2(pair[1], STDIN_FILENO) == -1) || 354 (dup2(pair[1], STDOUT_FILENO) == -1)) { 355 fprintf(stderr, "dup2: %s\n", strerror(errno)); 356 _exit(1); 357 } 358 close(pair[0]); 359 close(pair[1]); 360 closefrom(STDERR_FILENO + 1); 361 prog = getenv("SSH_PKCS11_HELPER"); 362 if (prog == NULL || strlen(prog) == 0) 363 prog = _PATH_SSH_PKCS11_HELPER; 364 if (log_level_get() >= SYSLOG_LEVEL_DEBUG1) 365 verbosity = "-vvv"; 366 debug_f("starting %s %s", prog, 367 verbosity == NULL ? "" : verbosity); 368 execlp(prog, prog, verbosity, (char *)NULL); 369 fprintf(stderr, "exec: %s: %s\n", prog, strerror(errno)); 370 _exit(1); 371 } 372 close(pair[1]); 373 helper->fd = pair[0]; 374 helper->path = xstrdup(path); 375 helper->pid = pid; 376 debug3_f("helper %zu for \"%s\" on fd %d pid %ld", nhelpers, 377 helper->path, helper->fd, (long)helper->pid); 378 helpers = xrecallocarray(helpers, nhelpers, 379 nhelpers + 1, sizeof(*helpers)); 380 helpers[nhelpers++] = helper; 381 return helper; 382 } 383 384 int 385 pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp, 386 char ***labelsp) 387 { 388 struct sshkey *k; 389 int r, type; 390 char *label; 391 u_int ret = -1, nkeys, i; 392 struct sshbuf *msg; 393 struct helper *helper; 394 395 if ((helper = helper_by_provider(name)) == NULL && 396 (helper = pkcs11_start_helper(name)) == NULL) 397 return -1; 398 399 debug3_f("add %s", helper->path); 400 401 if ((msg = sshbuf_new()) == NULL) 402 fatal_f("sshbuf_new failed"); 403 if ((r = sshbuf_put_u8(msg, SSH_AGENTC_ADD_SMARTCARD_KEY)) != 0 || 404 (r = sshbuf_put_cstring(msg, name)) != 0 || 405 (r = sshbuf_put_cstring(msg, pin)) != 0) 406 fatal_fr(r, "compose"); 407 send_msg(helper->fd, msg); 408 sshbuf_reset(msg); 409 410 type = recv_msg(helper->fd, msg); 411 debug3_f("response %d", type); 412 if (type == SSH2_AGENT_IDENTITIES_ANSWER) { 413 if ((r = sshbuf_get_u32(msg, &nkeys)) != 0) 414 fatal_fr(r, "parse nkeys"); 415 debug3_f("helper return %u keys", nkeys); 416 *keysp = xcalloc(nkeys, sizeof(struct sshkey *)); 417 if (labelsp) 418 *labelsp = xcalloc(nkeys, sizeof(char *)); 419 for (i = 0; i < nkeys; i++) { 420 /* XXX clean up properly instead of fatal() */ 421 if ((r = sshkey_froms(msg, &k)) != 0 || 422 (r = sshbuf_get_cstring(msg, &label, NULL)) != 0) 423 fatal_fr(r, "parse key"); 424 k->flags |= SSHKEY_FLAG_EXT; 425 helper_add_key(helper, k); 426 (*keysp)[i] = k; 427 if (labelsp) 428 (*labelsp)[i] = label; 429 else 430 free(label); 431 } 432 /* success */ 433 ret = 0; 434 } else if (type == SSH2_AGENT_FAILURE) { 435 if ((r = sshbuf_get_u32(msg, &nkeys)) != 0) 436 error_fr(r, "failed to parse failure response"); 437 } 438 if (ret != 0) { 439 debug_f("no keys; terminate helper"); 440 helper_terminate(helper); 441 } 442 sshbuf_free(msg); 443 return ret == 0 ? (int)nkeys : -1; 444 } 445 446 int 447 pkcs11_del_provider(char *name) 448 { 449 struct helper *helper; 450 451 /* 452 * ssh-agent deletes keys before calling this, so the helper entry 453 * should be gone before we get here. 454 */ 455 debug3_f("delete %s", name ? name : "(null)"); 456 if ((helper = helper_by_provider(name)) != NULL) 457 helper_terminate(helper); 458 return 0; 459 } 460 461 void 462 pkcs11_key_free(struct sshkey *key) 463 { 464 struct helper *helper; 465 struct sshbuf *keyblob = NULL; 466 size_t i; 467 int r, found = 0; 468 469 debug3_f("free %s key", sshkey_type(key)); 470 471 if ((helper = helper_by_key(key)) == NULL || helper->fd == -1) 472 fatal_f("no helper for %s key", sshkey_type(key)); 473 if ((keyblob = sshbuf_new()) == NULL) 474 fatal_f("sshbuf_new failed"); 475 if ((r = sshkey_putb(key, keyblob)) != 0) 476 fatal_fr(r, "serialise key"); 477 478 /* repack keys */ 479 for (i = 0; i < helper->nkeyblobs; i++) { 480 if (sshbuf_equals(keyblob, helper->keyblobs[i]) == 0) { 481 if (found) 482 fatal_f("key recorded more than once"); 483 found = 1; 484 } else if (found) 485 helper->keyblobs[i - 1] = helper->keyblobs[i]; 486 } 487 if (found) { 488 helper->keyblobs = xrecallocarray(helper->keyblobs, 489 helper->nkeyblobs, helper->nkeyblobs - 1, 490 sizeof(*helper->keyblobs)); 491 helper->nkeyblobs--; 492 } 493 if (helper->nkeyblobs == 0) 494 helper_terminate(helper); 495 } 496