Line data Source code
1 : /*
2 : * Copyright (c) 2004 - 2005 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 "hdb_locl.h"
35 : #include <der.h>
36 :
37 : krb5_error_code
38 0 : hdb_entry_check_mandatory(krb5_context context, const hdb_entry *ent)
39 : {
40 0 : size_t i;
41 :
42 0 : if (ent->extensions == NULL)
43 0 : return 0;
44 :
45 : /*
46 : * check for unknown extensions and if they were tagged mandatory
47 : */
48 :
49 0 : for (i = 0; i < ent->extensions->len; i++) {
50 0 : if (ent->extensions->val[i].data.element !=
51 : choice_HDB_extension_data_asn1_ellipsis)
52 0 : continue;
53 0 : if (ent->extensions->val[i].mandatory) {
54 0 : krb5_set_error_message(context, HDB_ERR_MANDATORY_OPTION,
55 : "Principal has unknown "
56 : "mandatory extension");
57 0 : return HDB_ERR_MANDATORY_OPTION;
58 : }
59 : }
60 0 : return 0;
61 : }
62 :
63 : HDB_extension *
64 7370 : hdb_find_extension(const hdb_entry *entry, int type)
65 : {
66 72 : size_t i;
67 :
68 7370 : if (entry->extensions == NULL)
69 1890 : return NULL;
70 :
71 5468 : for (i = 0; i < entry->extensions->len; i++)
72 5462 : if (entry->extensions->val[i].data.element == (unsigned)type)
73 5456 : return &entry->extensions->val[i];
74 6 : return NULL;
75 : }
76 :
77 : /*
78 : * Replace the extension `ext' in `entry'. Make a copy of the
79 : * extension, so the caller must still free `ext' on both success and
80 : * failure. Returns 0 or error code.
81 : */
82 :
83 : krb5_error_code
84 1645 : hdb_replace_extension(krb5_context context,
85 : hdb_entry *entry,
86 : const HDB_extension *ext)
87 : {
88 18 : HDB_extension *ext2;
89 18 : int ret;
90 :
91 1645 : ext2 = NULL;
92 :
93 1645 : if (entry->extensions == NULL) {
94 1645 : entry->extensions = calloc(1, sizeof(*entry->extensions));
95 1645 : if (entry->extensions == NULL) {
96 0 : krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
97 0 : return ENOMEM;
98 : }
99 0 : } else if (ext->data.element != choice_HDB_extension_data_asn1_ellipsis) {
100 0 : ext2 = hdb_find_extension(entry, ext->data.element);
101 : } else {
102 : /*
103 : * This is an unknown extension, and we are asked to replace a
104 : * possible entry in `entry' that is of the same type. This
105 : * might seem impossible, but ASN.1 CHOICE comes to our
106 : * rescue. The first tag in each branch in the CHOICE is
107 : * unique, so just find the element in the list that have the
108 : * same tag was we are putting into the list.
109 : */
110 0 : Der_class replace_class, list_class;
111 0 : Der_type replace_type, list_type;
112 0 : unsigned int replace_tag, list_tag;
113 0 : size_t size;
114 0 : size_t i;
115 :
116 0 : ret = der_get_tag(ext->data.u.asn1_ellipsis.data,
117 0 : ext->data.u.asn1_ellipsis.length,
118 : &replace_class, &replace_type, &replace_tag,
119 : &size);
120 0 : if (ret) {
121 0 : krb5_set_error_message(context, ret, "hdb: failed to decode "
122 : "replacement hdb extension");
123 0 : return ret;
124 : }
125 :
126 0 : for (i = 0; i < entry->extensions->len; i++) {
127 0 : HDB_extension *ext3 = &entry->extensions->val[i];
128 :
129 0 : if (ext3->data.element != choice_HDB_extension_data_asn1_ellipsis)
130 0 : continue;
131 :
132 0 : ret = der_get_tag(ext3->data.u.asn1_ellipsis.data,
133 : ext3->data.u.asn1_ellipsis.length,
134 : &list_class, &list_type, &list_tag,
135 : &size);
136 0 : if (ret) {
137 0 : krb5_set_error_message(context, ret, "hdb: failed to decode "
138 : "present hdb extension");
139 0 : return ret;
140 : }
141 :
142 0 : if (MAKE_TAG(replace_class,replace_type,replace_type) ==
143 0 : MAKE_TAG(list_class,list_type,list_type)) {
144 0 : ext2 = ext3;
145 0 : break;
146 : }
147 : }
148 : }
149 :
150 1627 : if (ext2) {
151 0 : free_HDB_extension(ext2);
152 0 : ret = copy_HDB_extension(ext, ext2);
153 0 : if (ret)
154 0 : krb5_set_error_message(context, ret, "hdb: failed to copy replacement "
155 : "hdb extension");
156 0 : return ret;
157 : }
158 :
159 1645 : return add_HDB_extensions(entry->extensions, ext);
160 : }
161 :
162 : krb5_error_code
163 0 : hdb_clear_extension(krb5_context context,
164 : hdb_entry *entry,
165 : int type)
166 : {
167 0 : size_t i;
168 :
169 0 : if (entry->extensions == NULL)
170 0 : return 0;
171 :
172 0 : for (i = 0; i < entry->extensions->len; ) {
173 0 : if (entry->extensions->val[i].data.element == (unsigned)type)
174 0 : (void) remove_HDB_extensions(entry->extensions, i);
175 : else
176 0 : i++;
177 : }
178 0 : if (entry->extensions->len == 0) {
179 0 : free(entry->extensions->val);
180 0 : free(entry->extensions);
181 0 : entry->extensions = NULL;
182 : }
183 :
184 0 : return 0;
185 : }
186 :
187 :
188 : krb5_error_code
189 2 : hdb_entry_get_pkinit_acl(const hdb_entry *entry, const HDB_Ext_PKINIT_acl **a)
190 : {
191 0 : const HDB_extension *ext;
192 :
193 2 : ext = hdb_find_extension(entry, choice_HDB_extension_data_pkinit_acl);
194 2 : if (ext)
195 0 : *a = &ext->data.u.pkinit_acl;
196 : else
197 2 : *a = NULL;
198 :
199 2 : return 0;
200 : }
201 :
202 : krb5_error_code
203 0 : hdb_entry_get_pkinit_hash(const hdb_entry *entry, const HDB_Ext_PKINIT_hash **a)
204 : {
205 0 : const HDB_extension *ext;
206 :
207 0 : ext = hdb_find_extension(entry, choice_HDB_extension_data_pkinit_cert_hash);
208 0 : if (ext)
209 0 : *a = &ext->data.u.pkinit_cert_hash;
210 : else
211 0 : *a = NULL;
212 :
213 0 : return 0;
214 : }
215 :
216 : krb5_error_code
217 108 : hdb_entry_get_pkinit_cert(const hdb_entry *entry, const HDB_Ext_PKINIT_cert **a)
218 : {
219 0 : const HDB_extension *ext;
220 :
221 108 : ext = hdb_find_extension(entry, choice_HDB_extension_data_pkinit_cert);
222 108 : if (ext)
223 0 : *a = &ext->data.u.pkinit_cert;
224 : else
225 108 : *a = NULL;
226 :
227 108 : return 0;
228 : }
229 :
230 : krb5_error_code
231 0 : hdb_entry_get_krb5_config(const hdb_entry *entry, heim_octet_string *c)
232 : {
233 0 : const HDB_extension *ext;
234 :
235 0 : c->data = NULL;
236 0 : c->length = 0;
237 0 : ext = hdb_find_extension(entry, choice_HDB_extension_data_krb5_config);
238 0 : if (ext)
239 0 : *c = ext->data.u.krb5_config;
240 0 : return 0;
241 : }
242 :
243 : krb5_error_code
244 0 : hdb_entry_set_krb5_config(krb5_context context,
245 : hdb_entry *entry,
246 : heim_octet_string *s)
247 : {
248 0 : HDB_extension ext;
249 :
250 0 : ext.mandatory = FALSE;
251 0 : ext.data.element = choice_HDB_extension_data_last_pw_change;
252 : /* hdb_replace_extension() copies this, so no need to copy it here */
253 0 : ext.data.u.krb5_config = *s;
254 0 : return hdb_replace_extension(context, entry, &ext);
255 : }
256 :
257 : krb5_error_code
258 0 : hdb_entry_get_pw_change_time(const hdb_entry *entry, time_t *t)
259 : {
260 0 : const HDB_extension *ext;
261 :
262 0 : ext = hdb_find_extension(entry, choice_HDB_extension_data_last_pw_change);
263 0 : if (ext)
264 0 : *t = ext->data.u.last_pw_change;
265 : else
266 0 : *t = 0;
267 :
268 0 : return 0;
269 : }
270 :
271 : krb5_error_code
272 0 : hdb_entry_set_pw_change_time(krb5_context context,
273 : hdb_entry *entry,
274 : time_t t)
275 : {
276 0 : HDB_extension ext;
277 :
278 0 : ext.mandatory = FALSE;
279 0 : ext.data.element = choice_HDB_extension_data_last_pw_change;
280 0 : if (t == 0)
281 0 : t = time(NULL);
282 0 : ext.data.u.last_pw_change = t;
283 :
284 0 : return hdb_replace_extension(context, entry, &ext);
285 : }
286 :
287 : int
288 0 : hdb_entry_get_password(krb5_context context, HDB *db,
289 : const hdb_entry *entry, char **p)
290 : {
291 0 : HDB_extension *ext;
292 0 : char *str;
293 0 : int ret;
294 :
295 0 : ext = hdb_find_extension(entry, choice_HDB_extension_data_password);
296 0 : if (ext) {
297 0 : heim_utf8_string xstr;
298 0 : heim_octet_string pw;
299 :
300 0 : if (db->hdb_master_key_set && ext->data.u.password.mkvno) {
301 0 : hdb_master_key key;
302 :
303 0 : key = _hdb_find_master_key(ext->data.u.password.mkvno,
304 : db->hdb_master_key);
305 :
306 0 : if (key == NULL) {
307 0 : krb5_set_error_message(context, HDB_ERR_NO_MKEY,
308 : "master key %d missing",
309 0 : *ext->data.u.password.mkvno);
310 0 : return HDB_ERR_NO_MKEY;
311 : }
312 :
313 0 : ret = _hdb_mkey_decrypt(context, key, HDB_KU_MKEY,
314 : ext->data.u.password.password.data,
315 : ext->data.u.password.password.length,
316 : &pw);
317 : } else {
318 0 : ret = der_copy_octet_string(&ext->data.u.password.password, &pw);
319 : }
320 0 : if (ret) {
321 0 : krb5_clear_error_message(context);
322 0 : return ret;
323 : }
324 :
325 0 : xstr = pw.data;
326 0 : if (xstr[pw.length - 1] != '\0') {
327 0 : krb5_set_error_message(context, EINVAL, "malformed password");
328 0 : return EINVAL;
329 : }
330 :
331 0 : *p = strdup(xstr);
332 :
333 0 : der_free_octet_string(&pw);
334 0 : if (*p == NULL) {
335 0 : krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
336 0 : return ENOMEM;
337 : }
338 0 : return 0;
339 : }
340 :
341 0 : ret = krb5_unparse_name(context, entry->principal, &str);
342 0 : if (ret == 0) {
343 0 : krb5_set_error_message(context, ENOENT,
344 : "no password attribute for %s", str);
345 0 : free(str);
346 : } else
347 0 : krb5_clear_error_message(context);
348 :
349 0 : return ENOENT;
350 : }
351 :
352 : int
353 0 : hdb_entry_set_password(krb5_context context, HDB *db,
354 : hdb_entry *entry, const char *p)
355 : {
356 0 : HDB_extension ext;
357 0 : hdb_master_key key;
358 0 : int ret;
359 :
360 0 : ext.mandatory = FALSE;
361 0 : ext.data.element = choice_HDB_extension_data_password;
362 :
363 0 : if (db->hdb_master_key_set) {
364 :
365 0 : key = _hdb_find_master_key(NULL, db->hdb_master_key);
366 0 : if (key == NULL) {
367 0 : krb5_set_error_message(context, HDB_ERR_NO_MKEY,
368 : "hdb_entry_set_password: "
369 : "failed to find masterkey");
370 0 : return HDB_ERR_NO_MKEY;
371 : }
372 :
373 0 : ret = _hdb_mkey_encrypt(context, key, HDB_KU_MKEY,
374 0 : p, strlen(p) + 1,
375 : &ext.data.u.password.password);
376 0 : if (ret)
377 0 : return ret;
378 :
379 0 : ext.data.u.password.mkvno =
380 0 : malloc(sizeof(*ext.data.u.password.mkvno));
381 0 : if (ext.data.u.password.mkvno == NULL) {
382 0 : free_HDB_extension(&ext);
383 0 : krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
384 0 : return ENOMEM;
385 : }
386 0 : *ext.data.u.password.mkvno = _hdb_mkey_version(key);
387 :
388 : } else {
389 0 : ext.data.u.password.mkvno = NULL;
390 :
391 0 : ret = krb5_data_copy(&ext.data.u.password.password,
392 0 : p, strlen(p) + 1);
393 0 : if (ret) {
394 0 : krb5_set_error_message(context, ret, "malloc: out of memory");
395 0 : free_HDB_extension(&ext);
396 0 : return ret;
397 : }
398 : }
399 :
400 0 : ret = hdb_replace_extension(context, entry, &ext);
401 :
402 0 : free_HDB_extension(&ext);
403 :
404 0 : return ret;
405 : }
406 :
407 : int
408 0 : hdb_entry_clear_password(krb5_context context, hdb_entry *entry)
409 : {
410 0 : return hdb_clear_extension(context, entry,
411 : choice_HDB_extension_data_password);
412 : }
413 :
414 : krb5_error_code
415 0 : hdb_entry_get_ConstrainedDelegACL(const hdb_entry *entry,
416 : const HDB_Ext_Constrained_delegation_acl **a)
417 : {
418 0 : const HDB_extension *ext;
419 :
420 0 : ext = hdb_find_extension(entry,
421 : choice_HDB_extension_data_allowed_to_delegate_to);
422 0 : if (ext)
423 0 : *a = &ext->data.u.allowed_to_delegate_to;
424 : else
425 0 : *a = NULL;
426 :
427 0 : return 0;
428 : }
429 :
430 : krb5_error_code
431 0 : hdb_entry_get_aliases(const hdb_entry *entry, const HDB_Ext_Aliases **a)
432 : {
433 0 : const HDB_extension *ext;
434 :
435 0 : ext = hdb_find_extension(entry, choice_HDB_extension_data_aliases);
436 0 : if (ext)
437 0 : *a = &ext->data.u.aliases;
438 : else
439 0 : *a = NULL;
440 :
441 0 : return 0;
442 : }
443 :
444 : unsigned int
445 0 : hdb_entry_get_kvno_diff_clnt(const hdb_entry *entry)
446 : {
447 0 : const HDB_extension *ext;
448 :
449 0 : ext = hdb_find_extension(entry,
450 : choice_HDB_extension_data_hist_kvno_diff_clnt);
451 0 : if (ext)
452 0 : return ext->data.u.hist_kvno_diff_clnt;
453 0 : return 1;
454 : }
455 :
456 : krb5_error_code
457 0 : hdb_entry_set_kvno_diff_clnt(krb5_context context, hdb_entry *entry,
458 : unsigned int diff)
459 : {
460 0 : HDB_extension ext;
461 :
462 0 : if (diff > 16384)
463 0 : return EINVAL;
464 0 : ext.mandatory = FALSE;
465 0 : ext.data.element = choice_HDB_extension_data_hist_kvno_diff_clnt;
466 0 : ext.data.u.hist_kvno_diff_clnt = diff;
467 0 : return hdb_replace_extension(context, entry, &ext);
468 : }
469 :
470 : krb5_error_code
471 0 : hdb_entry_clear_kvno_diff_clnt(krb5_context context, hdb_entry *entry)
472 : {
473 0 : return hdb_clear_extension(context, entry,
474 : choice_HDB_extension_data_hist_kvno_diff_clnt);
475 : }
476 :
477 : unsigned int
478 0 : hdb_entry_get_kvno_diff_svc(const hdb_entry *entry)
479 : {
480 0 : const HDB_extension *ext;
481 :
482 0 : ext = hdb_find_extension(entry,
483 : choice_HDB_extension_data_hist_kvno_diff_svc);
484 0 : if (ext)
485 0 : return ext->data.u.hist_kvno_diff_svc;
486 0 : return 1024; /* max_life effectively provides a better default */
487 : }
488 :
489 : krb5_error_code
490 0 : hdb_entry_set_kvno_diff_svc(krb5_context context, hdb_entry *entry,
491 : unsigned int diff)
492 : {
493 0 : HDB_extension ext;
494 :
495 0 : if (diff > 16384)
496 0 : return EINVAL;
497 0 : ext.mandatory = FALSE;
498 0 : ext.data.element = choice_HDB_extension_data_hist_kvno_diff_svc;
499 0 : ext.data.u.hist_kvno_diff_svc = diff;
500 0 : return hdb_replace_extension(context, entry, &ext);
501 : }
502 :
503 : krb5_error_code
504 0 : hdb_entry_clear_kvno_diff_svc(krb5_context context, hdb_entry *entry)
505 : {
506 0 : return hdb_clear_extension(context, entry,
507 : choice_HDB_extension_data_hist_kvno_diff_svc);
508 : }
509 :
510 : krb5_error_code
511 0 : hdb_set_last_modified_by(krb5_context context, hdb_entry *entry,
512 : krb5_principal modby, time_t modtime)
513 : {
514 0 : krb5_error_code ret;
515 0 : Event *old_ev;
516 0 : Event *ev;
517 :
518 0 : old_ev = entry->modified_by;
519 :
520 0 : ev = calloc(1, sizeof (*ev));
521 0 : if (!ev)
522 0 : return ENOMEM;
523 0 : if (modby)
524 0 : ret = krb5_copy_principal(context, modby, &ev->principal);
525 : else
526 0 : ret = krb5_parse_name(context, "root/admin", &ev->principal);
527 0 : if (ret) {
528 0 : free(ev);
529 0 : return ret;
530 : }
531 0 : ev->time = modtime;
532 0 : if (!ev->time)
533 0 : time(&ev->time);
534 :
535 0 : entry->modified_by = ev;
536 0 : if (old_ev)
537 0 : free_Event(old_ev);
538 0 : return 0;
539 : }
540 :
541 : krb5_error_code
542 0 : hdb_entry_get_key_rotation(krb5_context context,
543 : const hdb_entry *entry,
544 : const HDB_Ext_KeyRotation **kr)
545 : {
546 0 : HDB_extension *ext =
547 0 : hdb_find_extension(entry, choice_HDB_extension_data_key_rotation);
548 :
549 0 : *kr = ext ? &ext->data.u.key_rotation : NULL;
550 0 : return 0;
551 : }
552 :
553 : krb5_error_code
554 0 : hdb_validate_key_rotation(krb5_context context,
555 : const KeyRotation *past_kr,
556 : const KeyRotation *new_kr)
557 : {
558 0 : unsigned int last_kvno;
559 :
560 0 : if (new_kr->period < 1) {
561 0 : krb5_set_error_message(context, EINVAL,
562 : "Key rotation periods must be non-zero "
563 : "and positive");
564 0 : return EINVAL;
565 : }
566 0 : if (new_kr->base_key_kvno < 1 || new_kr->base_kvno < 1) {
567 0 : krb5_set_error_message(context, EINVAL,
568 : "Key version number zero not allowed "
569 : "for key rotation");
570 0 : return EINVAL;
571 : }
572 0 : if (!past_kr)
573 0 : return 0;
574 :
575 0 : if (past_kr->base_key_kvno == new_kr->base_key_kvno) {
576 : /*
577 : * The new base keys can be the same as the old, but must have
578 : * different kvnos. (Well, not must must. It's a convention for now.)
579 : */
580 0 : krb5_set_error_message(context, EINVAL,
581 : "Base key version numbers for KRs must differ");
582 0 : return EINVAL;
583 : }
584 0 : if (new_kr->epoch - past_kr->epoch <= 0) {
585 0 : krb5_set_error_message(context, EINVAL,
586 : "New key rotation periods must start later "
587 : "than existing ones");
588 0 : return EINVAL;
589 : }
590 :
591 0 : last_kvno = 1 + ((new_kr->epoch - past_kr->epoch) / past_kr->period);
592 0 : if (new_kr->base_kvno <= last_kvno) {
593 0 : krb5_set_error_message(context, EINVAL,
594 : "New key rotation base kvno must be larger "
595 : "than the last kvno for the current key "
596 : "rotation (%u)", last_kvno);
597 0 : return EINVAL;
598 : }
599 0 : return 0;
600 : }
601 :
602 : static int
603 0 : kr_eq(const KeyRotation *a, const KeyRotation *b)
604 : {
605 0 : return !!(
606 0 : a->epoch == b->epoch &&
607 0 : a->period == b->period &&
608 0 : a->base_kvno == b->base_kvno &&
609 0 : a->base_key_kvno == b->base_key_kvno &&
610 0 : KeyRotationFlags2int(a->flags) == KeyRotationFlags2int(b->flags)
611 : );
612 : }
613 :
614 : krb5_error_code
615 0 : hdb_validate_key_rotations(krb5_context context,
616 : const HDB_Ext_KeyRotation *existing,
617 : const HDB_Ext_KeyRotation *krs)
618 : {
619 0 : krb5_error_code ret = 0;
620 0 : size_t added = 0;
621 0 : size_t i;
622 :
623 0 : if ((!existing || !existing->len) && (!krs || !krs->len))
624 0 : return 0; /* Nothing to do; weird */
625 :
626 : /*
627 : * HDB_Ext_KeyRotation has to have 1..3 elements, and this is enforced by
628 : * the ASN.1 compiler and the code it generates. Nonetheless we'll check
629 : * that there's not zero elements.
630 : */
631 0 : if ((!krs || !krs->len)) {
632 : /*
633 : * NOTE: We can clear this on concrete principals with virtual keys
634 : * though. The caller can check for that case.
635 : */
636 0 : krb5_set_error_message(context, EINVAL,
637 : "Cannot clear key rotation metadata on "
638 : "virtual principal namespaces");
639 0 : ret = EINVAL;
640 : }
641 :
642 : /* Validate the new KRs by themselves */
643 0 : for (i = 0; ret == 0 && i < krs->len; i++) {
644 0 : ret = hdb_validate_key_rotation(context,
645 0 : i+1 < krs->len ? &krs->val[i+1] : 0,
646 0 : &krs->val[i]);
647 : }
648 0 : if (ret || !existing || !existing->len)
649 0 : return ret;
650 :
651 0 : if (existing->len == krs->len) {
652 : /* Check for no change */
653 0 : for (i = 0; i < krs->len; i++)
654 0 : if (!kr_eq(&existing->val[i], &krs->val[i]))
655 0 : break;
656 0 : if (i == krs->len)
657 0 : return 0; /* No change */
658 : }
659 :
660 : /*
661 : * Check that new KRs make sense in the context of the previous KRs.
662 : *
663 : * Permitted changes:
664 : *
665 : * - add one new KR in front
666 : * - drop old KRs
667 : *
668 : * Start by checking if we're adding a KR, then go on to check for dropped
669 : * KRs and/or last KR alteration.
670 : */
671 0 : if (existing->val[0].epoch == krs->val[0].epoch ||
672 0 : existing->val[0].base_kvno == krs->val[0].base_kvno) {
673 0 : if (!kr_eq(&existing->val[0], &krs->val[0])) {
674 0 : krb5_set_error_message(context, EINVAL,
675 : "Key rotation change not sensible");
676 0 : ret = EINVAL;
677 : }
678 : /* Key rotation *not* added */
679 : } else {
680 : /* Key rotation added; check it first */
681 0 : ret = hdb_validate_key_rotation(context,
682 0 : &existing->val[0],
683 0 : &krs->val[0]);
684 0 : added = 1;
685 : }
686 0 : for (i = 0; ret == 0 && i < existing->len && i + added < krs->len; i++)
687 0 : if (!kr_eq(&existing->val[i], &krs->val[i + added]))
688 0 : krb5_set_error_message(context, ret = EINVAL,
689 : "Only last key rotation may be truncated");
690 0 : return ret;
691 : }
692 :
693 : /* XXX We need a function to "revoke" the past */
694 :
695 : /**
696 : * This function adds a KeyRotation value to an entry, validating the
697 : * change. One of `entry' and `krs' must be NULL, and the other non-NULL, and
698 : * whichever is given will be altered.
699 : *
700 : * @param context Context
701 : * @param entry An HDB entry
702 : * @param krs A key rotation extension for hdb_entry
703 : * @param kr A new KeyRotation value
704 : *
705 : * @return Zero on success, an error otherwise.
706 : */
707 : krb5_error_code
708 0 : hdb_entry_add_key_rotation(krb5_context context,
709 : hdb_entry *entry,
710 : HDB_Ext_KeyRotation *krs,
711 : const KeyRotation *kr)
712 : {
713 0 : krb5_error_code ret;
714 0 : HDB_extension new_ext;
715 0 : HDB_extension *ext = &new_ext;
716 0 : KeyRotation tmp;
717 0 : size_t i, sz;
718 :
719 0 : if (kr->period < 1) {
720 0 : krb5_set_error_message(context, EINVAL,
721 : "Key rotation period cannot be zero");
722 0 : return EINVAL;
723 : }
724 :
725 0 : new_ext.mandatory = TRUE;
726 0 : new_ext.data.element = choice_HDB_extension_data_key_rotation;
727 0 : new_ext.data.u.key_rotation.len = 0;
728 0 : new_ext.data.u.key_rotation.val = 0;
729 :
730 0 : if (entry && krs)
731 0 : return EINVAL;
732 :
733 0 : if (entry) {
734 0 : ext = hdb_find_extension(entry, choice_HDB_extension_data_key_rotation);
735 0 : if (!ext)
736 0 : ext = &new_ext;
737 : } else {
738 0 : const KeyRotation *prev_kr = &krs->val[0];
739 0 : unsigned int last_kvno = 0;
740 :
741 0 : if (kr->epoch - prev_kr->epoch <= 0) {
742 0 : krb5_set_error_message(context, EINVAL,
743 : "New key rotation periods must start later "
744 : "than existing ones");
745 0 : return EINVAL;
746 : }
747 :
748 0 : if (kr->base_kvno <= prev_kr->base_kvno ||
749 0 : kr->base_kvno - prev_kr->base_kvno <=
750 0 : (last_kvno = 1 +
751 0 : ((kr->epoch - prev_kr->epoch) / prev_kr->period))) {
752 0 : krb5_set_error_message(context, EINVAL,
753 : "New key rotation base kvno must be larger "
754 : "than the last kvno for the current key "
755 : "rotation (%u)", last_kvno);
756 0 : return EINVAL;
757 : }
758 : }
759 :
760 : /* First, append */
761 0 : ret = add_HDB_Ext_KeyRotation(&ext->data.u.key_rotation, kr);
762 0 : if (ret)
763 0 : return ret;
764 :
765 : /* Rotate new to front */
766 0 : tmp = ext->data.u.key_rotation.val[ext->data.u.key_rotation.len - 1];
767 0 : sz = sizeof(ext->data.u.key_rotation.val[0]);
768 0 : memmove(&ext->data.u.key_rotation.val[1], &ext->data.u.key_rotation.val[0],
769 0 : (ext->data.u.key_rotation.len - 1) * sz);
770 0 : ext->data.u.key_rotation.val[0] = tmp;
771 :
772 : /* Drop too old entries */
773 0 : for (i = 3; i < ext->data.u.key_rotation.len; i++)
774 0 : free_KeyRotation(&ext->data.u.key_rotation.val[i]);
775 0 : ext->data.u.key_rotation.len =
776 0 : ext->data.u.key_rotation.len > 3 ? 3 : ext->data.u.key_rotation.len;
777 :
778 0 : if (ext != &new_ext)
779 0 : return 0;
780 :
781 : /* Install new extension */
782 0 : if (ret == 0 && entry)
783 0 : ret = hdb_replace_extension(context, entry, ext);
784 0 : free_HDB_extension(&new_ext);
785 0 : return ret;
786 : }
|