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