1*9469f4f1Schristos /* $NetBSD: ssh-pkcs11-client.c,v 1.20 2024/09/24 21:32:19 christos Exp $ */ 2*9469f4f1Schristos /* $OpenBSD: ssh-pkcs11-client.c,v 1.20 2024/08/15 00:51:51 djm Exp $ */ 317418e98Schristos 4264ec8a8Sadam /* 5264ec8a8Sadam * Copyright (c) 2010 Markus Friedl. All rights reserved. 6aa36fcacSchristos * Copyright (c) 2014 Pedro Martelletto. All rights reserved. 7264ec8a8Sadam * 8264ec8a8Sadam * Permission to use, copy, modify, and distribute this software for any 9264ec8a8Sadam * purpose with or without fee is hereby granted, provided that the above 10264ec8a8Sadam * copyright notice and this permission notice appear in all copies. 11264ec8a8Sadam * 12264ec8a8Sadam * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13264ec8a8Sadam * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14264ec8a8Sadam * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15264ec8a8Sadam * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16264ec8a8Sadam * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17264ec8a8Sadam * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18264ec8a8Sadam * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19264ec8a8Sadam */ 20aef795aaSadam #include "includes.h" 21*9469f4f1Schristos __RCSID("$NetBSD: ssh-pkcs11-client.c,v 1.20 2024/09/24 21:32:19 christos Exp $"); 22264ec8a8Sadam 23264ec8a8Sadam #include <sys/types.h> 24264ec8a8Sadam #include <sys/time.h> 25264ec8a8Sadam #include <sys/socket.h> 26264ec8a8Sadam 27264ec8a8Sadam #include <stdarg.h> 28264ec8a8Sadam #include <string.h> 29264ec8a8Sadam #include <unistd.h> 30264ec8a8Sadam #include <errno.h> 31a629fefcSchristos #include <limits.h> 32264ec8a8Sadam 33aa36fcacSchristos #include <openssl/ecdsa.h> 348a4530f9Schristos #include <openssl/rsa.h> 358a4530f9Schristos 36264ec8a8Sadam #include "pathnames.h" 37264ec8a8Sadam #include "xmalloc.h" 3855a4608bSchristos #include "sshbuf.h" 39264ec8a8Sadam #include "log.h" 40264ec8a8Sadam #include "misc.h" 4155a4608bSchristos #include "sshkey.h" 42264ec8a8Sadam #include "authfd.h" 43264ec8a8Sadam #include "atomicio.h" 44264ec8a8Sadam #include "ssh-pkcs11.h" 4555a4608bSchristos #include "ssherr.h" 46264ec8a8Sadam 47264ec8a8Sadam /* borrows code from sftp-server and ssh-agent */ 48264ec8a8Sadam 49a629fefcSchristos /* 50a629fefcSchristos * Maintain a list of ssh-pkcs11-helper subprocesses. These may be looked up 51a629fefcSchristos * by provider path or their unique EC/RSA METHOD pointers. 52a629fefcSchristos */ 53a629fefcSchristos struct helper { 54a629fefcSchristos char *path; 55a629fefcSchristos pid_t pid; 56a629fefcSchristos int fd; 57a629fefcSchristos RSA_METHOD *rsa_meth; 58a629fefcSchristos EC_KEY_METHOD *ec_meth; 59a629fefcSchristos int (*rsa_finish)(RSA *rsa); 60a629fefcSchristos void (*ec_finish)(EC_KEY *key); 61a629fefcSchristos size_t nrsa, nec; /* number of active keys of each type */ 62a629fefcSchristos }; 63a629fefcSchristos static struct helper **helpers; 64a629fefcSchristos static size_t nhelpers; 65a629fefcSchristos 66a629fefcSchristos static struct helper * 67a629fefcSchristos helper_by_provider(const char *path) 68a629fefcSchristos { 69a629fefcSchristos size_t i; 70a629fefcSchristos 71a629fefcSchristos for (i = 0; i < nhelpers; i++) { 72a629fefcSchristos if (helpers[i] == NULL || helpers[i]->path == NULL || 73a629fefcSchristos helpers[i]->fd == -1) 74a629fefcSchristos continue; 75a629fefcSchristos if (strcmp(helpers[i]->path, path) == 0) 76a629fefcSchristos return helpers[i]; 77a629fefcSchristos } 78a629fefcSchristos return NULL; 79a629fefcSchristos } 80a629fefcSchristos 81a629fefcSchristos static struct helper * 82a629fefcSchristos helper_by_rsa(const RSA *rsa) 83a629fefcSchristos { 84a629fefcSchristos size_t i; 85a629fefcSchristos const RSA_METHOD *meth; 86a629fefcSchristos 87a629fefcSchristos if ((meth = RSA_get_method(rsa)) == NULL) 88a629fefcSchristos return NULL; 89a629fefcSchristos for (i = 0; i < nhelpers; i++) { 90a629fefcSchristos if (helpers[i] != NULL && helpers[i]->rsa_meth == meth) 91a629fefcSchristos return helpers[i]; 92a629fefcSchristos } 93a629fefcSchristos return NULL; 94a629fefcSchristos 95a629fefcSchristos } 96a629fefcSchristos 97a629fefcSchristos static struct helper * 98a629fefcSchristos helper_by_ec(const EC_KEY *ec) 99a629fefcSchristos { 100a629fefcSchristos size_t i; 101a629fefcSchristos const EC_KEY_METHOD *meth; 102a629fefcSchristos 103a629fefcSchristos if ((meth = EC_KEY_get_method(ec)) == NULL) 104a629fefcSchristos return NULL; 105a629fefcSchristos for (i = 0; i < nhelpers; i++) { 106a629fefcSchristos if (helpers[i] != NULL && helpers[i]->ec_meth == meth) 107a629fefcSchristos return helpers[i]; 108a629fefcSchristos } 109a629fefcSchristos return NULL; 110a629fefcSchristos 111a629fefcSchristos } 112264ec8a8Sadam 113264ec8a8Sadam static void 114a629fefcSchristos helper_free(struct helper *helper) 115a629fefcSchristos { 116a629fefcSchristos size_t i; 117a629fefcSchristos int found = 0; 118a629fefcSchristos 119a629fefcSchristos if (helper == NULL) 120a629fefcSchristos return; 121a629fefcSchristos if (helper->path == NULL || helper->ec_meth == NULL || 122a629fefcSchristos helper->rsa_meth == NULL) 123a629fefcSchristos fatal_f("inconsistent helper"); 124a629fefcSchristos debug3_f("free helper for provider %s", helper->path); 125a629fefcSchristos for (i = 0; i < nhelpers; i++) { 126a629fefcSchristos if (helpers[i] == helper) { 127a629fefcSchristos if (found) 128a629fefcSchristos fatal_f("helper recorded more than once"); 129a629fefcSchristos found = 1; 130a629fefcSchristos } 131a629fefcSchristos else if (found) 132a629fefcSchristos helpers[i - 1] = helpers[i]; 133a629fefcSchristos } 134a629fefcSchristos if (found) { 135a629fefcSchristos helpers = xrecallocarray(helpers, nhelpers, 136a629fefcSchristos nhelpers - 1, sizeof(*helpers)); 137a629fefcSchristos nhelpers--; 138a629fefcSchristos } 139a629fefcSchristos free(helper->path); 140a629fefcSchristos EC_KEY_METHOD_free(helper->ec_meth); 141a629fefcSchristos RSA_meth_free(helper->rsa_meth); 142a629fefcSchristos free(helper); 143a629fefcSchristos } 144a629fefcSchristos 145a629fefcSchristos static void 146a629fefcSchristos helper_terminate(struct helper *helper) 147a629fefcSchristos { 148a629fefcSchristos if (helper == NULL) { 149a629fefcSchristos return; 150a629fefcSchristos } else if (helper->fd == -1) { 151a629fefcSchristos debug3_f("already terminated"); 152a629fefcSchristos } else { 153a629fefcSchristos debug3_f("terminating helper for %s; " 154a629fefcSchristos "remaining %zu RSA %zu ECDSA", 155a629fefcSchristos helper->path, helper->nrsa, helper->nec); 156a629fefcSchristos close(helper->fd); 157a629fefcSchristos /* XXX waitpid() */ 158a629fefcSchristos helper->fd = -1; 159a629fefcSchristos helper->pid = -1; 160a629fefcSchristos } 161a629fefcSchristos /* 162a629fefcSchristos * Don't delete the helper entry until there are no remaining keys 163a629fefcSchristos * that reference it. Otherwise, any signing operation would call 164a629fefcSchristos * a free'd METHOD pointer and that would be bad. 165a629fefcSchristos */ 166a629fefcSchristos if (helper->nrsa == 0 && helper->nec == 0) 167a629fefcSchristos helper_free(helper); 168a629fefcSchristos } 169a629fefcSchristos 170a629fefcSchristos static void 171a629fefcSchristos send_msg(int fd, struct sshbuf *m) 172264ec8a8Sadam { 173264ec8a8Sadam u_char buf[4]; 17455a4608bSchristos size_t mlen = sshbuf_len(m); 17555a4608bSchristos int r; 176264ec8a8Sadam 177a629fefcSchristos if (fd == -1) 178a629fefcSchristos return; 17955a4608bSchristos POKE_U32(buf, mlen); 180264ec8a8Sadam if (atomicio(vwrite, fd, buf, 4) != 4 || 18155a4608bSchristos atomicio(vwrite, fd, sshbuf_mutable_ptr(m), 18255a4608bSchristos sshbuf_len(m)) != sshbuf_len(m)) 183264ec8a8Sadam error("write to helper failed"); 18455a4608bSchristos if ((r = sshbuf_consume(m, mlen)) != 0) 18517418e98Schristos fatal_fr(r, "consume"); 186264ec8a8Sadam } 187264ec8a8Sadam 188264ec8a8Sadam static int 189a629fefcSchristos recv_msg(int fd, struct sshbuf *m) 190264ec8a8Sadam { 191264ec8a8Sadam u_int l, len; 19255a4608bSchristos u_char c, buf[1024]; 19355a4608bSchristos int r; 194264ec8a8Sadam 195a629fefcSchristos sshbuf_reset(m); 196a629fefcSchristos if (fd == -1) 197a629fefcSchristos return 0; /* XXX */ 198264ec8a8Sadam if ((len = atomicio(read, fd, buf, 4)) != 4) { 199264ec8a8Sadam error("read from helper failed: %u", len); 200264ec8a8Sadam return (0); /* XXX */ 201264ec8a8Sadam } 20255a4608bSchristos len = PEEK_U32(buf); 203264ec8a8Sadam if (len > 256 * 1024) 204264ec8a8Sadam fatal("response too long: %u", len); 205264ec8a8Sadam /* read len bytes into m */ 206264ec8a8Sadam while (len > 0) { 207264ec8a8Sadam l = len; 208264ec8a8Sadam if (l > sizeof(buf)) 209264ec8a8Sadam l = sizeof(buf); 210264ec8a8Sadam if (atomicio(read, fd, buf, l) != l) { 211264ec8a8Sadam error("response from helper failed."); 212264ec8a8Sadam return (0); /* XXX */ 213264ec8a8Sadam } 21455a4608bSchristos if ((r = sshbuf_put(m, buf, l)) != 0) 21517418e98Schristos fatal_fr(r, "sshbuf_put"); 216264ec8a8Sadam len -= l; 217264ec8a8Sadam } 21855a4608bSchristos if ((r = sshbuf_get_u8(m, &c)) != 0) 21917418e98Schristos fatal_fr(r, "parse type"); 22055a4608bSchristos return c; 221264ec8a8Sadam } 222264ec8a8Sadam 223264ec8a8Sadam int 224264ec8a8Sadam pkcs11_init(int interactive) 225264ec8a8Sadam { 226a629fefcSchristos return 0; 227264ec8a8Sadam } 228264ec8a8Sadam 229264ec8a8Sadam void 230264ec8a8Sadam pkcs11_terminate(void) 231264ec8a8Sadam { 232a629fefcSchristos size_t i; 233a629fefcSchristos 234a629fefcSchristos debug3_f("terminating %zu helpers", nhelpers); 235a629fefcSchristos for (i = 0; i < nhelpers; i++) 236a629fefcSchristos helper_terminate(helpers[i]); 237264ec8a8Sadam } 238264ec8a8Sadam 239264ec8a8Sadam static int 240aa36fcacSchristos rsa_encrypt(int flen, const u_char *from, u_char *to, RSA *rsa, int padding) 241264ec8a8Sadam { 242aa36fcacSchristos struct sshkey *key = NULL; 243aa36fcacSchristos struct sshbuf *msg = NULL; 244aa36fcacSchristos u_char *blob = NULL, *signature = NULL; 24555a4608bSchristos size_t blen, slen = 0; 24655a4608bSchristos int r, ret = -1; 247a629fefcSchristos struct helper *helper; 248264ec8a8Sadam 249a629fefcSchristos if ((helper = helper_by_rsa(rsa)) == NULL || helper->fd == -1) 250a629fefcSchristos fatal_f("no helper for PKCS11 key"); 251a629fefcSchristos debug3_f("signing with PKCS11 provider %s", helper->path); 252264ec8a8Sadam if (padding != RSA_PKCS1_PADDING) 253aa36fcacSchristos goto fail; 254*9469f4f1Schristos if ((key = sshkey_new(KEY_UNSPEC)) == NULL) { 25517418e98Schristos error_f("sshkey_new failed"); 256aa36fcacSchristos goto fail; 257aa36fcacSchristos } 258*9469f4f1Schristos if ((key->pkey = EVP_PKEY_new()) == NULL || 259*9469f4f1Schristos EVP_PKEY_set1_RSA(key->pkey, rsa) != 1) { 260*9469f4f1Schristos error_f("pkey setup failed"); 261*9469f4f1Schristos goto fail; 262*9469f4f1Schristos } 263*9469f4f1Schristos 264aa36fcacSchristos key->type = KEY_RSA; 265aa36fcacSchristos if ((r = sshkey_to_blob(key, &blob, &blen)) != 0) { 26617418e98Schristos error_fr(r, "encode key"); 267aa36fcacSchristos goto fail; 26855a4608bSchristos } 26955a4608bSchristos if ((msg = sshbuf_new()) == NULL) 27017418e98Schristos fatal_f("sshbuf_new failed"); 27155a4608bSchristos if ((r = sshbuf_put_u8(msg, SSH2_AGENTC_SIGN_REQUEST)) != 0 || 27255a4608bSchristos (r = sshbuf_put_string(msg, blob, blen)) != 0 || 27355a4608bSchristos (r = sshbuf_put_string(msg, from, flen)) != 0 || 27455a4608bSchristos (r = sshbuf_put_u32(msg, 0)) != 0) 27517418e98Schristos fatal_fr(r, "compose"); 276a629fefcSchristos send_msg(helper->fd, msg); 27755a4608bSchristos sshbuf_reset(msg); 278264ec8a8Sadam 279a629fefcSchristos if (recv_msg(helper->fd, msg) == SSH2_AGENT_SIGN_RESPONSE) { 28055a4608bSchristos if ((r = sshbuf_get_string(msg, &signature, &slen)) != 0) 28117418e98Schristos fatal_fr(r, "parse"); 28255a4608bSchristos if (slen <= (size_t)RSA_size(rsa)) { 283264ec8a8Sadam memcpy(to, signature, slen); 284264ec8a8Sadam ret = slen; 285264ec8a8Sadam } 28600a838c4Schristos free(signature); 287264ec8a8Sadam } 288aa36fcacSchristos fail: 289aa36fcacSchristos free(blob); 290aa36fcacSchristos sshkey_free(key); 29155a4608bSchristos sshbuf_free(msg); 292264ec8a8Sadam return (ret); 293264ec8a8Sadam } 294264ec8a8Sadam 295a629fefcSchristos static int 296a629fefcSchristos rsa_finish(RSA *rsa) 297a629fefcSchristos { 298a629fefcSchristos struct helper *helper; 299a629fefcSchristos 300a629fefcSchristos if ((helper = helper_by_rsa(rsa)) == NULL) 301a629fefcSchristos fatal_f("no helper for PKCS11 key"); 302a629fefcSchristos debug3_f("free PKCS11 RSA key for provider %s", helper->path); 303a629fefcSchristos if (helper->rsa_finish != NULL) 304a629fefcSchristos helper->rsa_finish(rsa); 305a629fefcSchristos if (helper->nrsa == 0) 306a629fefcSchristos fatal_f("RSA refcount error"); 307a629fefcSchristos helper->nrsa--; 308a629fefcSchristos debug3_f("provider %s remaining keys: %zu RSA %zu ECDSA", 309a629fefcSchristos helper->path, helper->nrsa, helper->nec); 310a629fefcSchristos if (helper->nrsa == 0 && helper->nec == 0) 311a629fefcSchristos helper_terminate(helper); 312a629fefcSchristos return 1; 313a629fefcSchristos } 314a629fefcSchristos 315aa36fcacSchristos static ECDSA_SIG * 316aa36fcacSchristos ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv, 317aa36fcacSchristos const BIGNUM *rp, EC_KEY *ec) 318264ec8a8Sadam { 319aa36fcacSchristos struct sshkey *key = NULL; 320aa36fcacSchristos struct sshbuf *msg = NULL; 321aa36fcacSchristos ECDSA_SIG *ret = NULL; 322aa36fcacSchristos const u_char *cp; 323aa36fcacSchristos u_char *blob = NULL, *signature = NULL; 324aa36fcacSchristos size_t blen, slen = 0; 325aa36fcacSchristos int r, nid; 326a629fefcSchristos struct helper *helper; 327aa36fcacSchristos 328a629fefcSchristos if ((helper = helper_by_ec(ec)) == NULL || helper->fd == -1) 329a629fefcSchristos fatal_f("no helper for PKCS11 key"); 330a629fefcSchristos debug3_f("signing with PKCS11 provider %s", helper->path); 331aa36fcacSchristos 332*9469f4f1Schristos if ((key = sshkey_new(KEY_UNSPEC)) == NULL) { 33317418e98Schristos error_f("sshkey_new failed"); 334aa36fcacSchristos goto fail; 335aa36fcacSchristos } 336*9469f4f1Schristos if ((key->pkey = EVP_PKEY_new()) == NULL || 337*9469f4f1Schristos EVP_PKEY_set1_EC_KEY(key->pkey, ec) != 1) { 338*9469f4f1Schristos error("pkey setup failed"); 339*9469f4f1Schristos goto fail; 340*9469f4f1Schristos } 341*9469f4f1Schristos if ((nid = sshkey_ecdsa_pkey_to_nid(key->pkey)) < 0) { 342*9469f4f1Schristos error("couldn't get curve nid"); 343*9469f4f1Schristos goto fail; 344*9469f4f1Schristos } 345aa36fcacSchristos key->ecdsa_nid = nid; 346aa36fcacSchristos key->type = KEY_ECDSA; 347aa36fcacSchristos 348aa36fcacSchristos if ((r = sshkey_to_blob(key, &blob, &blen)) != 0) { 34917418e98Schristos error_fr(r, "encode key"); 350aa36fcacSchristos goto fail; 351aa36fcacSchristos } 352aa36fcacSchristos if ((msg = sshbuf_new()) == NULL) 35317418e98Schristos fatal_f("sshbuf_new failed"); 354aa36fcacSchristos if ((r = sshbuf_put_u8(msg, SSH2_AGENTC_SIGN_REQUEST)) != 0 || 355aa36fcacSchristos (r = sshbuf_put_string(msg, blob, blen)) != 0 || 356aa36fcacSchristos (r = sshbuf_put_string(msg, dgst, dgst_len)) != 0 || 357aa36fcacSchristos (r = sshbuf_put_u32(msg, 0)) != 0) 35817418e98Schristos fatal_fr(r, "compose"); 359a629fefcSchristos send_msg(helper->fd, msg); 360aa36fcacSchristos sshbuf_reset(msg); 361aa36fcacSchristos 362a629fefcSchristos if (recv_msg(helper->fd, msg) == SSH2_AGENT_SIGN_RESPONSE) { 363aa36fcacSchristos if ((r = sshbuf_get_string(msg, &signature, &slen)) != 0) 36417418e98Schristos fatal_fr(r, "parse"); 365aa36fcacSchristos cp = signature; 366aa36fcacSchristos ret = d2i_ECDSA_SIG(NULL, &cp, slen); 367aa36fcacSchristos free(signature); 368aa36fcacSchristos } 369aa36fcacSchristos 370aa36fcacSchristos fail: 371aa36fcacSchristos free(blob); 372aa36fcacSchristos sshkey_free(key); 373aa36fcacSchristos sshbuf_free(msg); 374aa36fcacSchristos return (ret); 375aa36fcacSchristos } 376aa36fcacSchristos 377a629fefcSchristos static void 378a629fefcSchristos ecdsa_do_finish(EC_KEY *ec) 379a629fefcSchristos { 380a629fefcSchristos struct helper *helper; 381a629fefcSchristos 382a629fefcSchristos if ((helper = helper_by_ec(ec)) == NULL) 383a629fefcSchristos fatal_f("no helper for PKCS11 key"); 384a629fefcSchristos debug3_f("free PKCS11 ECDSA key for provider %s", helper->path); 385a629fefcSchristos if (helper->ec_finish != NULL) 386a629fefcSchristos helper->ec_finish(ec); 387a629fefcSchristos if (helper->nec == 0) 388a629fefcSchristos fatal_f("ECDSA refcount error"); 389a629fefcSchristos helper->nec--; 390a629fefcSchristos debug3_f("provider %s remaining keys: %zu RSA %zu ECDSA", 391a629fefcSchristos helper->path, helper->nrsa, helper->nec); 392a629fefcSchristos if (helper->nrsa == 0 && helper->nec == 0) 393a629fefcSchristos helper_terminate(helper); 394a629fefcSchristos } 395aa36fcacSchristos 396aa36fcacSchristos /* redirect private key crypto operations to the ssh-pkcs11-helper */ 397aa36fcacSchristos static void 398a629fefcSchristos wrap_key(struct helper *helper, struct sshkey *k) 399aa36fcacSchristos { 400*9469f4f1Schristos RSA *rsa = NULL; 401*9469f4f1Schristos EC_KEY *ecdsa = NULL; 402*9469f4f1Schristos 403a629fefcSchristos debug3_f("wrap %s for provider %s", sshkey_type(k), helper->path); 404a629fefcSchristos if (k->type == KEY_RSA) { 405*9469f4f1Schristos if ((rsa = EVP_PKEY_get1_RSA(k->pkey)) == NULL) 406*9469f4f1Schristos fatal_f("no RSA key"); 407*9469f4f1Schristos if (RSA_set_method(rsa, helper->rsa_meth) != 1) 408*9469f4f1Schristos fatal_f("RSA_set_method failed"); 409a629fefcSchristos if (helper->nrsa++ >= INT_MAX) 410a629fefcSchristos fatal_f("RSA refcount error"); 411*9469f4f1Schristos if (EVP_PKEY_set1_RSA(k->pkey, rsa) != 1) 412*9469f4f1Schristos fatal_f("EVP_PKEY_set1_RSA failed"); 413*9469f4f1Schristos RSA_free(rsa); 414a629fefcSchristos } else if (k->type == KEY_ECDSA) { 415*9469f4f1Schristos if ((ecdsa = EVP_PKEY_get1_EC_KEY(k->pkey)) == NULL) 416*9469f4f1Schristos fatal_f("no ECDSA key"); 417*9469f4f1Schristos if (EC_KEY_set_method(ecdsa, helper->ec_meth) != 1) 418*9469f4f1Schristos fatal_f("EC_KEY_set_method failed"); 419a629fefcSchristos if (helper->nec++ >= INT_MAX) 420a629fefcSchristos fatal_f("EC refcount error"); 421*9469f4f1Schristos if (EVP_PKEY_set1_EC_KEY(k->pkey, ecdsa) != 1) 422*9469f4f1Schristos fatal_f("EVP_PKEY_set1_EC_KEY failed"); 423*9469f4f1Schristos EC_KEY_free(ecdsa); 424a629fefcSchristos } else 42517418e98Schristos fatal_f("unknown key type"); 426a629fefcSchristos k->flags |= SSHKEY_FLAG_EXT; 427a629fefcSchristos debug3_f("provider %s remaining keys: %zu RSA %zu ECDSA", 428a629fefcSchristos helper->path, helper->nrsa, helper->nec); 429aa36fcacSchristos } 430aa36fcacSchristos 431514b5d45Schristos /* 432514b5d45Schristos * Make a private PKCS#11-backed certificate by grafting a previously-loaded 433514b5d45Schristos * PKCS#11 private key and a public certificate key. 434514b5d45Schristos */ 435514b5d45Schristos int 436514b5d45Schristos pkcs11_make_cert(const struct sshkey *priv, 437514b5d45Schristos const struct sshkey *certpub, struct sshkey **certprivp) 438514b5d45Schristos { 439514b5d45Schristos struct helper *helper = NULL; 440514b5d45Schristos struct sshkey *ret; 441514b5d45Schristos int r; 442*9469f4f1Schristos RSA *rsa_priv = NULL, *rsa_cert = NULL; 443*9469f4f1Schristos EC_KEY *ec_priv = NULL, *ec_cert = NULL; 444514b5d45Schristos 445514b5d45Schristos debug3_f("private key type %s cert type %s", sshkey_type(priv), 446514b5d45Schristos sshkey_type(certpub)); 447514b5d45Schristos *certprivp = NULL; 448514b5d45Schristos if (!sshkey_is_cert(certpub) || sshkey_is_cert(priv) || 449514b5d45Schristos !sshkey_equal_public(priv, certpub)) { 450514b5d45Schristos error_f("private key %s doesn't match cert %s", 451514b5d45Schristos sshkey_type(priv), sshkey_type(certpub)); 452514b5d45Schristos return SSH_ERR_INVALID_ARGUMENT; 453514b5d45Schristos } 454514b5d45Schristos *certprivp = NULL; 455514b5d45Schristos if (priv->type == KEY_RSA) { 456*9469f4f1Schristos if ((rsa_priv = EVP_PKEY_get1_RSA(priv->pkey)) == NULL) 457*9469f4f1Schristos fatal_f("no RSA pkey"); 458*9469f4f1Schristos if ((helper = helper_by_rsa(rsa_priv)) == NULL || 459514b5d45Schristos helper->fd == -1) 460514b5d45Schristos fatal_f("no helper for PKCS11 RSA key"); 461514b5d45Schristos if ((r = sshkey_from_private(priv, &ret)) != 0) 462514b5d45Schristos fatal_fr(r, "copy key"); 463*9469f4f1Schristos if ((rsa_cert = EVP_PKEY_get1_RSA(ret->pkey)) == NULL) 464*9469f4f1Schristos fatal_f("no RSA cert pkey"); 465*9469f4f1Schristos if (RSA_set_method(rsa_cert, helper->rsa_meth) != 1) 466*9469f4f1Schristos fatal_f("RSA_set_method failed"); 467514b5d45Schristos if (helper->nrsa++ >= INT_MAX) 468514b5d45Schristos fatal_f("RSA refcount error"); 469*9469f4f1Schristos if (EVP_PKEY_set1_RSA(ret->pkey, rsa_cert) != 1) 470*9469f4f1Schristos fatal_f("EVP_PKEY_set1_RSA failed"); 471*9469f4f1Schristos RSA_free(rsa_priv); 472*9469f4f1Schristos RSA_free(rsa_cert); 473514b5d45Schristos } else if (priv->type == KEY_ECDSA) { 474*9469f4f1Schristos if ((ec_priv = EVP_PKEY_get1_EC_KEY(priv->pkey)) == NULL) 475*9469f4f1Schristos fatal_f("no EC pkey"); 476*9469f4f1Schristos if ((helper = helper_by_ec(ec_priv)) == NULL || 477514b5d45Schristos helper->fd == -1) 478514b5d45Schristos fatal_f("no helper for PKCS11 EC key"); 479514b5d45Schristos if ((r = sshkey_from_private(priv, &ret)) != 0) 480514b5d45Schristos fatal_fr(r, "copy key"); 481*9469f4f1Schristos if ((ec_cert = EVP_PKEY_get1_EC_KEY(ret->pkey)) == NULL) 482*9469f4f1Schristos fatal_f("no EC cert pkey"); 483*9469f4f1Schristos if (EC_KEY_set_method(ec_cert, helper->ec_meth) != 1) 484*9469f4f1Schristos fatal_f("EC_KEY_set_method failed"); 485514b5d45Schristos if (helper->nec++ >= INT_MAX) 486514b5d45Schristos fatal_f("EC refcount error"); 487*9469f4f1Schristos if (EVP_PKEY_set1_EC_KEY(ret->pkey, ec_cert) != 1) 488*9469f4f1Schristos fatal_f("EVP_PKEY_set1_EC_KEY failed"); 489*9469f4f1Schristos EC_KEY_free(ec_priv); 490*9469f4f1Schristos EC_KEY_free(ec_cert); 491514b5d45Schristos } else 492514b5d45Schristos fatal_f("unknown key type %s", sshkey_type(priv)); 493514b5d45Schristos 494514b5d45Schristos ret->flags |= SSHKEY_FLAG_EXT; 495514b5d45Schristos if ((r = sshkey_to_certified(ret)) != 0 || 496514b5d45Schristos (r = sshkey_cert_copy(certpub, ret)) != 0) 497514b5d45Schristos fatal_fr(r, "graft certificate"); 498514b5d45Schristos debug3_f("provider %s remaining keys: %zu RSA %zu ECDSA", 499514b5d45Schristos helper->path, helper->nrsa, helper->nec); 500514b5d45Schristos /* success */ 501514b5d45Schristos *certprivp = ret; 502514b5d45Schristos return 0; 503514b5d45Schristos } 504514b5d45Schristos 505aa36fcacSchristos static int 506a629fefcSchristos pkcs11_start_helper_methods(struct helper *helper) 507aa36fcacSchristos { 508a629fefcSchristos int (*ec_init)(EC_KEY *key); 509a629fefcSchristos int (*ec_copy)(EC_KEY *dest, const EC_KEY *src); 510a629fefcSchristos int (*ec_set_group)(EC_KEY *key, const EC_GROUP *grp); 511a629fefcSchristos int (*ec_set_private)(EC_KEY *key, const BIGNUM *priv_key); 512a629fefcSchristos int (*ec_set_public)(EC_KEY *key, const EC_POINT *pub_key); 513a629fefcSchristos int (*ec_sign)(int, const unsigned char *, int, unsigned char *, 514aa36fcacSchristos unsigned int *, const BIGNUM *, const BIGNUM *, EC_KEY *) = NULL; 515a629fefcSchristos RSA_METHOD *rsa_meth; 516a629fefcSchristos EC_KEY_METHOD *ec_meth; 517264ec8a8Sadam 518a629fefcSchristos if ((ec_meth = EC_KEY_METHOD_new(EC_KEY_OpenSSL())) == NULL) 519a629fefcSchristos return -1; 520a629fefcSchristos EC_KEY_METHOD_get_sign(ec_meth, &ec_sign, NULL, NULL); 521a629fefcSchristos EC_KEY_METHOD_set_sign(ec_meth, ec_sign, NULL, ecdsa_do_sign); 522a629fefcSchristos EC_KEY_METHOD_get_init(ec_meth, &ec_init, &helper->ec_finish, 523a629fefcSchristos &ec_copy, &ec_set_group, &ec_set_private, &ec_set_public); 524a629fefcSchristos EC_KEY_METHOD_set_init(ec_meth, ec_init, ecdsa_do_finish, 525a629fefcSchristos ec_copy, ec_set_group, ec_set_private, ec_set_public); 526a629fefcSchristos 527a629fefcSchristos if ((rsa_meth = RSA_meth_dup(RSA_get_default_method())) == NULL) 52817418e98Schristos fatal_f("RSA_meth_dup failed"); 529a629fefcSchristos helper->rsa_finish = RSA_meth_get_finish(rsa_meth); 530a629fefcSchristos if (!RSA_meth_set1_name(rsa_meth, "ssh-pkcs11-helper") || 531a629fefcSchristos !RSA_meth_set_priv_enc(rsa_meth, rsa_encrypt) || 532a629fefcSchristos !RSA_meth_set_finish(rsa_meth, rsa_finish)) 53317418e98Schristos fatal_f("failed to prepare method"); 534aa36fcacSchristos 535a629fefcSchristos helper->ec_meth = ec_meth; 536a629fefcSchristos helper->rsa_meth = rsa_meth; 537a629fefcSchristos return 0; 538264ec8a8Sadam } 539264ec8a8Sadam 540a629fefcSchristos static struct helper * 541a629fefcSchristos pkcs11_start_helper(const char *path) 542264ec8a8Sadam { 543264ec8a8Sadam int pair[2]; 544a629fefcSchristos const char *prog, *verbosity = NULL; 545a629fefcSchristos struct helper *helper; 546a629fefcSchristos pid_t pid; 547aa36fcacSchristos 548a629fefcSchristos if (nhelpers >= INT_MAX) 549a629fefcSchristos fatal_f("too many helpers"); 550a629fefcSchristos debug3_f("start helper for %s", path); 551264ec8a8Sadam if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) { 552a629fefcSchristos error_f("socketpair: %s", strerror(errno)); 553a629fefcSchristos return NULL; 554a629fefcSchristos } 555a629fefcSchristos helper = xcalloc(1, sizeof(*helper)); 556a629fefcSchristos if (pkcs11_start_helper_methods(helper) == -1) { 557a629fefcSchristos error_f("pkcs11_start_helper_methods failed"); 558a629fefcSchristos goto fail; 559264ec8a8Sadam } 560264ec8a8Sadam if ((pid = fork()) == -1) { 561a629fefcSchristos error_f("fork: %s", strerror(errno)); 562a629fefcSchristos fail: 563a629fefcSchristos close(pair[0]); 564a629fefcSchristos close(pair[1]); 565a629fefcSchristos RSA_meth_free(helper->rsa_meth); 566a629fefcSchristos EC_KEY_METHOD_free(helper->ec_meth); 567a629fefcSchristos free(helper); 568a629fefcSchristos return NULL; 569264ec8a8Sadam } else if (pid == 0) { 570264ec8a8Sadam if ((dup2(pair[1], STDIN_FILENO) == -1) || 571264ec8a8Sadam (dup2(pair[1], STDOUT_FILENO) == -1)) { 572264ec8a8Sadam fprintf(stderr, "dup2: %s\n", strerror(errno)); 573264ec8a8Sadam _exit(1); 574264ec8a8Sadam } 575264ec8a8Sadam close(pair[0]); 576264ec8a8Sadam close(pair[1]); 577a629fefcSchristos prog = getenv("SSH_PKCS11_HELPER"); 578a629fefcSchristos if (prog == NULL || strlen(prog) == 0) 579a629fefcSchristos prog = _PATH_SSH_PKCS11_HELPER; 580a629fefcSchristos if (log_level_get() >= SYSLOG_LEVEL_DEBUG1) 581a629fefcSchristos verbosity = "-vvv"; 582a629fefcSchristos debug_f("starting %s %s", prog, 583aa36fcacSchristos verbosity == NULL ? "" : verbosity); 584a629fefcSchristos execlp(prog, prog, verbosity, (char *)NULL); 585a629fefcSchristos fprintf(stderr, "exec: %s: %s\n", prog, strerror(errno)); 586264ec8a8Sadam _exit(1); 587264ec8a8Sadam } 588264ec8a8Sadam close(pair[1]); 589a629fefcSchristos helper->fd = pair[0]; 590a629fefcSchristos helper->path = xstrdup(path); 591a629fefcSchristos helper->pid = pid; 592a629fefcSchristos debug3_f("helper %zu for \"%s\" on fd %d pid %ld", nhelpers, 593a629fefcSchristos helper->path, helper->fd, (long)helper->pid); 594a629fefcSchristos helpers = xrecallocarray(helpers, nhelpers, 595a629fefcSchristos nhelpers + 1, sizeof(*helpers)); 596a629fefcSchristos helpers[nhelpers++] = helper; 597a629fefcSchristos return helper; 598264ec8a8Sadam } 599264ec8a8Sadam 600264ec8a8Sadam int 601ed75d7a8Schristos pkcs11_add_provider(char *name, char *pin, struct sshkey ***keysp, 602ed75d7a8Schristos char ***labelsp) 603264ec8a8Sadam { 6047a183406Schristos struct sshkey *k; 605aa36fcacSchristos int r, type; 606264ec8a8Sadam u_char *blob; 607ed75d7a8Schristos char *label; 60855a4608bSchristos size_t blen; 60955a4608bSchristos u_int nkeys, i; 61055a4608bSchristos struct sshbuf *msg; 611a629fefcSchristos struct helper *helper; 612264ec8a8Sadam 613a629fefcSchristos if ((helper = helper_by_provider(name)) == NULL && 614a629fefcSchristos (helper = pkcs11_start_helper(name)) == NULL) 615a629fefcSchristos return -1; 616264ec8a8Sadam 61755a4608bSchristos if ((msg = sshbuf_new()) == NULL) 61817418e98Schristos fatal_f("sshbuf_new failed"); 61955a4608bSchristos if ((r = sshbuf_put_u8(msg, SSH_AGENTC_ADD_SMARTCARD_KEY)) != 0 || 62055a4608bSchristos (r = sshbuf_put_cstring(msg, name)) != 0 || 62155a4608bSchristos (r = sshbuf_put_cstring(msg, pin)) != 0) 62217418e98Schristos fatal_fr(r, "compose"); 623a629fefcSchristos send_msg(helper->fd, msg); 62455a4608bSchristos sshbuf_reset(msg); 625264ec8a8Sadam 626a629fefcSchristos type = recv_msg(helper->fd, msg); 627aa36fcacSchristos if (type == SSH2_AGENT_IDENTITIES_ANSWER) { 62855a4608bSchristos if ((r = sshbuf_get_u32(msg, &nkeys)) != 0) 62917418e98Schristos fatal_fr(r, "parse nkeys"); 63055a4608bSchristos *keysp = xcalloc(nkeys, sizeof(struct sshkey *)); 631ed75d7a8Schristos if (labelsp) 632ed75d7a8Schristos *labelsp = xcalloc(nkeys, sizeof(char *)); 633264ec8a8Sadam for (i = 0; i < nkeys; i++) { 63455a4608bSchristos /* XXX clean up properly instead of fatal() */ 63555a4608bSchristos if ((r = sshbuf_get_string(msg, &blob, &blen)) != 0 || 636ed75d7a8Schristos (r = sshbuf_get_cstring(msg, &label, NULL)) != 0) 63717418e98Schristos fatal_fr(r, "parse key"); 63855a4608bSchristos if ((r = sshkey_from_blob(blob, blen, &k)) != 0) 63917418e98Schristos fatal_fr(r, "decode key"); 640a629fefcSchristos wrap_key(helper, k); 641264ec8a8Sadam (*keysp)[i] = k; 642ed75d7a8Schristos if (labelsp) 643ed75d7a8Schristos (*labelsp)[i] = label; 644ed75d7a8Schristos else 645ed75d7a8Schristos free(label); 64600a838c4Schristos free(blob); 647264ec8a8Sadam } 648aa36fcacSchristos } else if (type == SSH2_AGENT_FAILURE) { 649aa36fcacSchristos if ((r = sshbuf_get_u32(msg, &nkeys)) != 0) 650aa36fcacSchristos nkeys = -1; 651264ec8a8Sadam } else { 652264ec8a8Sadam nkeys = -1; 653264ec8a8Sadam } 65455a4608bSchristos sshbuf_free(msg); 655264ec8a8Sadam return (nkeys); 656264ec8a8Sadam } 657264ec8a8Sadam 658264ec8a8Sadam int 659264ec8a8Sadam pkcs11_del_provider(char *name) 660264ec8a8Sadam { 661a629fefcSchristos struct helper *helper; 662264ec8a8Sadam 663a629fefcSchristos /* 664a629fefcSchristos * ssh-agent deletes keys before calling this, so the helper entry 665a629fefcSchristos * should be gone before we get here. 666a629fefcSchristos */ 667a629fefcSchristos debug3_f("delete %s", name); 668a629fefcSchristos if ((helper = helper_by_provider(name)) != NULL) 669a629fefcSchristos helper_terminate(helper); 670a629fefcSchristos return 0; 671264ec8a8Sadam } 672