1 /* $NetBSD: ssh-ecdsa.c,v 1.18 2026/04/08 18:58:41 christos Exp $ */ 2 /* $OpenBSD: ssh-ecdsa.c,v 1.29 2026/02/14 00:18:34 jsg Exp $ */ 3 4 /* 5 * Copyright (c) 2000 Markus Friedl. All rights reserved. 6 * Copyright (c) 2010 Damien Miller. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "includes.h" 30 __RCSID("$NetBSD: ssh-ecdsa.c,v 1.18 2026/04/08 18:58:41 christos Exp $"); 31 #include <sys/types.h> 32 33 #include <openssl/bn.h> 34 #include <openssl/ec.h> 35 #include <openssl/ecdsa.h> 36 #include <openssl/evp.h> 37 38 #include <string.h> 39 40 #include "sshbuf.h" 41 #include "ssherr.h" 42 #define SSHKEY_INTERNAL 43 #include "sshkey.h" 44 45 int 46 sshkey_ecdsa_fixup_group(EVP_PKEY *k) 47 { 48 int nids[] = { 49 NID_X9_62_prime256v1, 50 NID_secp384r1, 51 NID_secp521r1, 52 -1 53 }; 54 int nid = -1; 55 u_int i; 56 const EC_GROUP *g; 57 EC_KEY *ec = NULL; 58 EC_GROUP *eg = NULL; 59 60 if ((ec = EVP_PKEY_get1_EC_KEY(k)) == NULL || 61 (g = EC_KEY_get0_group(ec)) == NULL) 62 goto out; 63 /* 64 * The group may be stored in a ASN.1 encoded private key in one of two 65 * ways: as a "named group", which is reconstituted by ASN.1 object ID 66 * or explicit group parameters encoded into the key blob. Only the 67 * "named group" case sets the group NID for us, but we can figure 68 * it out for the other case by comparing against all the groups that 69 * are supported. 70 */ 71 if ((nid = EC_GROUP_get_curve_name(g)) > 0) 72 goto out; 73 nid = -1; 74 for (i = 0; nids[i] != -1; i++) { 75 if ((eg = EC_GROUP_new_by_curve_name(nids[i])) == NULL) 76 goto out; 77 if (EC_GROUP_cmp(g, eg, NULL) == 0) 78 break; 79 EC_GROUP_free(eg); 80 eg = NULL; 81 } 82 if (nids[i] == -1) 83 goto out; 84 85 /* Use the group with the NID attached */ 86 EC_GROUP_set_asn1_flag(eg, OPENSSL_EC_NAMED_CURVE); 87 if (EC_KEY_set_group(ec, eg) != 1 || 88 EVP_PKEY_set1_EC_KEY(k, ec) != 1) 89 goto out; 90 /* success */ 91 nid = nids[i]; 92 out: 93 EC_KEY_free(ec); 94 EC_GROUP_free(eg); 95 return nid; 96 } 97 98 static u_int 99 ssh_ecdsa_size(const struct sshkey *key) 100 { 101 switch (key->ecdsa_nid) { 102 case NID_X9_62_prime256v1: 103 return 256; 104 case NID_secp384r1: 105 return 384; 106 case NID_secp521r1: 107 return 521; 108 default: 109 return 0; 110 } 111 } 112 113 static void 114 ssh_ecdsa_cleanup(struct sshkey *k) 115 { 116 EVP_PKEY_free(k->pkey); 117 k->pkey = NULL; 118 } 119 120 static int 121 ssh_ecdsa_equal(const struct sshkey *a, const struct sshkey *b) 122 { 123 if (a->pkey == NULL || b->pkey == NULL) 124 return 0; 125 return EVP_PKEY_cmp(a->pkey, b->pkey) == 1; 126 } 127 128 static int 129 ssh_ecdsa_serialize_public(const struct sshkey *key, struct sshbuf *b, 130 enum sshkey_serialize_rep opts) 131 { 132 int r; 133 134 if (key->pkey == NULL) 135 return SSH_ERR_INVALID_ARGUMENT; 136 if ((r = sshbuf_put_cstring(b, 137 sshkey_curve_nid_to_name(key->ecdsa_nid))) != 0 || 138 (r = sshbuf_put_ec_pkey(b, key->pkey)) != 0) 139 return r; 140 141 return 0; 142 } 143 144 static int 145 ssh_ecdsa_serialize_private(const struct sshkey *key, struct sshbuf *b, 146 enum sshkey_serialize_rep opts) 147 { 148 int r; 149 150 if (!sshkey_is_cert(key)) { 151 if ((r = ssh_ecdsa_serialize_public(key, b, opts)) != 0) 152 return r; 153 } 154 if ((r = sshbuf_put_bignum2(b, 155 EC_KEY_get0_private_key(EVP_PKEY_get0_EC_KEY(key->pkey)))) != 0) 156 return r; 157 return 0; 158 } 159 160 static int 161 ssh_ecdsa_generate(struct sshkey *k, int bits) 162 { 163 EVP_PKEY *res = NULL; 164 EVP_PKEY_CTX *ctx = NULL; 165 int ret = SSH_ERR_INTERNAL_ERROR; 166 167 if ((k->ecdsa_nid = sshkey_ecdsa_bits_to_nid(bits)) == -1) 168 return SSH_ERR_KEY_LENGTH; 169 170 if ((ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)) == NULL) 171 return SSH_ERR_ALLOC_FAIL; 172 173 if (EVP_PKEY_keygen_init(ctx) <= 0 || 174 EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, k->ecdsa_nid) <= 0 || 175 EVP_PKEY_keygen(ctx, &res) <= 0) { 176 ret = SSH_ERR_LIBCRYPTO_ERROR; 177 goto out; 178 } 179 /* success */ 180 k->pkey = res; 181 res = NULL; 182 ret = 0; 183 out: 184 EVP_PKEY_free(res); 185 EVP_PKEY_CTX_free(ctx); 186 return ret; 187 } 188 189 static int 190 ssh_ecdsa_copy_public(const struct sshkey *from, struct sshkey *to) 191 { 192 const EC_KEY *ec_from; 193 EC_KEY *ec_to = NULL; 194 int ret = SSH_ERR_INTERNAL_ERROR; 195 196 ec_from = EVP_PKEY_get0_EC_KEY(from->pkey); 197 if (ec_from == NULL) 198 return SSH_ERR_LIBCRYPTO_ERROR; 199 200 to->ecdsa_nid = from->ecdsa_nid; 201 if ((ec_to = EC_KEY_new_by_curve_name(from->ecdsa_nid)) == NULL) 202 return SSH_ERR_ALLOC_FAIL; 203 if (EC_KEY_set_public_key(ec_to, 204 EC_KEY_get0_public_key(ec_from)) != 1) { 205 ret = SSH_ERR_LIBCRYPTO_ERROR; 206 goto out; 207 } 208 EVP_PKEY_free(to->pkey); 209 if ((to->pkey = EVP_PKEY_new()) == NULL) { 210 ret = SSH_ERR_ALLOC_FAIL; 211 goto out; 212 } 213 if (EVP_PKEY_set1_EC_KEY(to->pkey, ec_to) != 1) { 214 ret = SSH_ERR_LIBCRYPTO_ERROR; 215 goto out; 216 } 217 ret = 0; 218 out: 219 EC_KEY_free(ec_to); 220 return ret; 221 } 222 223 static int 224 ssh_ecdsa_deserialize_public(const char *ktype, struct sshbuf *b, 225 struct sshkey *key) 226 { 227 int r; 228 char *curve = NULL; 229 EVP_PKEY *pkey = NULL; 230 EC_KEY *ec = NULL; 231 232 if ((key->ecdsa_nid = sshkey_ecdsa_nid_from_name(ktype)) == -1) 233 return SSH_ERR_INVALID_ARGUMENT; 234 if ((r = sshbuf_get_cstring(b, &curve, NULL)) != 0) 235 goto out; 236 if (key->ecdsa_nid != sshkey_curve_name_to_nid(curve)) { 237 r = SSH_ERR_EC_CURVE_MISMATCH; 238 goto out; 239 } 240 if ((ec = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL) { 241 r = SSH_ERR_LIBCRYPTO_ERROR; 242 goto out; 243 } 244 if ((r = sshbuf_get_eckey(b, ec)) != 0) 245 goto out; 246 if (sshkey_ec_validate_public(EC_KEY_get0_group(ec), 247 EC_KEY_get0_public_key(ec)) != 0) { 248 r = SSH_ERR_KEY_INVALID_EC_VALUE; 249 goto out; 250 } 251 if ((pkey = EVP_PKEY_new()) == NULL) { 252 r = SSH_ERR_ALLOC_FAIL; 253 goto out; 254 } 255 if (EVP_PKEY_set1_EC_KEY(pkey, ec) != 1) { 256 r = SSH_ERR_LIBCRYPTO_ERROR; 257 goto out; 258 } 259 EVP_PKEY_free(key->pkey); 260 key->pkey = pkey; 261 pkey = NULL; 262 /* success */ 263 r = 0; 264 #ifdef DEBUG_PK 265 sshkey_dump_ec_point( 266 EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(key->pkey)), 267 EC_KEY_get0_public_key(EVP_PKEY_get0_EC_KEY(key->pkey))); 268 #endif 269 out: 270 EC_KEY_free(ec); 271 EVP_PKEY_free(pkey); 272 free(curve); 273 return r; 274 } 275 276 static int 277 ssh_ecdsa_deserialize_private(const char *ktype, struct sshbuf *b, 278 struct sshkey *key) 279 { 280 int r; 281 BIGNUM *exponent = NULL; 282 EC_KEY *ec = NULL; 283 284 if (!sshkey_is_cert(key)) { 285 if ((r = ssh_ecdsa_deserialize_public(ktype, b, key)) != 0) 286 return r; 287 } 288 if ((r = sshbuf_get_bignum2(b, &exponent)) != 0) 289 goto out; 290 if ((ec = EVP_PKEY_get1_EC_KEY(key->pkey)) == NULL) { 291 r = SSH_ERR_LIBCRYPTO_ERROR; 292 goto out; 293 } 294 if (EC_KEY_set_private_key(ec, exponent) != 1) { 295 r = SSH_ERR_LIBCRYPTO_ERROR; 296 goto out; 297 } 298 if ((r = sshkey_ec_validate_private(ec)) != 0) 299 goto out; 300 if (EVP_PKEY_set1_EC_KEY(key->pkey, ec) != 1) { 301 r = SSH_ERR_LIBCRYPTO_ERROR; 302 goto out; 303 } 304 /* success */ 305 r = 0; 306 out: 307 BN_clear_free(exponent); 308 EC_KEY_free(ec); 309 return r; 310 } 311 312 static int 313 ssh_ecdsa_sign(struct sshkey *key, 314 u_char **sigp, size_t *lenp, 315 const u_char *data, size_t dlen, 316 const char *alg, const char *sk_provider, const char *sk_pin, u_int compat) 317 { 318 ECDSA_SIG *esig = NULL; 319 unsigned char *sigb = NULL; 320 const unsigned char *psig; 321 const BIGNUM *sig_r, *sig_s; 322 int hash_alg; 323 size_t slen = 0; 324 int ret = SSH_ERR_INTERNAL_ERROR; 325 326 if (lenp != NULL) 327 *lenp = 0; 328 if (sigp != NULL) 329 *sigp = NULL; 330 331 if (key == NULL || key->pkey == NULL || 332 sshkey_type_plain(key->type) != KEY_ECDSA) 333 return SSH_ERR_INVALID_ARGUMENT; 334 335 if ((hash_alg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1) 336 return SSH_ERR_INTERNAL_ERROR; 337 338 if ((ret = sshkey_pkey_digest_sign(key->pkey, hash_alg, &sigb, &slen, 339 data, dlen)) != 0) 340 goto out; 341 342 psig = sigb; 343 if ((esig = d2i_ECDSA_SIG(NULL, &psig, slen)) == NULL) { 344 ret = SSH_ERR_LIBCRYPTO_ERROR; 345 goto out; 346 } 347 ECDSA_SIG_get0(esig, &sig_r, &sig_s); 348 349 if ((ret = ssh_ecdsa_encode_store_sig(key, sig_r, sig_s, 350 sigp, lenp)) != 0) 351 goto out; 352 /* success */ 353 ret = 0; 354 out: 355 freezero(sigb, slen); 356 ECDSA_SIG_free(esig); 357 return ret; 358 } 359 360 int 361 ssh_ecdsa_encode_store_sig(const struct sshkey *key, 362 const BIGNUM *sig_r, const BIGNUM *sig_s, 363 u_char **sigp, size_t *lenp) 364 { 365 struct sshbuf *b = NULL, *bb = NULL; 366 int ret; 367 size_t len; 368 369 if (lenp != NULL) 370 *lenp = 0; 371 if (sigp != NULL) 372 *sigp = NULL; 373 374 if ((bb = sshbuf_new()) == NULL || (b = sshbuf_new()) == NULL) { 375 ret = SSH_ERR_ALLOC_FAIL; 376 goto out; 377 } 378 if ((ret = sshbuf_put_bignum2(bb, sig_r)) != 0 || 379 (ret = sshbuf_put_bignum2(bb, sig_s)) != 0) 380 goto out; 381 if ((ret = sshbuf_put_cstring(b, sshkey_ssh_name_plain(key))) != 0 || 382 (ret = sshbuf_put_stringb(b, bb)) != 0) 383 goto out; 384 len = sshbuf_len(b); 385 if (sigp != NULL) { 386 if ((*sigp = malloc(len)) == NULL) { 387 ret = SSH_ERR_ALLOC_FAIL; 388 goto out; 389 } 390 memcpy(*sigp, sshbuf_ptr(b), len); 391 } 392 if (lenp != NULL) 393 *lenp = len; 394 ret = 0; 395 out: 396 sshbuf_free(b); 397 sshbuf_free(bb); 398 return ret; 399 } 400 401 static int 402 ssh_ecdsa_verify(const struct sshkey *key, 403 const u_char *sig, size_t siglen, 404 const u_char *data, size_t dlen, const char *alg, u_int compat, 405 struct sshkey_sig_details **detailsp) 406 { 407 ECDSA_SIG *esig = NULL; 408 BIGNUM *sig_r = NULL, *sig_s = NULL; 409 int hash_alg, len = 0; 410 int ret = SSH_ERR_INTERNAL_ERROR; 411 struct sshbuf *b = NULL, *sigbuf = NULL; 412 char *ktype = NULL; 413 unsigned char *sigb = NULL, *cp; 414 415 if (key == NULL || key->pkey == NULL || 416 sshkey_type_plain(key->type) != KEY_ECDSA || 417 sig == NULL || siglen == 0) 418 return SSH_ERR_INVALID_ARGUMENT; 419 420 if ((hash_alg = sshkey_ec_nid_to_hash_alg(key->ecdsa_nid)) == -1) 421 return SSH_ERR_INTERNAL_ERROR; 422 423 /* fetch signature */ 424 if ((b = sshbuf_from(sig, siglen)) == NULL) 425 return SSH_ERR_ALLOC_FAIL; 426 if (sshbuf_get_cstring(b, &ktype, NULL) != 0 || 427 sshbuf_froms(b, &sigbuf) != 0) { 428 ret = SSH_ERR_INVALID_FORMAT; 429 goto out; 430 } 431 if (strcmp(sshkey_ssh_name_plain(key), ktype) != 0) { 432 ret = SSH_ERR_KEY_TYPE_MISMATCH; 433 goto out; 434 } 435 if (sshbuf_len(b) != 0) { 436 ret = SSH_ERR_UNEXPECTED_TRAILING_DATA; 437 goto out; 438 } 439 440 /* parse signature */ 441 if (sshbuf_get_bignum2(sigbuf, &sig_r) != 0 || 442 sshbuf_get_bignum2(sigbuf, &sig_s) != 0) { 443 ret = SSH_ERR_INVALID_FORMAT; 444 goto out; 445 } 446 if (sshbuf_len(sigbuf) != 0) { 447 ret = SSH_ERR_UNEXPECTED_TRAILING_DATA; 448 goto out; 449 } 450 451 if ((esig = ECDSA_SIG_new()) == NULL) { 452 ret = SSH_ERR_ALLOC_FAIL; 453 goto out; 454 } 455 if (!ECDSA_SIG_set0(esig, sig_r, sig_s)) { 456 ret = SSH_ERR_LIBCRYPTO_ERROR; 457 goto out; 458 } 459 sig_r = sig_s = NULL; /* transferred */ 460 461 if ((len = i2d_ECDSA_SIG(esig, NULL)) <= 0) { 462 len = 0; 463 ret = SSH_ERR_LIBCRYPTO_ERROR; 464 goto out; 465 } 466 if ((sigb = calloc(1, len)) == NULL) { 467 ret = SSH_ERR_ALLOC_FAIL; 468 goto out; 469 } 470 cp = sigb; /* ASN1_item_i2d increments the pointer past the object */ 471 if (i2d_ECDSA_SIG(esig, &cp) != len) { 472 ret = SSH_ERR_LIBCRYPTO_ERROR; 473 goto out; 474 } 475 if ((ret = sshkey_pkey_digest_verify(key->pkey, hash_alg, 476 data, dlen, sigb, len)) != 0) 477 goto out; 478 /* success */ 479 out: 480 freezero(sigb, len); 481 sshbuf_free(sigbuf); 482 sshbuf_free(b); 483 ECDSA_SIG_free(esig); 484 BN_clear_free(sig_r); 485 BN_clear_free(sig_s); 486 free(ktype); 487 return ret; 488 } 489 490 /* NB. not static; used by ECDSA-SK */ 491 const struct sshkey_impl_funcs sshkey_ecdsa_funcs = { 492 /* .size = */ ssh_ecdsa_size, 493 /* .alloc = */ NULL, 494 /* .cleanup = */ ssh_ecdsa_cleanup, 495 /* .equal = */ ssh_ecdsa_equal, 496 /* .ssh_serialize_public = */ ssh_ecdsa_serialize_public, 497 /* .ssh_deserialize_public = */ ssh_ecdsa_deserialize_public, 498 /* .ssh_serialize_private = */ ssh_ecdsa_serialize_private, 499 /* .ssh_deserialize_private = */ ssh_ecdsa_deserialize_private, 500 /* .generate = */ ssh_ecdsa_generate, 501 /* .copy_public = */ ssh_ecdsa_copy_public, 502 /* .sign = */ ssh_ecdsa_sign, 503 /* .verify = */ ssh_ecdsa_verify, 504 }; 505 506 const struct sshkey_impl sshkey_ecdsa_nistp256_impl = { 507 /* .name = */ "ecdsa-sha2-nistp256", 508 /* .shortname = */ "ECDSA", 509 /* .sigalg = */ NULL, 510 /* .type = */ KEY_ECDSA, 511 /* .nid = */ NID_X9_62_prime256v1, 512 /* .cert = */ 0, 513 /* .sigonly = */ 0, 514 /* .keybits = */ 0, 515 /* .funcs = */ &sshkey_ecdsa_funcs, 516 }; 517 518 const struct sshkey_impl sshkey_ecdsa_nistp256_cert_impl = { 519 /* .name = */ "ecdsa-sha2-nistp256-cert-v01 (at) openssh.com", 520 /* .shortname = */ "ECDSA-CERT", 521 /* .sigalg = */ NULL, 522 /* .type = */ KEY_ECDSA_CERT, 523 /* .nid = */ NID_X9_62_prime256v1, 524 /* .cert = */ 1, 525 /* .sigonly = */ 0, 526 /* .keybits = */ 0, 527 /* .funcs = */ &sshkey_ecdsa_funcs, 528 }; 529 530 const struct sshkey_impl sshkey_ecdsa_nistp384_impl = { 531 /* .name = */ "ecdsa-sha2-nistp384", 532 /* .shortname = */ "ECDSA", 533 /* .sigalg = */ NULL, 534 /* .type = */ KEY_ECDSA, 535 /* .nid = */ NID_secp384r1, 536 /* .cert = */ 0, 537 /* .sigonly = */ 0, 538 /* .keybits = */ 0, 539 /* .funcs = */ &sshkey_ecdsa_funcs, 540 }; 541 542 const struct sshkey_impl sshkey_ecdsa_nistp384_cert_impl = { 543 /* .name = */ "ecdsa-sha2-nistp384-cert-v01 (at) openssh.com", 544 /* .shortname = */ "ECDSA-CERT", 545 /* .sigalg = */ NULL, 546 /* .type = */ KEY_ECDSA_CERT, 547 /* .nid = */ NID_secp384r1, 548 /* .cert = */ 1, 549 /* .sigonly = */ 0, 550 /* .keybits = */ 0, 551 /* .funcs = */ &sshkey_ecdsa_funcs, 552 }; 553 554 const struct sshkey_impl sshkey_ecdsa_nistp521_impl = { 555 /* .name = */ "ecdsa-sha2-nistp521", 556 /* .shortname = */ "ECDSA", 557 /* .sigalg = */ NULL, 558 /* .type = */ KEY_ECDSA, 559 /* .nid = */ NID_secp521r1, 560 /* .cert = */ 0, 561 /* .sigonly = */ 0, 562 /* .keybits = */ 0, 563 /* .funcs = */ &sshkey_ecdsa_funcs, 564 }; 565 566 const struct sshkey_impl sshkey_ecdsa_nistp521_cert_impl = { 567 /* .name = */ "ecdsa-sha2-nistp521-cert-v01 (at) openssh.com", 568 /* .shortname = */ "ECDSA-CERT", 569 /* .sigalg = */ NULL, 570 /* .type = */ KEY_ECDSA_CERT, 571 /* .nid = */ NID_secp521r1, 572 /* .cert = */ 1, 573 /* .sigonly = */ 0, 574 /* .keybits = */ 0, 575 /* .funcs = */ &sshkey_ecdsa_funcs, 576 }; 577