1/* $NetBSD: kx509.c,v 1.4 2023/06/19 21:41:42 christos Exp $ */ 2 3/* 4 * Copyright (c) 2006 - 2007 Kungliga Tekniska H��gskolan 5 * (Royal Institute of Technology, Stockholm, Sweden). 6 * 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 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36#include "kdc_locl.h" 37#include <krb5/hex.h> 38#include <krb5/rfc2459_asn1.h> 39#include <krb5/hx509.h> 40 41#ifdef KX509 42 43/* 44 * 45 */ 46 47krb5_error_code 48_kdc_try_kx509_request(void *ptr, size_t len, struct Kx509Request *req, size_t *size) 49{ 50 if (len < 4) 51 return -1; 52 if (memcmp("\x00\x00\x02\x00", ptr, 4) != 0) 53 return -1; 54 return decode_Kx509Request(((unsigned char *)ptr) + 4, len - 4, req, size); 55} 56 57/* 58 * 59 */ 60 61static const unsigned char version_2_0[4] = {0 , 0, 2, 0}; 62 63static krb5_error_code 64verify_req_hash(krb5_context context, 65 const Kx509Request *req, 66 krb5_keyblock *key) 67{ 68 unsigned char digest[SHA_DIGEST_LENGTH]; 69 HMAC_CTX *ctx; 70 71 if (req->pk_hash.length != sizeof(digest)) { 72 krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED, 73 "pk-hash have wrong length: %lu", 74 (unsigned long)req->pk_hash.length); 75 return KRB5KDC_ERR_PREAUTH_FAILED; 76 } 77 78#if OPENSSL_VERSION_NUMBER < 0x10100000UL 79 HMAC_CTX ctxs; 80 ctx = &ctxs; 81 HMAC_CTX_init(ctx); 82#else 83 ctx = HMAC_CTX_new(); 84#endif 85 HMAC_Init_ex(ctx, 86 key->keyvalue.data, key->keyvalue.length, 87 EVP_sha1(), NULL); 88 if (sizeof(digest) != HMAC_size(ctx)) 89 krb5_abortx(context, "runtime error, hmac buffer wrong size in kx509"); 90 HMAC_Update(ctx, version_2_0, sizeof(version_2_0)); 91 HMAC_Update(ctx, req->pk_key.data, req->pk_key.length); 92 HMAC_Final(ctx, digest, 0); 93#if OPENSSL_VERSION_NUMBER < 0x10100000UL 94 HMAC_CTX_cleanup(ctx); 95#else 96 HMAC_CTX_free(ctx); 97#endif 98 99 if (memcmp(req->pk_hash.data, digest, sizeof(digest)) != 0) { 100 krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED, 101 "pk-hash is not correct"); 102 return KRB5KDC_ERR_PREAUTH_FAILED; 103 } 104 return 0; 105} 106 107static krb5_error_code 108calculate_reply_hash(krb5_context context, 109 krb5_keyblock *key, 110 Kx509Response *rep) 111{ 112 krb5_error_code ret; 113 HMAC_CTX *ctx; 114 115#if OPENSSL_VERSION_NUMBER < 0x10100000UL 116 HMAC_CTX ctxs; 117 ctx = &ctxs; 118 HMAC_CTX_init(ctx); 119#else 120 ctx = HMAC_CTX_new(); 121#endif 122 123 HMAC_Init_ex(ctx, key->keyvalue.data, key->keyvalue.length, 124 EVP_sha1(), NULL); 125 ret = krb5_data_alloc(rep->hash, HMAC_size(ctx)); 126 if (ret) { 127#if OPENSSL_VERSION_NUMBER < 0x10100000UL 128 HMAC_CTX_cleanup(ctx); 129#else 130 HMAC_CTX_free(ctx); 131#endif 132 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 133 return ENOMEM; 134 } 135 136 HMAC_Update(ctx, version_2_0, sizeof(version_2_0)); 137 if (rep->error_code) { 138 int32_t t = *rep->error_code; 139 do { 140 unsigned char p = (t & 0xff); 141 HMAC_Update(ctx, &p, 1); 142 t >>= 8; 143 } while (t); 144 } 145 if (rep->certificate) 146 HMAC_Update(ctx, rep->certificate->data, rep->certificate->length); 147 if (rep->e_text) 148 HMAC_Update(ctx, (unsigned char *)*rep->e_text, strlen(*rep->e_text)); 149 150 HMAC_Final(ctx, rep->hash->data, 0); 151#if OPENSSL_VERSION_NUMBER < 0x10100000UL 152 HMAC_CTX_cleanup(ctx); 153#else 154 HMAC_CTX_free(ctx); 155#endif 156 157 return 0; 158} 159 160/* 161 * Build a certifate for `principal�� that will expire at `endtime��. 162 */ 163 164static krb5_error_code 165build_certificate(krb5_context context, 166 krb5_kdc_configuration *config, 167 const krb5_data *key, 168 time_t endtime, 169 krb5_principal principal, 170 krb5_data *certificate) 171{ 172 char *name = NULL; 173 const char *kx509_ca; 174 hx509_ca_tbs tbs = NULL; 175 hx509_env env = NULL; 176 hx509_cert cert = NULL; 177 hx509_cert signer = NULL; 178 krb5_boolean def_bool; 179 int ret; 180 181 ret = krb5_unparse_name_flags(context, principal, 182 KRB5_PRINCIPAL_UNPARSE_NO_REALM, 183 &name); 184 if (ret) 185 goto out; 186 187 ret = hx509_env_add(context->hx509ctx, &env, "principal-name-without-realm", 188 name); 189 krb5_xfree(name); 190 name = NULL; 191 if (ret) 192 goto out; 193 194 /* 195 * Include the realm in the principal-name env var; the template 196 * might not use $principal-name-realm after all. 197 */ 198 ret = krb5_unparse_name(context, principal, &name); 199 if (ret) 200 goto out; 201 202 ret = hx509_env_add(context->hx509ctx, &env, "principal-name", 203 name); 204 if (ret) 205 goto out; 206 207 ret = hx509_env_add(context->hx509ctx, &env, "principal-name-realm", 208 krb5_principal_get_realm(context, principal)); 209 if (ret) 210 goto out; 211 212 /* Pick an issuer based on the crealm if we can */ 213 kx509_ca = krb5_config_get_string(context, NULL, "kdc", 214 krb5_principal_get_realm(context, 215 principal), 216 "kx509_ca", NULL); 217 if (kx509_ca == NULL) 218 kx509_ca = config->kx509_ca; 219 220 { 221 hx509_certs certs; 222 hx509_query *q; 223 224 ret = hx509_certs_init(context->hx509ctx, config->kx509_ca, 0, 225 NULL, &certs); 226 if (ret) { 227 kdc_log(context, config, 0, "Failed to load CA %s", 228 config->kx509_ca); 229 goto out; 230 } 231 ret = hx509_query_alloc(context->hx509ctx, &q); 232 if (ret) { 233 hx509_certs_free(&certs); 234 goto out; 235 } 236 237 hx509_query_match_option(q, HX509_QUERY_OPTION_PRIVATE_KEY); 238 hx509_query_match_option(q, HX509_QUERY_OPTION_KU_KEYCERTSIGN); 239 240 ret = hx509_certs_find(context->hx509ctx, certs, q, &signer); 241 hx509_query_free(context->hx509ctx, q); 242 hx509_certs_free(&certs); 243 if (ret) { 244 kdc_log(context, config, 0, "Failed to find a CA in %s", 245 config->kx509_ca); 246 goto out; 247 } 248 } 249 250 ret = hx509_ca_tbs_init(context->hx509ctx, &tbs); 251 if (ret) 252 goto out; 253 254 { 255 SubjectPublicKeyInfo spki; 256 heim_any any; 257 258 memset(&spki, 0, sizeof(spki)); 259 260 spki.subjectPublicKey.data = key->data; 261 spki.subjectPublicKey.length = key->length * 8; 262 263 ret = der_copy_oid(&asn1_oid_id_pkcs1_rsaEncryption, 264 &spki.algorithm.algorithm); 265 266 any.data = "\x05\x00"; 267 any.length = 2; 268 spki.algorithm.parameters = &any; 269 270 ret = hx509_ca_tbs_set_spki(context->hx509ctx, tbs, &spki); 271 der_free_oid(&spki.algorithm.algorithm); 272 if (ret) 273 goto out; 274 } 275 276 { 277 hx509_certs certs; 278 hx509_cert template; 279 280 ret = hx509_certs_init(context->hx509ctx, config->kx509_template, 0, 281 NULL, &certs); 282 if (ret) { 283 kdc_log(context, config, 0, "Failed to load template %s", 284 config->kx509_template); 285 goto out; 286 } 287 ret = hx509_get_one_cert(context->hx509ctx, certs, &template); 288 hx509_certs_free(&certs); 289 if (ret) { 290 kdc_log(context, config, 0, "Failed to find template in %s", 291 config->kx509_template); 292 goto out; 293 } 294 ret = hx509_ca_tbs_set_template(context->hx509ctx, tbs, 295 HX509_CA_TEMPLATE_SUBJECT| 296 HX509_CA_TEMPLATE_KU| 297 HX509_CA_TEMPLATE_EKU, 298 template); 299 hx509_cert_free(template); 300 if (ret) 301 goto out; 302 } 303 304 def_bool = krb5_config_get_bool_default(context, NULL, TRUE, "kdc", 305 "kx509_include_pkinit_san", 306 NULL); 307 if (krb5_config_get_bool_default(context, NULL, def_bool, "kdc", 308 krb5_principal_get_realm(context, 309 principal), 310 "kx509_include_pkinit_san", 311 NULL)) { 312 ret = hx509_ca_tbs_add_san_pkinit(context->hx509ctx, tbs, name); 313 if (ret) 314 goto out; 315 } 316 317 hx509_ca_tbs_set_notAfter(context->hx509ctx, tbs, endtime); 318 319 hx509_ca_tbs_subject_expand(context->hx509ctx, tbs, env); 320 hx509_env_free(&env); 321 322 ret = hx509_ca_sign(context->hx509ctx, tbs, signer, &cert); 323 hx509_cert_free(signer); 324 if (ret) 325 goto out; 326 327 hx509_ca_tbs_free(&tbs); 328 329 ret = hx509_cert_binary(context->hx509ctx, cert, certificate); 330 hx509_cert_free(cert); 331 if (ret) 332 goto out; 333 334 /* cleanup on success */ 335 krb5_xfree(name); 336 337 return 0; 338out: 339 if (name) 340 krb5_xfree(name); 341 if (env) 342 hx509_env_free(&env); 343 if (tbs) 344 hx509_ca_tbs_free(&tbs); 345 if (signer) 346 hx509_cert_free(signer); 347 krb5_set_error_message(context, ret, "cert creation failed"); 348 return ret; 349} 350 351krb5_error_code 352kdc_kx509_verify_service_principal(krb5_context context, 353 const char *cname, 354 krb5_principal sprincipal) 355{ 356 krb5_error_code ret, aret; 357 krb5_boolean bret; 358 krb5_principal principal = NULL; 359 char *expected = NULL; 360 char localhost[MAXHOSTNAMELEN]; 361 362 ret = gethostname(localhost, sizeof(localhost) - 1); 363 if (ret != 0) { 364 ret = errno; 365 krb5_set_error_message(context, ret, 366 N_("Failed to get local hostname", "")); 367 return ret; 368 } 369 localhost[sizeof(localhost) - 1] = '\0'; 370 371 ret = krb5_make_principal(context, &principal, "", "kca_service", 372 localhost, NULL); 373 if (ret) 374 goto out; 375 376 bret = krb5_principal_compare_any_realm(context, sprincipal, principal); 377 if (bret == TRUE) 378 goto out; /* found a match */ 379 380 ret = KRB5KDC_ERR_SERVER_NOMATCH; 381 382 aret = krb5_unparse_name(context, sprincipal, &expected); 383 if (aret) 384 goto out; 385 386 krb5_set_error_message(context, ret, 387 "User %s used wrong Kx509 service " 388 "principal, expected: %s", 389 cname, expected); 390 391 out: 392 krb5_xfree(expected); 393 krb5_free_principal(context, principal); 394 395 return ret; 396} 397 398/* 399 * 400 */ 401 402krb5_error_code 403_kdc_do_kx509(krb5_context context, 404 krb5_kdc_configuration *config, 405 const struct Kx509Request *req, krb5_data *reply, 406 const char *from, struct sockaddr *addr) 407{ 408 krb5_error_code ret; 409 krb5_ticket *ticket = NULL; 410 krb5_flags ap_req_options; 411 krb5_auth_context ac = NULL; 412 krb5_keytab id = NULL; 413 krb5_principal sprincipal = NULL, cprincipal = NULL; 414 char *cname = NULL; 415 Kx509Response rep; 416 size_t size; 417 krb5_keyblock *key = NULL; 418 krb5_boolean def_bool; 419 420 krb5_data_zero(reply); 421 memset(&rep, 0, sizeof(rep)); 422 423 if(!config->enable_kx509) { 424 kdc_log(context, config, 0, 425 "Rejected kx509 request (disabled) from %s", from); 426 return KRB5KDC_ERR_POLICY; 427 } 428 429 kdc_log(context, config, 0, "Kx509 request from %s", from); 430 431 ret = krb5_kt_resolve(context, "HDBGET:", &id); 432 if (ret) { 433 kdc_log(context, config, 0, "Can't open database for digest"); 434 goto out; 435 } 436 437 ret = krb5_rd_req(context, 438 &ac, 439 &req->authenticator, 440 NULL, 441 id, 442 &ap_req_options, 443 &ticket); 444 if (ret) 445 goto out; 446 447 ret = krb5_ticket_get_client(context, ticket, &cprincipal); 448 if (ret) 449 goto out; 450 451 def_bool = krb5_config_get_bool_default(context, NULL, TRUE, "kdc", 452 "require_initial_kca_tickets", 453 NULL); 454 if (!ticket->ticket.flags.initial && 455 krb5_config_get_bool_default(context, NULL, def_bool, "kdc", 456 krb5_principal_get_realm(context, 457 cprincipal), 458 "require_initial_kca_tickets", NULL)) { 459 ret = KRB5KDC_ERR_POLICY; 460 goto out; 461 } 462 463 ret = krb5_unparse_name(context, cprincipal, &cname); 464 if (ret) 465 goto out; 466 467 ret = krb5_ticket_get_server(context, ticket, &sprincipal); 468 if (ret) 469 goto out; 470 471 ret = kdc_kx509_verify_service_principal(context, cname, sprincipal); 472 if (ret) 473 goto out; 474 475 ret = krb5_auth_con_getkey(context, ac, &key); 476 if (ret == 0 && key == NULL) 477 ret = KRB5KDC_ERR_NULL_KEY; 478 if (ret) { 479 krb5_set_error_message(context, ret, "Kx509 can't get session key"); 480 goto out; 481 } 482 483 ret = verify_req_hash(context, req, key); 484 if (ret) 485 goto out; 486 487 /* Verify that the key is encoded RSA key */ 488 { 489 RSAPublicKey rsapkey; 490 size_t rsapkeysize; 491 492 ret = decode_RSAPublicKey(req->pk_key.data, req->pk_key.length, 493 &rsapkey, &rsapkeysize); 494 if (ret) 495 goto out; 496 free_RSAPublicKey(&rsapkey); 497 if (rsapkeysize != req->pk_key.length) { 498 ret = ASN1_EXTRA_DATA; 499 goto out; 500 } 501 } 502 503 ALLOC(rep.certificate); 504 if (rep.certificate == NULL) 505 goto out; 506 krb5_data_zero(rep.certificate); 507 ALLOC(rep.hash); 508 if (rep.hash == NULL) 509 goto out; 510 krb5_data_zero(rep.hash); 511 512 ret = build_certificate(context, config, &req->pk_key, 513 krb5_ticket_get_endtime(context, ticket), 514 cprincipal, rep.certificate); 515 if (ret) 516 goto out; 517 518 ret = calculate_reply_hash(context, key, &rep); 519 if (ret) 520 goto out; 521 522 /* 523 * Encode reply, [ version | Kx509Response ] 524 */ 525 526 { 527 krb5_data data; 528 529 ASN1_MALLOC_ENCODE(Kx509Response, data.data, data.length, &rep, 530 &size, ret); 531 if (ret) { 532 krb5_set_error_message(context, ret, "Failed to encode kx509 reply"); 533 goto out; 534 } 535 if (size != data.length) 536 krb5_abortx(context, "ASN1 internal error"); 537 538 ret = krb5_data_alloc(reply, data.length + sizeof(version_2_0)); 539 if (ret) { 540 free(data.data); 541 goto out; 542 } 543 memcpy(reply->data, version_2_0, sizeof(version_2_0)); 544 memcpy(((unsigned char *)reply->data) + sizeof(version_2_0), 545 data.data, data.length); 546 free(data.data); 547 } 548 549 kdc_log(context, config, 0, "Successful Kx509 request for %s", cname); 550 551out: 552 if (ac) 553 krb5_auth_con_free(context, ac); 554 if (ret) 555 krb5_warn(context, ret, "Kx509 request from %s failed", from); 556 if (ticket) 557 krb5_free_ticket(context, ticket); 558 if (id) 559 krb5_kt_close(context, id); 560 if (sprincipal) 561 krb5_free_principal(context, sprincipal); 562 if (cprincipal) 563 krb5_free_principal(context, cprincipal); 564 if (key) 565 krb5_free_keyblock (context, key); 566 if (cname) 567 free(cname); 568 free_Kx509Response(&rep); 569 570 return 0; 571} 572 573#endif /* KX509 */ 574