Home | History | Annotate | Line # | Download | only in kdc
      1 /*	$NetBSD: kx509.c,v 1.4 2023/06/19 21:41:42 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2006 - 2007 Kungliga Tekniska Hgskolan
      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 
     47 krb5_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 
     61 static const unsigned char version_2_0[4] = {0 , 0, 2, 0};
     62 
     63 static krb5_error_code
     64 verify_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 
    107 static krb5_error_code
    108 calculate_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 
    164 static krb5_error_code
    165 build_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;
    338 out:
    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 
    351 krb5_error_code
    352 kdc_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 
    402 krb5_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 
    551 out:
    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