Coverage Report

Created: 2026-09-03 07:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/third_party/heimdal/lib/krb5/kx509.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2019 Kungliga Tekniska Högskolan
3
 * (Royal Institute of Technology, Stockholm, Sweden).
4
 * 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
 *
10
 * 1. Redistributions of source code must retain the above copyright
11
 *    notice, this list of conditions and the following disclaimer.
12
 *
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in the
15
 *    documentation and/or other materials provided with the distribution.
16
 *
17
 * 3. Neither the name of the Institute nor the names of its contributors
18
 *    may be used to endorse or promote products derived from this software
19
 *    without specific prior written permission.
20
 *
21
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31
 * SUCH DAMAGE.
32
 */
33
34
#include "krb5_locl.h"
35
#include <kx509_asn1.h>
36
#include <kx509_err.h>
37
#include "../hx509/hx_locl.h" /* XXX find a better way */
38
#include "hx509-private.h"
39
40
/*
41
 * This file implements a client for the kx509 protocol -- a Kerberized online
42
 * CA that can issue a Certificate to a client that authenticates using
43
 * Kerberos.
44
 *
45
 * The kx509 protocol is the inverse of PKINIT.  Whereas PKINIT allows users
46
 * with PKIX credentials to acquire Kerberos credentials, the kx509 protocol
47
 * allows users with Kerberos credentials to acquire PKIX credentials.
48
 *
49
 * I.e., kx509 is a bridge, just like PKINIT.
50
 *
51
 * The kx509 protocol is very simple, and very limited.
52
 *
53
 * A request consists of a DER-encoded Kx509Request message prefixed with four
54
 * bytes identifying the protocol (see `version_2_0' below).
55
 *
56
 * A Kx509Request message contains an AP-REQ, a public key, and an HMAC of the
57
 * public key made with the session key of the AP-REQ's ticket.
58
 *
59
 * The service principal can be either kca_service/hostname.fqdn or
60
 * krbtgt/REALM (a Heimdal innovation).
61
 *
62
 * If a request is missing a public key, then the request is a probe intended
63
 * to discover whether the service is enabled, thus helping the client avoid
64
 * a possibly-slow private key generation operation.
65
 *
66
 * The response is a DER-encoded Kx509Response also prefixed with
67
 * `version_2_0', and contains: an optional error code and error text, an
68
 * optional certificate (for the success case), and an optional HMAC of those
69
 * fields that is present when the service was able to verify the AP-REQ.
70
 *
71
 * Limitations:
72
 *
73
 *  - no proof of possession for the public key
74
 *  - only RSA keys are supported
75
 *  - no way to express options (e.g., what KUs, EKUs, or SANs are desired)
76
 *  - no sub-session key usage
77
 *  - no reflection protection other than the HMAC's forgery protection and the
78
 *    fact that the client could tell that a reflected attack isn't success
79
 *
80
 * Future directions:
81
 *
82
 *  - Since the public key field of the request is an OCTET STRING, we could
83
 *    send a CSR, or even an expired certificate (possibly self-signed,
84
 *    possibly one issued earlier) that can serve as a template.
85
 *
86
 *    This solves the first three limitations, as it allows the client to
87
 *    demonstrate proof of possession, allows arbitrary public key types, and
88
 *    allows the client to express desires about the to-be-issued certificate.
89
 *
90
 *  - Use the AP-REQ's Authenticator's sub-session key for the HMAC, and derive
91
 *    per-direction sub-sub-keys.
92
 *
93
 *  - We might design a new protocol that better fits the RFC4120 KDC message
94
 *    framework.
95
 */
96
97
static const unsigned char version_2_0[4] = {0 , 0, 2, 0};
98
99
struct krb5_kx509_req_ctx_data {
100
    krb5_auth_context   ac;
101
    krb5_data           given_csr;
102
    hx509_request       csr;
103
    Kx509CSRPlus        csr_plus;
104
    char                *realm;     /* Realm to which to send request */
105
    krb5_keyblock       *hmac_key;  /* For HMAC validation */
106
    hx509_private_key   *keys;
107
    hx509_private_key   priv_key;
108
    unsigned int        expect_chain;
109
};
110
111
/**
112
 * Create a kx509 request context.
113
 *
114
 * @param context The Kerberos library context
115
 * @param out Where to place the kx509 request context
116
 *
117
 * @return A krb5 error code.
118
 */
119
krb5_error_code
120
krb5_kx509_ctx_init(krb5_context context, krb5_kx509_req_ctx *out)
121
0
{
122
0
    krb5_kx509_req_ctx ctx;
123
0
    krb5_error_code ret;
124
0
    hx509_name name = NULL;
125
126
0
    ALLOC(ctx, 1);
127
0
    if (ctx == NULL)
128
0
        return krb5_enomem(context);
129
0
    ctx->given_csr.data = NULL;
130
0
    ctx->priv_key = NULL;
131
0
    ctx->hmac_key = NULL;
132
0
    ctx->realm = NULL;
133
0
    ctx->keys = NULL;
134
0
    ctx->csr = NULL;
135
0
    ret = hx509_request_init(context->hx509ctx, &ctx->csr);
136
0
    if (ret == 0)
137
0
        ret = hx509_parse_name(context->hx509ctx, "", &name);
138
0
    if (ret == 0)
139
0
        ret = hx509_request_set_name(context->hx509ctx, ctx->csr, name);
140
0
    if (ret == 0)
141
0
        ret = krb5_auth_con_init(context, &ctx->ac);
142
0
    if (name)
143
0
        hx509_name_free(&name);
144
0
    if (ret == 0)
145
0
        *out = ctx;
146
0
    else
147
0
        krb5_kx509_ctx_free(context, &ctx);
148
0
    return ret;
149
0
}
150
151
/**
152
 * Free a kx509 request context.
153
 *
154
 * @param context The Kerberos library context
155
 * @param ctxp Pointer to krb5 request context to free
156
 *
157
 * @return A krb5 error code.
158
 */
159
void
160
krb5_kx509_ctx_free(krb5_context context, krb5_kx509_req_ctx *ctxp)
161
0
{
162
0
    krb5_kx509_req_ctx ctx = *ctxp;
163
164
0
    *ctxp = NULL;
165
0
    if (ctx == NULL)
166
0
        return;
167
0
    krb5_free_keyblock(context, ctx->hmac_key);
168
0
    krb5_auth_con_free(context, ctx->ac);
169
0
    free_Kx509CSRPlus(&ctx->csr_plus);
170
0
    free(ctx->realm);
171
0
    hx509_request_free(&ctx->csr);
172
0
    krb5_data_free(&ctx->given_csr);
173
0
    hx509_private_key_free(&ctx->priv_key);
174
0
    _hx509_certs_keys_free(context->hx509ctx, ctx->keys);
175
0
    free(ctx);
176
0
}
177
178
/**
179
 * Set a realm to send kx509 request to, if different from the client's.
180
 *
181
 * @param context The Kerberos library context
182
 * @param ctx The kx509 request context
183
 * @param realm Realm name
184
 *
185
 * @return A krb5 error code.
186
 */
187
krb5_error_code
188
krb5_kx509_ctx_set_realm(krb5_context context,
189
                         krb5_kx509_req_ctx kx509_ctx,
190
                         const char *realm)
191
0
{
192
0
    return ((kx509_ctx->realm = strdup(realm)) == NULL) ?
193
0
        krb5_enomem(context) : 0;
194
0
}
195
196
/**
197
 * Sets a CSR for a kx509 request.
198
 *
199
 * Normally kx509 will generate a CSR (and even a private key for it)
200
 * automatically.  If a CSR is given then kx509 will use it instead of
201
 * generating one.
202
 *
203
 * @param context The Kerberos library context
204
 * @param ctx The kx509 request context
205
 * @param csr_der A DER-encoded PKCS#10 CSR
206
 *
207
 * @return A krb5 error code.
208
 */
209
krb5_error_code
210
krb5_kx509_ctx_set_csr_der(krb5_context context,
211
                           krb5_kx509_req_ctx ctx,
212
                           krb5_data *csr_der)
213
0
{
214
0
    krb5_data_free(&ctx->given_csr);
215
0
    return krb5_data_copy(&ctx->given_csr, csr_der->data, csr_der->length);
216
0
}
217
218
/**
219
 * Adds an EKU as an additional desired Certificate Extension or in the CSR if
220
 * the caller does not set a CSR.
221
 *
222
 * @param context The Kerberos library context
223
 * @param ctx The kx509 request context
224
 * @param oids A string representation of an OID
225
 *
226
 * @return A krb5 error code.
227
 */
228
krb5_error_code
229
krb5_kx509_ctx_add_eku(krb5_context context,
230
                       krb5_kx509_req_ctx kx509_ctx,
231
                       const char *oids)
232
0
{
233
0
    krb5_error_code ret;
234
0
    heim_oid oid;
235
236
0
    ret = der_parse_heim_oid(oids, NULL, &oid);
237
0
    if (ret == 0)
238
0
        hx509_request_add_eku(context->hx509ctx, kx509_ctx->csr, &oid);
239
0
    der_free_oid(&oid);
240
0
    return ret;
241
0
}
242
243
/**
244
 * Adds a dNSName SAN (domainname, hostname) as an additional desired
245
 * Certificate Extension or in the CSR if the caller does not set a CSR.
246
 *
247
 * @param context The Kerberos library context
248
 * @param ctx The kx509 request context
249
 * @param dname A string containing a DNS domainname
250
 *
251
 * @return A krb5 error code.
252
 */
253
krb5_error_code
254
krb5_kx509_ctx_add_san_dns_name(krb5_context context,
255
                                krb5_kx509_req_ctx kx509_ctx,
256
                                const char *dname)
257
0
{
258
0
    return hx509_request_add_dns_name(context->hx509ctx, kx509_ctx->csr,
259
0
                                      dname);
260
0
}
261
262
/**
263
 * Adds an xmppAddr SAN (jabber address) as an additional desired Certificate
264
 * Extension or in the CSR if the caller does not set a CSR.
265
 *
266
 * @param context The Kerberos library context
267
 * @param ctx The kx509 request context
268
 * @param jid A string containing a Jabber address
269
 *
270
 * @return A krb5 error code.
271
 */
272
krb5_error_code
273
krb5_kx509_ctx_add_san_xmpp(krb5_context context,
274
                            krb5_kx509_req_ctx kx509_ctx,
275
                            const char *jid)
276
0
{
277
0
    return hx509_request_add_xmpp_name(context->hx509ctx, kx509_ctx->csr, jid);
278
0
}
279
280
/**
281
 * Adds an rfc822Name SAN (e-mail address) as an additional desired Certificate
282
 * Extension or in the CSR if the caller does not set a CSR.
283
 *
284
 * @param context The Kerberos library context
285
 * @param ctx The kx509 request context
286
 * @param email A string containing an e-mail address
287
 *
288
 * @return A krb5 error code.
289
 */
290
krb5_error_code
291
krb5_kx509_ctx_add_san_rfc822Name(krb5_context context,
292
                                  krb5_kx509_req_ctx kx509_ctx,
293
                                  const char *email)
294
0
{
295
0
    return hx509_request_add_email(context->hx509ctx, kx509_ctx->csr, email);
296
0
}
297
298
/**
299
 * Adds an pkinit SAN (Kerberos principal name) as an additional desired
300
 * Certificate Extension or in the CSR if the caller does not set a CSR.
301
 *
302
 * @param context The Kerberos library context
303
 * @param ctx The kx509 request context
304
 * @param pname A string containing a representation of a Kerberos principal
305
 *              name
306
 *
307
 * @return A krb5 error code.
308
 */
309
krb5_error_code
310
krb5_kx509_ctx_add_san_pkinit(krb5_context context,
311
                              krb5_kx509_req_ctx kx509_ctx,
312
                              const char *pname)
313
0
{
314
0
    return hx509_request_add_pkinit(context->hx509ctx, kx509_ctx->csr, pname);
315
0
}
316
317
/**
318
 * Adds a Microsoft-style UPN (user principal name) as an additional desired
319
 * Certificate Extension or in the CSR if the caller does not set a CSR.
320
 *
321
 * @param context The Kerberos library context
322
 * @param ctx The kx509 request context
323
 * @param upn A string containing a representation of a UPN
324
 *
325
 * @return A krb5 error code.
326
 */
327
krb5_error_code
328
krb5_kx509_ctx_add_san_ms_upn(krb5_context context,
329
                              krb5_kx509_req_ctx kx509_ctx,
330
                              const char *upn)
331
0
{
332
0
    return hx509_request_add_ms_upn_name(context->hx509ctx, kx509_ctx->csr,
333
0
                                         upn);
334
0
}
335
336
/**
337
 * Adds an registeredID SAN (OID) as an additional desired Certificate
338
 * Extension or in the CSR if the caller does not set a CSR.
339
 *
340
 * @param context The Kerberos library context
341
 * @param ctx The kx509 request context
342
 * @param oids A string representation of an OID
343
 *
344
 * @return A krb5 error code.
345
 */
346
krb5_error_code
347
krb5_kx509_ctx_add_san_registeredID(krb5_context context,
348
                                    krb5_kx509_req_ctx kx509_ctx,
349
                                    const char *oids)
350
0
{
351
0
    krb5_error_code ret;
352
0
    heim_oid oid;
353
354
0
    ret = der_parse_heim_oid(oids, NULL, &oid);
355
0
    if (ret == 0)
356
0
        hx509_request_add_registered(context->hx509ctx, kx509_ctx->csr, &oid);
357
0
    der_free_oid(&oid);
358
0
    return ret;
359
0
}
360
361
static krb5_error_code
362
load_priv_key(krb5_context context,
363
              krb5_kx509_req_ctx kx509_ctx,
364
              const char *fn)
365
0
{
366
0
    hx509_private_key *keys = NULL;
367
0
    hx509_certs certs = NULL;
368
0
    krb5_error_code ret;
369
370
0
    ret = hx509_certs_init(context->hx509ctx, fn, 0, NULL, &certs);
371
0
    if (ret == ENOENT)
372
0
        return 0;
373
0
    if (ret == 0)
374
0
        ret = _hx509_certs_keys_get(context->hx509ctx, certs, &keys);
375
0
    if (ret == 0 && keys[0] == NULL)
376
0
        ret = ENOENT;
377
0
    if (ret == 0)
378
0
        kx509_ctx->priv_key = _hx509_private_key_ref(keys[0]);
379
0
    if (ret) {
380
0
  char *emsg = hx509_get_error_string(context->hx509ctx, ret);
381
382
0
        krb5_set_error_message(context, ret, "Could not load private key "
383
0
                               "from %s for kx509: %s", fn, emsg);
384
0
  hx509_free_error_string(emsg);
385
0
    }
386
0
    hx509_certs_free(&certs);
387
0
    return ret;
388
0
}
389
390
/**
391
 * Set a private key.
392
 *
393
 * @param context The Kerberos library context
394
 * @param ctx The kx509 request context
395
 * @param store The name of a PKIX credential store
396
 *
397
 * @return A krb5 error code.
398
 */
399
krb5_error_code
400
krb5_kx509_ctx_set_key(krb5_context context,
401
                       krb5_kx509_req_ctx kx509_ctx,
402
                       const char *store)
403
0
{
404
0
    SubjectPublicKeyInfo key;
405
0
    krb5_error_code ret;
406
407
0
    memset(&key, 0, sizeof(key));
408
0
    hx509_private_key_free(&kx509_ctx->priv_key);
409
0
    _hx509_certs_keys_free(context->hx509ctx, kx509_ctx->keys);
410
0
    kx509_ctx->keys = NULL;
411
0
    ret = load_priv_key(context, kx509_ctx, store);
412
0
    if (ret == 0)
413
0
        ret = hx509_private_key2SPKI(context->hx509ctx, kx509_ctx->priv_key,
414
0
                                     &key);
415
0
    if (ret == 0)
416
0
        ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx,
417
0
                                                     kx509_ctx->csr, &key);
418
0
    free_SubjectPublicKeyInfo(&key);
419
0
    return ret;
420
0
}
421
422
static krb5_error_code
423
gen_priv_key(krb5_context context,
424
             const char *gen_type,
425
             unsigned long gen_bits,
426
             hx509_private_key *key)
427
0
{
428
0
    struct hx509_generate_private_context *key_gen_ctx = NULL;
429
0
    krb5_error_code ret;
430
431
0
    _krb5_debug(context, 1, "kx509: gen priv key");
432
0
    if (strcmp(gen_type, "rsa") != 0) {
433
0
        krb5_set_error_message(context, ENOTSUP, "Key type %s is not "
434
0
                               "supported for kx509; only \"rsa\" is "
435
0
                               "supported for kx509 at this time",
436
0
                               gen_type);
437
0
        return ENOTSUP;
438
0
    }
439
440
0
    ret = _hx509_generate_private_key_init(context->hx509ctx,
441
0
                                           ASN1_OID_ID_PKCS1_RSAENCRYPTION,
442
0
                                           &key_gen_ctx);
443
0
    if (ret == 0)
444
0
        ret = _hx509_generate_private_key_bits(context->hx509ctx, key_gen_ctx, gen_bits);
445
446
0
    if (ret == 0)
447
0
        ret = _hx509_generate_private_key(context->hx509ctx, key_gen_ctx, key);
448
0
    _hx509_generate_private_key_free(&key_gen_ctx);
449
0
    if (ret) {
450
0
  char *emsg = hx509_get_error_string(context->hx509ctx, ret);
451
452
0
        krb5_set_error_message(context, ret,
453
0
                               "Could not generate a private key: %s", emsg);
454
0
  hx509_free_error_string(emsg);
455
0
    }
456
0
    return ret;
457
0
}
458
459
/**
460
 * Generate a private key.
461
 *
462
 * @param context The Kerberos library context
463
 * @param ctx The kx509 request context
464
 * @param gen_type The type of key (default: rsa)
465
 * @param gen_bits The size of the key (for non-ECC, really, for RSA)
466
 *
467
 * @return A krb5 error code.
468
 */
469
krb5_error_code
470
krb5_kx509_ctx_gen_key(krb5_context context,
471
                       krb5_kx509_req_ctx kx509_ctx,
472
                       const char *gen_type,
473
                       int gen_bits)
474
0
{
475
0
    SubjectPublicKeyInfo key;
476
0
    krb5_error_code ret;
477
478
0
    memset(&key, 0, sizeof(key));
479
480
0
    if (gen_type == NULL) {
481
0
        gen_type = krb5_config_get_string_default(context, NULL, "rsa",
482
0
                                                  "libdefaults",
483
0
                                                  "kx509_gen_key_type", NULL);
484
0
    }
485
0
    if (gen_bits == 0) {
486
        /*
487
         * The key size is really only for non-ECC, of which we'll only support
488
         * RSA.  For ECC key sizes will either be implied by the `key_type' or
489
         * will have to be a magic value that allows us to pick from some small
490
         * set of curves (e.g., 255 == Curve25519).
491
         */
492
0
        gen_bits = krb5_config_get_int_default(context, NULL, 2048,
493
0
                                               "libdefaults",
494
0
                                               "kx509_gen_rsa_key_size", NULL);
495
0
    }
496
0
    hx509_private_key_free(&kx509_ctx->priv_key);
497
0
    _hx509_certs_keys_free(context->hx509ctx, kx509_ctx->keys);
498
0
    kx509_ctx->keys = NULL;
499
500
0
    ret = gen_priv_key(context, gen_type, gen_bits, &kx509_ctx->priv_key);
501
0
    if (ret == 0)
502
0
        ret = hx509_private_key2SPKI(context->hx509ctx, kx509_ctx->priv_key,
503
0
                                     &key);
504
0
    if (ret == 0)
505
0
        ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx,
506
0
                                                     kx509_ctx->csr, &key);
507
0
    free_SubjectPublicKeyInfo(&key);
508
0
    return ret;
509
0
}
510
511
/* Set a cc config entry indicating that the kx509 service is not available */
512
static void
513
store_kx509_disabled(krb5_context context, const char *realm, krb5_ccache cc)
514
0
{
515
0
    krb5_data data;
516
517
0
    if (!cc)
518
0
        return;
519
520
0
    data.data = (void *)(uintptr_t)realm;
521
0
    data.length = strlen(realm);
522
0
    krb5_cc_set_config(context, cc, NULL, "kx509_service_realm", &data);
523
0
    data.data = "disabled";
524
0
    data.length = strlen(data.data);
525
0
    krb5_cc_set_config(context, cc, NULL, "kx509_service_status", &data);
526
0
}
527
528
static int KRB5_CALLCONV
529
certs_export_func(hx509_context context, void *d, hx509_cert c)
530
0
{
531
0
    heim_octet_string os;
532
0
    Certificates *cs = d;
533
0
    Certificate c2;
534
0
    int ret;
535
536
0
    ret = hx509_cert_binary(context, c, &os);
537
0
    if (ret)
538
0
        return ret;
539
0
    ret = decode_Certificate(os.data, os.length, &c2, NULL);
540
0
    der_free_octet_string(&os);
541
0
    if (ret)
542
0
        return ret;
543
0
    ret = add_Certificates(cs, &c2);
544
0
    free_Certificate(&c2);
545
0
    return ret;
546
0
}
547
548
static krb5_error_code
549
certs_export(hx509_context context, hx509_certs certs, heim_octet_string *out)
550
0
{
551
0
    Certificates cs;
552
0
    size_t len;
553
0
    int ret;
554
555
0
    cs.len = 0;
556
0
    cs.val = 0;
557
0
    ret = hx509_certs_iter_f(context, certs, certs_export_func, &cs);
558
0
    if (ret == 0)
559
0
        ASN1_MALLOC_ENCODE(Certificates, out->data, out->length, &cs, &len, ret);
560
0
    free_Certificates(&cs);
561
0
    return ret;
562
0
}
563
564
/* Store the private key and certificate where requested */
565
static krb5_error_code
566
store(krb5_context context,
567
      const char *hx509_store,
568
      const char *realm,
569
      krb5_ccache cc,
570
      hx509_private_key key,
571
      hx509_cert cert,
572
      hx509_certs chain)
573
0
{
574
0
    heim_octet_string hdata;
575
0
    krb5_error_code ret = 0;
576
0
    krb5_data data;
577
578
0
    krb5_clear_error_message(context);
579
580
0
    if (cc) {
581
        /* Record the realm we used */
582
0
        data.data = (void *)(uintptr_t)realm;
583
0
        data.length = strlen(realm);
584
0
        krb5_cc_set_config(context, cc, NULL, "kx509_service_realm", &data);
585
586
        /* Serialize and store the certificate in the ccache */
587
0
        ret = hx509_cert_binary(context->hx509ctx, cert, &hdata);
588
0
        if (ret == 0)
589
0
            ret = krb5_cc_set_config(context, cc, NULL, "kx509cert", &hdata);
590
0
        der_free_octet_string(&hdata);
591
592
0
        if (ret == 0 && key) {
593
            /*
594
             * Serialized and store the key in the ccache.  Use PKCS#8 so that we
595
             * store the algorithm OID too, which is needed in order to be able to
596
             * read the private key back.
597
             */
598
0
            if (ret == 0)
599
0
                ret = _hx509_private_key_export(context->hx509ctx, key,
600
0
                                                HX509_KEY_FORMAT_PKCS8, &hdata);
601
0
            if (ret == 0)
602
0
                ret = krb5_cc_set_config(context, cc, NULL, "kx509key", &hdata);
603
0
            der_free_octet_string(&hdata);
604
0
            if (ret)
605
0
                krb5_set_error_message(context, ret, "Could not store kx509 "
606
0
                                       "private key and certificate in ccache %s",
607
0
                                       krb5_cc_get_name(context, cc));
608
0
        }
609
610
0
        if (ret == 0 && chain) {
611
0
            ret = certs_export(context->hx509ctx, chain, &hdata);
612
0
            if (ret == 0)
613
0
                ret = krb5_cc_set_config(context, cc, NULL, "kx509cert-chain",
614
0
                                         &hdata);
615
0
            der_free_octet_string(&hdata);
616
0
        }
617
0
    }
618
619
    /* Store the private key and cert in an hx509 store */
620
0
    if (hx509_store != NULL) {
621
0
        hx509_certs certs;
622
623
0
        if (key)
624
0
            _hx509_cert_assign_key(cert, key); /* store both in the same store */
625
626
0
        ret = hx509_certs_init(context->hx509ctx, hx509_store,
627
0
                               HX509_CERTS_CREATE, NULL, &certs);
628
0
        if (ret == 0)
629
0
            ret = hx509_certs_add(context->hx509ctx, certs, cert);
630
0
        if (ret == 0 && chain != NULL)
631
0
            ret = hx509_certs_merge(context->hx509ctx, certs, chain);
632
0
        if (ret == 0)
633
0
            ret = hx509_certs_store(context->hx509ctx, certs, 0, NULL);
634
0
        hx509_certs_free(&certs);
635
0
        if (ret)
636
0
            krb5_prepend_error_message(context, ret, "Could not store kx509 "
637
0
                                       "private key and certificate in key "
638
0
                                       "store %s", hx509_store);
639
0
    }
640
641
    /* Store the name of the hx509 store in the ccache too */
642
0
    if (cc && hx509_store) {
643
0
        data.data = (void *)(uintptr_t)hx509_store;
644
0
        data.length = strlen(hx509_store);
645
0
        (void) krb5_cc_set_config(context, cc, NULL, "kx509store", &data);
646
0
    }
647
0
    return ret;
648
0
}
649
650
/* Make a Kx509CSRPlus or a raw SPKI */
651
static krb5_error_code
652
mk_kx509_req_body(krb5_context context,
653
                  krb5_kx509_req_ctx kx509_ctx,
654
                  krb5_data *out)
655
0
{
656
0
    krb5_error_code ret;
657
0
    size_t len;
658
659
0
    if (krb5_config_get_bool_default(context, NULL, FALSE,
660
0
                                     "realms", kx509_ctx->realm,
661
0
                                     "kx509_req_use_raw_spki", NULL)) {
662
0
        SubjectPublicKeyInfo spki;
663
664
        /* Interop with old kx509 servers, send a raw SPKI, not a CSR */
665
0
        out->data = NULL;
666
0
        out->length = 0;
667
0
        memset(&spki, 0, sizeof(spki));
668
0
        ret = hx509_private_key2SPKI(context->hx509ctx,
669
0
                                     kx509_ctx->priv_key, &spki);
670
0
        if (ret == 0) {
671
0
            out->length = spki.subjectPublicKey.length >> 3;
672
0
            out->data = spki.subjectPublicKey.data;
673
0
        }
674
0
        kx509_ctx->expect_chain = 0;
675
0
        return ret;
676
0
    }
677
678
    /*
679
     * New kx509 servers use a CSR for proof of possession, and send back a
680
     * chain of certificates, with the issued certificate first.
681
     */
682
0
    kx509_ctx->expect_chain = 1;
683
684
0
    if (kx509_ctx->given_csr.length) {
685
0
        krb5_data exts_der;
686
687
0
        exts_der.data = NULL;
688
0
        exts_der.length = 0;
689
690
        /* Use the given CSR */
691
0
        ret = der_copy_octet_string(&kx509_ctx->given_csr,
692
0
                                    &kx509_ctx->csr_plus.csr);
693
694
        /*
695
         * Extract the desired Certificate Extensions from our internal
696
         * as-yet-unsigned CSR, then decode them into place in the
697
         * Kx509CSRPlus.
698
         */
699
0
        if (ret == 0)
700
0
            ret = hx509_request_get_exts(context->hx509ctx,
701
0
                                         kx509_ctx->csr,
702
0
                                         &exts_der);
703
0
        if (ret == 0 && exts_der.data && exts_der.length &&
704
0
            (kx509_ctx->csr_plus.exts =
705
0
             calloc(1, sizeof (kx509_ctx->csr_plus.exts[0]))) == NULL)
706
0
            ret = krb5_enomem(context);
707
0
        if (ret == 0 && exts_der.data && exts_der.length)
708
0
            ret = decode_Extensions(exts_der.data, exts_der.length,
709
0
                                    kx509_ctx->csr_plus.exts, NULL);
710
0
        krb5_data_free(&exts_der);
711
0
    } else {
712
        /*
713
         * Sign and use our internal CSR, which will carry all our desired
714
         * Certificate Extensions as an extReq CSR Attribute.
715
         */
716
0
        ret = hx509_request_to_pkcs10(context->hx509ctx,
717
0
                                      kx509_ctx->csr,
718
0
                                      kx509_ctx->priv_key,
719
0
                                      &kx509_ctx->csr_plus.csr);
720
0
    }
721
0
    if (ret == 0)
722
0
        ASN1_MALLOC_ENCODE(Kx509CSRPlus, out->data, out->length,
723
0
                           &kx509_ctx->csr_plus, &len, ret);
724
0
    return ret;
725
0
}
726
727
static krb5_error_code
728
get_start_realm(krb5_context context,
729
                krb5_ccache cc,
730
                krb5_const_principal princ,
731
                char **out)
732
0
{
733
0
    krb5_error_code ret;
734
0
    krb5_data d;
735
736
0
    ret = krb5_cc_get_config(context, cc, NULL, "start_realm", &d);
737
0
    if (ret == 0) {
738
0
        *out = strndup(d.data, d.length);
739
0
        krb5_data_free(&d);
740
0
    } else if (princ) {
741
0
        *out = strdup(krb5_principal_get_realm(context, princ));
742
0
    } else {
743
0
        krb5_principal ccprinc = NULL;
744
745
0
        ret = krb5_cc_get_principal(context, cc, &ccprinc);
746
0
        if (ret)
747
0
            return ret;
748
0
        *out = strdup(krb5_principal_get_realm(context, ccprinc));
749
0
        krb5_free_principal(context, ccprinc);
750
0
    }
751
0
    return (*out) ? 0 : krb5_enomem(context);
752
0
}
753
754
/*
755
 * Make a request, which is a DER-encoded Kx509Request with version_2_0
756
 * prefixed to it.
757
 *
758
 * If no private key is given, then a probe request will be made.
759
 */
760
static krb5_error_code
761
mk_kx509_req(krb5_context context,
762
             krb5_kx509_req_ctx kx509_ctx,
763
             krb5_ccache incc,
764
             hx509_private_key private_key,
765
             krb5_data *req)
766
0
{
767
0
    unsigned char digest[SHA_DIGEST_LENGTH];
768
0
    SubjectPublicKeyInfo spki;
769
0
    struct Kx509Request kx509_req;
770
0
    krb5_data pre_req;
771
0
    krb5_error_code ret = 0;
772
0
    krb5_creds this_cred;
773
0
    krb5_creds *cred = NULL;
774
0
    HMAC_CTX ctx;
775
0
    const char *hostname;
776
0
    char *start_realm = NULL;
777
0
    size_t len = 0;
778
779
0
    krb5_data_zero(&pre_req);
780
0
    memset(&spki, 0, sizeof(spki));
781
0
    memset(&this_cred, 0, sizeof(this_cred));
782
0
    memset(&kx509_req, 0, sizeof(kx509_req));
783
0
    kx509_req.pk_hash.data = digest;
784
0
    kx509_req.pk_hash.length = SHA_DIGEST_LENGTH;
785
786
0
    if (private_key || kx509_ctx->given_csr.data) {
787
        /* Encode the CSR or public key for use in the request */
788
0
        ret = mk_kx509_req_body(context, kx509_ctx, &kx509_req.pk_key);
789
0
    } else {
790
        /* Probe */
791
0
        kx509_req.pk_key.data = NULL;
792
0
        kx509_req.pk_key.length = 0;
793
0
    }
794
795
0
    if (ret == 0)
796
0
        ret = krb5_cc_get_principal(context, incc, &this_cred.client);
797
0
    if (ret == 0)
798
0
        ret = get_start_realm(context, incc, this_cred.client, &start_realm);
799
0
    if (ret == 0 && kx509_ctx->realm == NULL)
800
0
        ret = krb5_kx509_ctx_set_realm(context, kx509_ctx, start_realm);
801
0
    if (ret == 0) {
802
        /*
803
         * The kx509 protocol as deployed uses kca_service/kdc_hostname, but
804
         * this is inconvenient in libkrb5: we want to be able to use the
805
         * send_to_kdc machinery, and since the Heimdal KDC is also the kx509
806
         * service, we want not to have to specify kx509 hosts separately from
807
         * KDCs.
808
         *
809
         * We'd much rather use krbtgt/CLIENT_REALM@REQUESTED_REALM.  What
810
         * we do is assume all KDCs for `realm' support the kx509 service and
811
         * then sendto the KDCs for that realm while using a hostbased service
812
         * if still desired.
813
         *
814
         * Note that upstairs we try to get the start_realm cc config, so if
815
         * realm wasn't given to krb5_kx509_ext(), then it should be set to
816
         * that already unless there's no start_realm cc config, in which case
817
         * we'll use the ccache's default client principal's realm.
818
         */
819
0
        hostname = krb5_config_get_string(context, NULL, "realms",
820
0
                                          kx509_ctx->realm, "kx509_hostname",
821
0
                                          NULL);
822
0
        if (hostname == NULL)
823
0
            hostname = krb5_config_get_string(context, NULL, "libdefaults",
824
0
                                              "kx509_hostname", NULL);
825
0
        if (hostname) {
826
0
            ret = krb5_sname_to_principal(context, hostname, "kca_service",
827
0
                                          KRB5_NT_SRV_HST, &this_cred.server);
828
0
            if (ret == 0)
829
0
                ret = krb5_principal_set_realm(context, this_cred.server,
830
0
                                               kx509_ctx->realm);
831
0
        } else {
832
0
            ret = krb5_make_principal(context, &this_cred.server,
833
0
                                      start_realm,
834
0
                                      KRB5_TGS_NAME,
835
0
                                      kx509_ctx->realm,
836
0
                                      NULL);
837
0
        }
838
0
    }
839
840
    /* Make the AP-REQ and extract the HMAC key */
841
0
    if (ret == 0)
842
0
        ret = krb5_get_credentials(context, 0, incc, &this_cred, &cred);
843
0
    if (ret == 0)
844
0
        ret = krb5_mk_req_extended(context, &kx509_ctx->ac, AP_OPTS_USE_SUBKEY,
845
0
                                   NULL, cred, &kx509_req.authenticator);
846
0
    krb5_free_keyblock(context, kx509_ctx->hmac_key);
847
0
    kx509_ctx->hmac_key = NULL;
848
0
    if (ret == 0)
849
0
        ret = krb5_auth_con_getkey(context, kx509_ctx->ac,
850
0
                                   &kx509_ctx->hmac_key);
851
852
0
    if (ret)
853
0
        goto out;
854
855
    /* Add the the key and HMAC to the message */
856
0
    HMAC_CTX_init(&ctx);
857
0
    if (HMAC_Init_ex(&ctx, kx509_ctx->hmac_key->keyvalue.data,
858
0
                     kx509_ctx->hmac_key->keyvalue.length,
859
0
                     EVP_sha1(), NULL) == 0) {
860
0
        HMAC_CTX_cleanup(&ctx);
861
0
        ret = krb5_enomem(context);
862
0
    } else {
863
0
        HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
864
0
        if (private_key || kx509_ctx->given_csr.data) {
865
0
            HMAC_Update(&ctx, kx509_req.pk_key.data, kx509_req.pk_key.length);
866
0
        } else {
867
            /* Probe */
868
0
            HMAC_Update(&ctx, kx509_req.authenticator.data, kx509_req.authenticator.length);
869
0
        }
870
0
        HMAC_Final(&ctx, kx509_req.pk_hash.data, 0);
871
0
        HMAC_CTX_cleanup(&ctx);
872
0
    }
873
874
    /* Encode the message, prefix `version_2_0', output the result */
875
0
    if (ret == 0)
876
0
        ASN1_MALLOC_ENCODE(Kx509Request, pre_req.data, pre_req.length, &kx509_req, &len, ret);
877
0
    if (ret == 0)
878
0
        ret = krb5_data_alloc(req, pre_req.length + sizeof(version_2_0));
879
0
    if (ret == 0) {
880
0
        memcpy(req->data, version_2_0, sizeof(version_2_0));
881
0
        memcpy(((unsigned char *)req->data) + sizeof(version_2_0),
882
0
               pre_req.data, pre_req.length);
883
0
    }
884
885
0
out:
886
0
    free(start_realm);
887
0
    free(pre_req.data);
888
0
    krb5_free_creds(context, cred);
889
0
    kx509_req.pk_hash.data = NULL;
890
0
    kx509_req.pk_hash.length = 0;
891
0
    free_Kx509Request(&kx509_req);
892
0
    free_SubjectPublicKeyInfo(&spki);
893
0
    krb5_free_cred_contents(context, &this_cred);
894
0
    if (ret == 0 && req->length != len + sizeof(version_2_0)) {
895
0
        krb5_data_free(req);
896
0
        krb5_set_error_message(context, ret = ERANGE,
897
0
                               "Could not make a kx509 request");
898
0
    }
899
0
    return ret;
900
0
}
901
902
static krb5_error_code
903
rd_chain(krb5_context context,
904
         heim_octet_string *d,
905
         hx509_cert *cert,
906
         hx509_certs *chain,
907
         heim_error_t *herr)
908
0
{
909
0
    krb5_error_code ret;
910
0
    Certificates certs;
911
0
    size_t i, len;
912
913
0
    *cert = NULL;
914
0
    *chain = NULL;
915
916
0
    if ((ret = decode_Certificates(d->data, d->length, &certs, &len)))
917
0
        return ret;
918
0
    if (certs.len == 0) {
919
0
        *herr = heim_error_create(EINVAL, "Server sent empty Certificate list");
920
0
        return EINVAL;
921
0
    }
922
0
    *cert = hx509_cert_init(context->hx509ctx, &certs.val[0], herr);
923
0
    if (*cert == NULL) {
924
0
        free_Certificates(&certs);
925
0
        return errno;
926
0
    }
927
0
    if (certs.len == 1)
928
0
        _krb5_debug(context, 1, "kx509 server sent certificate but no chain");
929
0
    else
930
0
        _krb5_debug(context, 1, "kx509 server sent %llu certificates",
931
0
                    (unsigned long long)certs.len);
932
933
0
    ret = hx509_certs_init(context->hx509ctx, "MEMORY:anonymous",
934
0
                           HX509_CERTS_CREATE, NULL, chain);
935
0
    if (ret) {
936
0
        hx509_cert_free(*cert);
937
0
        *cert = NULL;
938
0
        free_Certificates(&certs);
939
0
        return ret;
940
0
    }
941
942
0
    for (i = 1; ret == 0 && i < certs.len; i++) {
943
0
        hx509_cert c = hx509_cert_init(context->hx509ctx, &certs.val[i], herr);
944
945
0
        if (c == NULL)
946
0
            ret = errno;
947
0
        else
948
0
            ret = hx509_certs_add(context->hx509ctx, *chain, c);
949
0
        hx509_cert_free(c);
950
0
    }
951
0
    free_Certificates(&certs);
952
0
    if (ret) {
953
0
        hx509_certs_free(chain);
954
0
        hx509_cert_free(*cert);
955
0
        *cert = NULL;
956
0
    }
957
0
    return ret;
958
0
}
959
960
/* Parse and validate a kx509 reply */
961
static krb5_error_code
962
rd_kx509_resp(krb5_context context,
963
              krb5_kx509_req_ctx kx509_ctx,
964
              krb5_data *rep,
965
              hx509_cert *cert,
966
              hx509_certs *chain)
967
0
{
968
0
    unsigned char digest[SHA_DIGEST_LENGTH];
969
0
    Kx509Response r;
970
0
    krb5_error_code code = 0;
971
0
    krb5_error_code ret = 0;
972
0
    heim_string_t hestr;
973
0
    heim_error_t herr = NULL;
974
0
    const char *estr;
975
0
    HMAC_CTX ctx;
976
0
    size_t hdr_len = sizeof(version_2_0);
977
0
    size_t len;
978
979
0
    *cert = NULL;
980
0
    *chain = NULL;
981
982
    /* Strip `version_2_0' prefix */
983
0
    if (rep->length < hdr_len || memcmp(rep->data, version_2_0, hdr_len) != 0) {
984
0
        krb5_set_error_message(context, ENOTSUP,
985
0
                               "KDC does not support kx509 protocol");
986
0
        return ENOTSUP; /* XXX */
987
0
    }
988
989
    /* Decode */
990
0
    ret = decode_Kx509Response(((unsigned char *)rep->data) + 4,
991
0
                               rep->length - 4, &r, &len);
992
0
    if (ret == 0 && len + hdr_len != rep->length)
993
0
        ret = EINVAL; /* XXX */
994
0
    if (ret) {
995
0
        krb5_set_error_message(context, ret, "kx509 response is not valid");
996
0
        return ret;
997
0
    }
998
999
0
    HMAC_CTX_init(&ctx);
1000
0
    if (HMAC_Init_ex(&ctx, kx509_ctx->hmac_key->keyvalue.data,
1001
0
                     kx509_ctx->hmac_key->keyvalue.length, EVP_sha1(), NULL) == 0) {
1002
0
        free_Kx509Response(&r);
1003
0
        HMAC_CTX_cleanup(&ctx);
1004
0
        return krb5_enomem(context);
1005
0
    }
1006
1007
0
    HMAC_Update(&ctx, version_2_0, sizeof(version_2_0));
1008
1009
0
    {
1010
0
        int32_t t = r.error_code;
1011
0
        unsigned char encint[sizeof(t) + 1];
1012
0
        size_t k;
1013
1014
        /*
1015
         * RFC6717 says this about how the error-code is included in the HMAC:
1016
         *
1017
         *  o DER representation of the error-code exclusive of the tag and
1018
         *    length, if it is present.
1019
         *
1020
         * So we use der_put_integer(), which encodes from the right.
1021
         *
1022
         * RFC6717 does not constrain the error-code's range.  We assume it to
1023
         * be a 32-bit, signed integer, for which we'll need no more than 5
1024
         * bytes.
1025
         */
1026
0
        ret = der_put_integer(&encint[sizeof(encint) - 1],
1027
0
                              sizeof(encint), &t, &k);
1028
0
        if (ret == 0)
1029
0
            HMAC_Update(&ctx, &encint[sizeof(encint)] - k, k);
1030
1031
        /* Normalize error code */
1032
0
        if (r.error_code == 0) {
1033
0
            code = 0; /* No error */
1034
0
        } else if (r.error_code < 0) {
1035
0
            code = KRB5KRB_ERR_GENERIC; /* ??? */
1036
0
        } else if (r.error_code <= KX509_ERR_SRV_OVERLOADED - ERROR_TABLE_BASE_kx59) {
1037
            /*
1038
             * RFC6717 (kx509) error code.  These are actually not used on the
1039
             * wire in any existing implementations that we are aware of.  Just
1040
             * in case, however, we'll map these.
1041
             */
1042
0
            code = KX509_ERR_CLNT_FATAL + r.error_code;
1043
0
        } else if (r.error_code < kx509_krb5_error_base) {
1044
            /* Unknown error codes */
1045
0
            code = KRB5KRB_ERR_GENERIC;
1046
0
        } else {
1047
            /*
1048
             * Heimdal-specific enhancement to RFC6171: Kerberos wire protocol
1049
             * error codes.
1050
             */
1051
0
            code = KRB5KDC_ERR_NONE + r.error_code - kx509_krb5_error_base;
1052
0
            if (code >= KRB5_ERR_RCSID)
1053
0
                code = KRB5KRB_ERR_GENERIC;
1054
0
            if (code == KRB5KDC_ERR_NONE)
1055
0
                code = 0;
1056
0
        }
1057
0
    }
1058
0
    if (r.certificate)
1059
0
        HMAC_Update(&ctx, r.certificate->data, r.certificate->length);
1060
0
    if (r.e_text)
1061
0
        HMAC_Update(&ctx, *r.e_text, strlen(*r.e_text));
1062
0
    HMAC_Final(&ctx, &digest, 0);
1063
0
    HMAC_CTX_cleanup(&ctx);
1064
1065
0
    if (r.hash == NULL) {
1066
        /*
1067
         * No HMAC -> unauthenticated [error] response.
1068
         *
1069
         * Do not output any certificate.
1070
         */
1071
0
        free_Kx509Response(&r);
1072
0
        return code;
1073
0
    }
1074
1075
    /*
1076
     * WARNING: We do not validate that `r.certificate' is a DER-encoded
1077
     *          Certificate, not here, and we don't use a different HMAC key
1078
     *          for the response than for the request.
1079
     *
1080
     *          If ever we start sending a Certificate as the Kx509Request
1081
     *          pk-key field, then we'll have a reflection attack.  As the
1082
     *          Certificate we'd send in that case will be expired, the
1083
     *          reflection attack would be just a DoS.
1084
     */
1085
0
    if (r.hash->length != sizeof(digest) ||
1086
0
        ct_memcmp(r.hash->data, digest, sizeof(digest)) != 0) {
1087
0
        krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED,
1088
0
                               "kx509 response MAC mismatch");
1089
0
        free_Kx509Response(&r);
1090
0
        return KRB5KRB_AP_ERR_BAD_INTEGRITY;
1091
0
    }
1092
1093
0
    if (r.certificate == NULL) {
1094
        /* Authenticated response, either an error or probe success */
1095
0
        free_Kx509Response(&r);
1096
0
        if (code != KRB5KDC_ERR_POLICY && kx509_ctx->priv_key == NULL)
1097
0
            return 0; /* Probe success */
1098
0
        return code ? code : KRB5KDC_ERR_POLICY; /* Not a probe -> must fail */
1099
0
    }
1100
1101
    /* Import the certificate payload */
1102
0
    if (kx509_ctx->expect_chain) {
1103
0
        ret = rd_chain(context, r.certificate, cert, chain, &herr);
1104
0
    } else {
1105
0
        *cert = hx509_cert_init_data(context->hx509ctx, r.certificate->data,
1106
0
                                     r.certificate->length, &herr);
1107
0
        if (!*cert)
1108
0
            ret = errno;
1109
0
    }
1110
0
    free_Kx509Response(&r);
1111
0
    if (*cert) {
1112
0
        heim_release(herr);
1113
0
        return 0;
1114
0
    }
1115
1116
0
    hestr = herr ? heim_error_copy_string(herr) : NULL;
1117
0
    estr = hestr ? heim_string_get_utf8(hestr) : "(no error message)";
1118
0
    krb5_set_error_message(context, ret, "Could not parse certificate "
1119
0
                           "produced by kx509 KDC: %s (%ld)",
1120
0
                           estr,
1121
0
                           herr ? (long)heim_error_get_code(herr) : 0L);
1122
1123
0
    heim_release(hestr);
1124
0
    heim_release(herr);
1125
0
    return HEIM_PKINIT_CERTIFICATE_INVALID; /* XXX */
1126
0
}
1127
1128
/*
1129
 * Make a request, send it, get the response, parse it, and store the
1130
 * private key and certificate.
1131
 */
1132
static krb5_error_code
1133
kx509_core(krb5_context context,
1134
           krb5_kx509_req_ctx kx509_ctx,
1135
           krb5_ccache incc,
1136
           const char *hx509_store,
1137
           krb5_ccache outcc)
1138
0
{
1139
0
    krb5_error_code ret;
1140
0
    hx509_certs chain = NULL;
1141
0
    hx509_cert cert = NULL;
1142
0
    krb5_data req, resp;
1143
1144
0
    krb5_data_zero(&req);
1145
0
    krb5_data_zero(&resp);
1146
1147
    /* Make the kx509 request */
1148
0
    ret = mk_kx509_req(context, kx509_ctx, incc, kx509_ctx->priv_key, &req);
1149
1150
    /* Send the kx509 request and get the response */
1151
0
    if (ret == 0)
1152
0
        ret = krb5_sendto_context(context, NULL, &req,
1153
0
                                  kx509_ctx->realm, &resp);
1154
0
    if (ret == 0)
1155
0
        ret = rd_kx509_resp(context, kx509_ctx, &resp, &cert, &chain);
1156
1157
    /* Store the key and cert! */
1158
0
    if (ret == 0 && cert && (kx509_ctx->priv_key || kx509_ctx->given_csr.data))
1159
0
        ret = store(context, hx509_store, kx509_ctx->realm, outcc,
1160
0
                    kx509_ctx->priv_key, cert, chain);
1161
0
    else if (ret == KRB5KDC_ERR_POLICY)
1162
        /* Probe failed -> record that the realm does not support kx509 */
1163
0
        store_kx509_disabled(context, kx509_ctx->realm, outcc);
1164
1165
0
    hx509_certs_free(&chain);
1166
0
    hx509_cert_free(cert);
1167
0
    krb5_data_free(&resp);
1168
0
    krb5_data_free(&req);
1169
0
    return ret;
1170
0
}
1171
1172
/**
1173
 * Use the kx509 v2 protocol to get a certificate for the client principal.
1174
 *
1175
 * Given a private key this function will get a certificate.  If no private key
1176
 * is given, one will be generated.
1177
 *
1178
 * The private key and certificate will be stored in the given PKIX credential
1179
 * store (e.g, "PEM-FILE:/path/to/file.pem") and/or given output ccache.  When
1180
 * stored in a ccache, the DER-encoded Certificate will be stored as the data
1181
 * payload of a "cc config" named "kx509cert", while the key will be stored as
1182
 * a DER-encoded PKCS#8 PrivateKeyInfo in a cc config named "kx509key".
1183
 *
1184
 * @param context The Kerberos library context
1185
 * @param kx509_ctx A kx509 request context
1186
 * @param incc A credential cache (if NULL use default ccache)
1187
 * @param hx509_store An PKIX credential store into which to store the private
1188
 *                    key and certificate (e.g, "PEM-FILE:/path/to/file.pem")
1189
 * @param outcc A ccache into which to store the private key and certificate
1190
 *              (mandatory)
1191
 *
1192
 * @return A krb5 error code.
1193
 */
1194
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1195
krb5_kx509_ext(krb5_context context,
1196
               krb5_kx509_req_ctx kx509_ctx,
1197
               krb5_ccache incc,
1198
               const char *hx509_store,
1199
               krb5_ccache outcc)
1200
0
{
1201
0
    krb5_ccache def_cc = NULL;
1202
0
    krb5_error_code ret;
1203
1204
0
    if (incc == NULL) {
1205
0
        if ((ret = krb5_cc_default(context, &def_cc)))
1206
0
            return ret;
1207
0
        incc = def_cc;
1208
0
    }
1209
1210
0
    if (kx509_ctx->realm == NULL &&
1211
0
        (ret = get_start_realm(context, incc, NULL, &kx509_ctx->realm))) {
1212
0
        if (def_cc)
1213
0
            krb5_cc_close(context, def_cc);
1214
0
        return ret;
1215
0
    }
1216
1217
0
    if (kx509_ctx->priv_key || kx509_ctx->given_csr.data) {
1218
        /* If given a private key, use it */
1219
0
        ret = kx509_core(context, kx509_ctx, incc, hx509_store, outcc);
1220
0
        if (def_cc)
1221
0
            krb5_cc_close(context, def_cc);
1222
0
        return ret;
1223
0
    }
1224
1225
    /*
1226
     * No private key given, so we generate one.
1227
     *
1228
     * However, before taking the hit for generating a keypair we probe to see
1229
     * if we're likely to succeeed.
1230
     */
1231
1232
    /* Probe == call kx509_core() w/o a private key */
1233
0
    ret = kx509_core(context, kx509_ctx, incc, NULL, outcc);
1234
0
    if (ret == 0 && kx509_ctx->given_csr.data == NULL)
1235
0
        ret = krb5_kx509_ctx_gen_key(context, kx509_ctx, NULL, 0);
1236
0
    if (ret == 0)
1237
0
        ret = kx509_core(context, kx509_ctx, incc, hx509_store, outcc);
1238
1239
0
    if (def_cc)
1240
0
        krb5_cc_close(context, def_cc);
1241
0
    return ret;
1242
0
}
1243
1244
/**
1245
 * Generates a public key and uses the kx509 v2 protocol to get a certificate
1246
 * for that key and the client principal's subject name.
1247
 *
1248
 * The private key and certificate will be stored in the given ccache, and also
1249
 * in a corresponding PKIX credential store if one is configured via
1250
 * [libdefaults] kx509_store.
1251
 *
1252
 * XXX NOTE: Dicey feature here...  Review carefully!
1253
 *
1254
 * @param context The Kerberos library context
1255
 * @param cc A credential cache
1256
 * @param realm A realm from which to get the certificate (uses the client
1257
 *              principal's realm if NULL)
1258
 *
1259
 * @return A krb5 error code.
1260
 */
1261
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1262
krb5_kx509(krb5_context context, krb5_ccache cc, const char *realm)
1263
0
{
1264
0
    krb5_kx509_req_ctx kx509_ctx;
1265
0
    krb5_error_code ret;
1266
0
    const char *defcc;
1267
0
    char *ccache_full_name = NULL;
1268
0
    char *store_exp = NULL;
1269
1270
0
    ret = krb5_kx509_ctx_init(context, &kx509_ctx);
1271
0
    if (ret)
1272
0
        return ret;
1273
0
    if (realm)
1274
0
        ret = krb5_kx509_ctx_set_realm(context, kx509_ctx, realm);
1275
1276
    /*
1277
     * The idea is that IF we are asked to do kx509 w/ creds from a default
1278
     * ccache THEN we should store the kx509 certificate (if we get one) and
1279
     * private key in the default hx509 store for kx509.
1280
     *
1281
     * Ideally we could have HTTP user-agents and/or TLS libraries look for
1282
     * client certificates and private keys in that default hx509 store.
1283
     *
1284
     * Of course, those user-agents / libraries should be configured to use
1285
     * those credentials with specific hostnames/domainnames, not the entire
1286
     * Internet, as the latter leaks the user's identity to the world.
1287
     *
1288
     * So we check if the full name for `cc' is the same as that of the default
1289
     * ccache name, and if so we get the [libdefaults] kx509_store string and
1290
     * expand it, then use it.
1291
     */
1292
0
    if (ret == 0 &&
1293
0
        (defcc = krb5_cc_configured_default_name(context)) &&
1294
0
        krb5_cc_get_full_name(context, cc, &ccache_full_name) == 0 &&
1295
0
        strcmp(defcc, ccache_full_name) == 0) {
1296
1297
        /* Find an hx509 store */
1298
0
        const char *store = krb5_config_get_string(context, NULL,
1299
0
                                                   "libdefaults",
1300
0
                                                   "kx509_store", NULL);
1301
0
        if (store)
1302
0
            ret = _krb5_expand_path_tokens(context, store, 1, &store_exp);
1303
1304
        /*
1305
         * If there's a private key in the store already, we'll use it, else
1306
         * we'll let krb5_kx509_ext() generate one, so we ignore this return
1307
         * value:
1308
         */
1309
0
        (void) krb5_kx509_ctx_set_key(context, kx509_ctx, store);
1310
0
    }
1311
1312
    /*
1313
     * If we did settle on a default hx509 store, we'll use it for reading the
1314
     * private key from (if it exists) as well as for storing the certificate
1315
     * (and private key) into, which may save us some key generation cycles.
1316
     */
1317
0
    if (ret == 0)
1318
0
        ret = krb5_kx509_ext(context, kx509_ctx, cc, store_exp, cc);
1319
0
    krb5_kx509_ctx_free(context, &kx509_ctx);
1320
0
    free(ccache_full_name);
1321
0
    free(store_exp);
1322
0
    return ret;
1323
0
}