1 /* $NetBSD: auth2-pubkey.c,v 1.4 2010/11/21 18:59:04 adam Exp $ */ 2 /* $OpenBSD: auth2-pubkey.c,v 1.26 2010/06/29 23:16:46 djm Exp $ */ 3 /* 4 * Copyright (c) 2000 Markus Friedl. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include "includes.h" 28 __RCSID("$NetBSD: auth2-pubkey.c,v 1.4 2010/11/21 18:59:04 adam Exp $"); 29 #include <sys/types.h> 30 #include <sys/stat.h> 31 32 #include <fcntl.h> 33 #include <pwd.h> 34 #include <stdio.h> 35 #include <stdarg.h> 36 #include <string.h> 37 #include <time.h> 38 #include <unistd.h> 39 40 #include "xmalloc.h" 41 #include "ssh.h" 42 #include "ssh2.h" 43 #include "packet.h" 44 #include "buffer.h" 45 #include "log.h" 46 #include "servconf.h" 47 #include "compat.h" 48 #include "key.h" 49 #include "hostfile.h" 50 #include "auth.h" 51 #include "pathnames.h" 52 #include "uidswap.h" 53 #include "auth-options.h" 54 #include "canohost.h" 55 #ifdef GSSAPI 56 #include "ssh-gss.h" 57 #endif 58 #include "monitor_wrap.h" 59 #include "misc.h" 60 #include "authfile.h" 61 #include "match.h" 62 63 #ifdef WITH_LDAP_PUBKEY 64 #include "ldapauth.h" 65 #endif 66 67 /* import */ 68 extern ServerOptions options; 69 extern u_char *session_id2; 70 extern u_int session_id2_len; 71 72 static int 73 userauth_pubkey(Authctxt *authctxt) 74 { 75 Buffer b; 76 Key *key = NULL; 77 char *pkalg; 78 u_char *pkblob, *sig; 79 u_int alen, blen, slen; 80 int have_sig, pktype; 81 int authenticated = 0; 82 83 if (!authctxt->valid) { 84 debug2("userauth_pubkey: disabled because of invalid user"); 85 return 0; 86 } 87 have_sig = packet_get_char(); 88 if (datafellows & SSH_BUG_PKAUTH) { 89 debug2("userauth_pubkey: SSH_BUG_PKAUTH"); 90 /* no explicit pkalg given */ 91 pkblob = packet_get_string(&blen); 92 buffer_init(&b); 93 buffer_append(&b, pkblob, blen); 94 /* so we have to extract the pkalg from the pkblob */ 95 pkalg = buffer_get_string(&b, &alen); 96 buffer_free(&b); 97 } else { 98 pkalg = packet_get_string(&alen); 99 pkblob = packet_get_string(&blen); 100 } 101 pktype = key_type_from_name(pkalg); 102 if (pktype == KEY_UNSPEC) { 103 /* this is perfectly legal */ 104 logit("userauth_pubkey: unsupported public key algorithm: %s", 105 pkalg); 106 goto done; 107 } 108 key = key_from_blob(pkblob, blen); 109 if (key == NULL) { 110 error("userauth_pubkey: cannot decode key: %s", pkalg); 111 goto done; 112 } 113 if (key->type != pktype) { 114 error("userauth_pubkey: type mismatch for decoded key " 115 "(received %d, expected %d)", key->type, pktype); 116 goto done; 117 } 118 if (have_sig) { 119 sig = packet_get_string(&slen); 120 packet_check_eom(); 121 buffer_init(&b); 122 if (datafellows & SSH_OLD_SESSIONID) { 123 buffer_append(&b, session_id2, session_id2_len); 124 } else { 125 buffer_put_string(&b, session_id2, session_id2_len); 126 } 127 /* reconstruct packet */ 128 buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST); 129 buffer_put_cstring(&b, authctxt->user); 130 buffer_put_cstring(&b, 131 datafellows & SSH_BUG_PKSERVICE ? 132 "ssh-userauth" : 133 authctxt->service); 134 if (datafellows & SSH_BUG_PKAUTH) { 135 buffer_put_char(&b, have_sig); 136 } else { 137 buffer_put_cstring(&b, "publickey"); 138 buffer_put_char(&b, have_sig); 139 buffer_put_cstring(&b, pkalg); 140 } 141 buffer_put_string(&b, pkblob, blen); 142 #ifdef DEBUG_PK 143 buffer_dump(&b); 144 #endif 145 /* test for correct signature */ 146 authenticated = 0; 147 if (PRIVSEP(user_key_allowed(authctxt->pw, key)) && 148 PRIVSEP(key_verify(key, sig, slen, buffer_ptr(&b), 149 buffer_len(&b))) == 1) 150 authenticated = 1; 151 buffer_free(&b); 152 xfree(sig); 153 } else { 154 debug("test whether pkalg/pkblob are acceptable"); 155 packet_check_eom(); 156 157 /* XXX fake reply and always send PK_OK ? */ 158 /* 159 * XXX this allows testing whether a user is allowed 160 * to login: if you happen to have a valid pubkey this 161 * message is sent. the message is NEVER sent at all 162 * if a user is not allowed to login. is this an 163 * issue? -markus 164 */ 165 if (PRIVSEP(user_key_allowed(authctxt->pw, key))) { 166 packet_start(SSH2_MSG_USERAUTH_PK_OK); 167 packet_put_string(pkalg, alen); 168 packet_put_string(pkblob, blen); 169 packet_send(); 170 packet_write_wait(); 171 authctxt->postponed = 1; 172 } 173 } 174 if (authenticated != 1) 175 auth_clear_options(); 176 done: 177 debug2("userauth_pubkey: authenticated %d pkalg %s", authenticated, pkalg); 178 if (key != NULL) 179 key_free(key); 180 xfree(pkalg); 181 xfree(pkblob); 182 return authenticated; 183 } 184 185 static int 186 match_principals_option(const char *principal_list, struct KeyCert *cert) 187 { 188 char *result; 189 u_int i; 190 191 /* XXX percent_expand() sequences for authorized_principals? */ 192 193 for (i = 0; i < cert->nprincipals; i++) { 194 if ((result = match_list(cert->principals[i], 195 principal_list, NULL)) != NULL) { 196 debug3("matched principal from key options \"%.100s\"", 197 result); 198 xfree(result); 199 return 1; 200 } 201 } 202 return 0; 203 } 204 205 static int 206 match_principals_file(char *file, struct passwd *pw, struct KeyCert *cert) 207 { 208 FILE *f; 209 char line[SSH_MAX_PUBKEY_BYTES], *cp, *ep, *line_opts; 210 u_long linenum = 0; 211 u_int i; 212 213 temporarily_use_uid(pw); 214 debug("trying authorized principals file %s", file); 215 if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) { 216 restore_uid(); 217 return 0; 218 } 219 while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) { 220 /* Skip leading whitespace. */ 221 for (cp = line; *cp == ' ' || *cp == '\t'; cp++) 222 ; 223 /* Skip blank and comment lines. */ 224 if ((ep = strchr(cp, '#')) != NULL) 225 *ep = '\0'; 226 if (!*cp || *cp == '\n') 227 continue; 228 /* Trim trailing whitespace. */ 229 ep = cp + strlen(cp) - 1; 230 while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t')) 231 *ep-- = '\0'; 232 /* 233 * If the line has internal whitespace then assume it has 234 * key options. 235 */ 236 line_opts = NULL; 237 if ((ep = strrchr(cp, ' ')) != NULL || 238 (ep = strrchr(cp, '\t')) != NULL) { 239 for (; *ep == ' ' || *ep == '\t'; ep++) 240 ;; 241 line_opts = cp; 242 cp = ep; 243 } 244 for (i = 0; i < cert->nprincipals; i++) { 245 if (strcmp(cp, cert->principals[i]) == 0) { 246 debug3("matched principal from file \"%.100s\"", 247 cert->principals[i]); 248 if (auth_parse_options(pw, line_opts, 249 file, linenum) != 1) 250 continue; 251 fclose(f); 252 restore_uid(); 253 return 1; 254 } 255 } 256 } 257 fclose(f); 258 restore_uid(); 259 return 0; 260 } 261 262 /* return 1 if user allows given key */ 263 static int 264 user_key_allowed2(struct passwd *pw, Key *key, char *file) 265 { 266 char line[SSH_MAX_PUBKEY_BYTES]; 267 const char *reason; 268 int found_key = 0; 269 FILE *f; 270 u_long linenum = 0; 271 Key *found; 272 char *fp; 273 #ifdef WITH_LDAP_PUBKEY 274 ldap_key_t * k; 275 unsigned int i = 0; 276 #endif 277 278 /* Temporarily use the user's uid. */ 279 temporarily_use_uid(pw); 280 281 #ifdef WITH_LDAP_PUBKEY 282 found_key = 0; 283 /* allocate a new key type */ 284 found = key_new(key->type); 285 286 /* first check if the options is enabled, then try.. */ 287 if (options.lpk.on) { 288 debug("[LDAP] trying LDAP first uid=%s",pw->pw_name); 289 if (ldap_ismember(&options.lpk, pw->pw_name) > 0) { 290 if ((k = ldap_getuserkey(&options.lpk, pw->pw_name)) != NULL) { 291 /* Skip leading whitespace, empty and comment lines. */ 292 for (i = 0 ; i < k->num ; i++) { 293 /* dont forget if multiple keys to reset options */ 294 char *cp, *options = NULL; 295 296 for (cp = (char *)k->keys[i]->bv_val; *cp == ' ' || *cp == '\t'; cp++) 297 ; 298 if (!*cp || *cp == '\n' || *cp == '#') 299 continue; 300 301 if (key_read(found, &cp) != 1) { 302 /* no key? check if there are options for this key */ 303 int quoted = 0; 304 debug2("[LDAP] user_key_allowed: check options: '%s'", cp); 305 options = cp; 306 for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) { 307 if (*cp == '\\' && cp[1] == '"') 308 cp++; /* Skip both */ 309 else if (*cp == '"') 310 quoted = !quoted; 311 } 312 /* Skip remaining whitespace. */ 313 for (; *cp == ' ' || *cp == '\t'; cp++) 314 ; 315 if (key_read(found, &cp) != 1) { 316 debug2("[LDAP] user_key_allowed: advance: '%s'", cp); 317 /* still no key? advance to next line*/ 318 continue; 319 } 320 } 321 322 if (key_equal(found, key) && 323 auth_parse_options(pw, options, file, linenum) == 1) { 324 found_key = 1; 325 debug("[LDAP] matching key found"); 326 fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX); 327 verbose("[LDAP] Found matching %s key: %s", key_type(found), fp); 328 329 /* restoring memory */ 330 ldap_keys_free(k); 331 xfree(fp); 332 restore_uid(); 333 key_free(found); 334 return found_key; 335 break; 336 } 337 }/* end of LDAP for() */ 338 } else { 339 logit("[LDAP] no keys found for '%s'!", pw->pw_name); 340 } 341 } else { 342 logit("[LDAP] '%s' is not in '%s'", pw->pw_name, options.lpk.sgroup); 343 } 344 } 345 #endif 346 debug("trying public key file %s", file); 347 f = auth_openkeyfile(file, pw, options.strict_modes); 348 349 if (!f) { 350 restore_uid(); 351 return 0; 352 } 353 354 found_key = 0; 355 found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type); 356 357 while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) { 358 char *cp, *key_options = NULL; 359 360 auth_clear_options(); 361 362 /* Skip leading whitespace, empty and comment lines. */ 363 for (cp = line; *cp == ' ' || *cp == '\t'; cp++) 364 ; 365 if (!*cp || *cp == '\n' || *cp == '#') 366 continue; 367 368 if (key_read(found, &cp) != 1) { 369 /* no key? check if there are options for this key */ 370 int quoted = 0; 371 debug2("user_key_allowed: check options: '%s'", cp); 372 key_options = cp; 373 for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) { 374 if (*cp == '\\' && cp[1] == '"') 375 cp++; /* Skip both */ 376 else if (*cp == '"') 377 quoted = !quoted; 378 } 379 /* Skip remaining whitespace. */ 380 for (; *cp == ' ' || *cp == '\t'; cp++) 381 ; 382 if (key_read(found, &cp) != 1) { 383 debug2("user_key_allowed: advance: '%s'", cp); 384 /* still no key? advance to next line*/ 385 continue; 386 } 387 } 388 if (key_is_cert(key)) { 389 if (!key_equal(found, key->cert->signature_key)) 390 continue; 391 if (auth_parse_options(pw, key_options, file, 392 linenum) != 1) 393 continue; 394 if (!key_is_cert_authority) 395 continue; 396 fp = key_fingerprint(found, SSH_FP_MD5, 397 SSH_FP_HEX); 398 debug("matching CA found: file %s, line %lu, %s %s", 399 file, linenum, key_type(found), fp); 400 /* 401 * If the user has specified a list of principals as 402 * a key option, then prefer that list to matching 403 * their username in the certificate principals list. 404 */ 405 if (authorized_principals != NULL && 406 !match_principals_option(authorized_principals, 407 key->cert)) { 408 reason = "Certificate does not contain an " 409 "authorized principal"; 410 fail_reason: 411 xfree(fp); 412 error("%s", reason); 413 auth_debug_add("%s", reason); 414 continue; 415 } 416 if (key_cert_check_authority(key, 0, 0, 417 authorized_principals == NULL ? pw->pw_name : NULL, 418 &reason) != 0) 419 goto fail_reason; 420 if (auth_cert_options(key, pw) != 0) { 421 xfree(fp); 422 continue; 423 } 424 verbose("Accepted certificate ID \"%s\" " 425 "signed by %s CA %s via %s", key->cert->key_id, 426 key_type(found), fp, file); 427 xfree(fp); 428 found_key = 1; 429 break; 430 } else if (key_equal(found, key)) { 431 if (auth_parse_options(pw, key_options, file, 432 linenum) != 1) 433 continue; 434 if (key_is_cert_authority) 435 continue; 436 found_key = 1; 437 debug("matching key found: file %s, line %lu", 438 file, linenum); 439 fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX); 440 verbose("Found matching %s key: %s", 441 key_type(found), fp); 442 xfree(fp); 443 break; 444 } 445 } 446 restore_uid(); 447 fclose(f); 448 key_free(found); 449 if (!found_key) 450 debug2("key not found"); 451 return found_key; 452 } 453 454 /* Authenticate a certificate key against TrustedUserCAKeys */ 455 static int 456 user_cert_trusted_ca(struct passwd *pw, Key *key) 457 { 458 char *ca_fp, *principals_file = NULL; 459 const char *reason; 460 int ret = 0; 461 462 if (!key_is_cert(key) || options.trusted_user_ca_keys == NULL) 463 return 0; 464 465 ca_fp = key_fingerprint(key->cert->signature_key, 466 SSH_FP_MD5, SSH_FP_HEX); 467 468 if (key_in_file(key->cert->signature_key, 469 options.trusted_user_ca_keys, 1) != 1) { 470 debug2("%s: CA %s %s is not listed in %s", __func__, 471 key_type(key->cert->signature_key), ca_fp, 472 options.trusted_user_ca_keys); 473 goto out; 474 } 475 /* 476 * If AuthorizedPrincipals is in use, then compare the certificate 477 * principals against the names in that file rather than matching 478 * against the username. 479 */ 480 if ((principals_file = authorized_principals_file(pw)) != NULL) { 481 if (!match_principals_file(principals_file, pw, key->cert)) { 482 reason = "Certificate does not contain an " 483 "authorized principal"; 484 fail_reason: 485 error("%s", reason); 486 auth_debug_add("%s", reason); 487 goto out; 488 } 489 } 490 if (key_cert_check_authority(key, 0, 1, 491 principals_file == NULL ? pw->pw_name : NULL, &reason) != 0) 492 goto fail_reason; 493 if (auth_cert_options(key, pw) != 0) 494 goto out; 495 496 verbose("Accepted certificate ID \"%s\" signed by %s CA %s via %s", 497 key->cert->key_id, key_type(key->cert->signature_key), ca_fp, 498 options.trusted_user_ca_keys); 499 ret = 1; 500 501 out: 502 if (principals_file != NULL) 503 xfree(principals_file); 504 if (ca_fp != NULL) 505 xfree(ca_fp); 506 return ret; 507 } 508 509 /* check whether given key is in .ssh/authorized_keys* */ 510 int 511 user_key_allowed(struct passwd *pw, Key *key) 512 { 513 int success; 514 char *file; 515 516 if (auth_key_is_revoked(key)) 517 return 0; 518 if (key_is_cert(key) && auth_key_is_revoked(key->cert->signature_key)) 519 return 0; 520 521 success = user_cert_trusted_ca(pw, key); 522 if (success) 523 return success; 524 525 file = authorized_keys_file(pw); 526 success = user_key_allowed2(pw, key, file); 527 xfree(file); 528 if (success) 529 return success; 530 531 /* try suffix "2" for backward compat, too */ 532 file = authorized_keys_file2(pw); 533 success = user_key_allowed2(pw, key, file); 534 xfree(file); 535 return success; 536 } 537 538 Authmethod method_pubkey = { 539 "publickey", 540 userauth_pubkey, 541 &options.pubkey_authentication 542 }; 543