xref: /netbsd-src/crypto/external/bsd/heimdal/dist/kdc/kx509.c (revision afab4e300d3a9fb07dd8c80daf53d0feb3345706)
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 
47 krb5_error_code
_kdc_try_kx509_request(void * ptr,size_t len,struct Kx509Request * req,size_t * size)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
verify_req_hash(krb5_context context,const Kx509Request * req,krb5_keyblock * key)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
calculate_reply_hash(krb5_context context,krb5_keyblock * key,Kx509Response * rep)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
build_certificate(krb5_context context,krb5_kdc_configuration * config,const krb5_data * key,time_t endtime,krb5_principal principal,krb5_data * certificate)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
kdc_kx509_verify_service_principal(krb5_context context,const char * cname,krb5_principal sprincipal)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
_kdc_do_kx509(krb5_context context,krb5_kdc_configuration * config,const struct Kx509Request * req,krb5_data * reply,const char * from,struct sockaddr * addr)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