1 /*
2  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
10  *
11  *	Openvision retains the copyright to derivative works of
12  *	this source code.  Do *NOT* create a derivative of this
13  *	source code before consulting with your legal department.
14  *	Do *NOT* integrate *ANY* of this source code into another
15  *	product before consulting with your legal department.
16  *
17  *	For further information, read the top-level Openvision
18  *	copyright which is contained in the top-level MIT Kerberos
19  *	copyright.
20  *
21  * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
22  *
23  */
24 
25 
26 /*
27  * admin/create/kdb5_create.c
28  *
29  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
30  * All Rights Reserved.
31  *
32  * Export of this software from the United States of America may
33  *   require a specific license from the United States Government.
34  *   It is the responsibility of any person or organization contemplating
35  *   export to obtain such a license before exporting.
36  *
37  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
38  * distribute this software and its documentation for any purpose and
39  * without fee is hereby granted, provided that the above copyright
40  * notice appear in all copies and that both that copyright notice and
41  * this permission notice appear in supporting documentation, and that
42  * the name of M.I.T. not be used in advertising or publicity pertaining
43  * to distribution of the software without specific, written prior
44  * permission.  Furthermore if you modify this software you must label
45  * your software as modified software and not distribute it in such a
46  * fashion that it might be confused with the original M.I.T. software.
47  * M.I.T. makes no representations about the suitability of
48  * this software for any purpose.  It is provided "as is" without express
49  * or implied warranty.
50  *
51  *
52  * Generate (from scratch) a Kerberos KDC database.
53  */
54 
55 /*
56  *  Yes, I know this is a hack, but we need admin.h without including the
57  *  rpc.h header. Additionally, our rpc.h header brings in
58  *  a des.h header which causes other problems.
59  */
60 #define	_RPC_RPC_H
61 
62 #include <stdio.h>
63 #include <k5-int.h>
64 #include <krb5/kdb.h>
65 #include <kadm5/server_internal.h>
66 #include <kadm5/admin.h>
67 #include <rpc/types.h>
68 #include <rpc/xdr.h>
69 #include <libintl.h>
70 #include "kdb5_util.h"
71 
72 enum ap_op {
73     NULL_KEY,				/* setup null keys */
74     MASTER_KEY,				/* use master key as new key */
75     TGT_KEY				/* special handling for tgt key */
76 };
77 
78 krb5_key_salt_tuple def_kslist = { ENCTYPE_DES_CBC_CRC, KRB5_KDB_SALTTYPE_NORMAL };
79 
80 struct realm_info {
81     krb5_deltat max_life;
82     krb5_deltat max_rlife;
83     krb5_timestamp expiration;
84     krb5_flags flags;
85     krb5_keyblock *key;
86     krb5_int32 nkslist;
87     krb5_key_salt_tuple *kslist;
88 } rblock = { /* XXX */
89     KRB5_KDB_MAX_LIFE,
90     KRB5_KDB_MAX_RLIFE,
91     KRB5_KDB_EXPIRATION,
92     KRB5_KDB_DEF_FLAGS,
93     (krb5_keyblock *) NULL,
94     1,
95     &def_kslist
96 };
97 
98 struct iterate_args {
99     krb5_context	ctx;
100     struct realm_info	*rblock;
101     krb5_db_entry	*dbentp;
102 };
103 
104 static krb5_error_code add_principal
105 	(krb5_context,
106 	 krb5_principal,
107 	 enum ap_op,
108 	 struct realm_info *,
109 		krb5_keyblock *);
110 
111 /*
112  * Steps in creating a database:
113  *
114  * 1) use the db calls to open/create a new database
115  *
116  * 2) get a realm name for the new db
117  *
118  * 3) get a master password for the new db; convert to an encryption key.
119  *
120  * 4) create various required entries in the database
121  *
122  * 5) close & exit
123  */
124 
125 extern krb5_principal master_princ;
126 
127 krb5_data tgt_princ_entries[] = {
128 	{0, KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME},
129 	{0, 0, 0} };
130 
131 krb5_data db_creator_entries[] = {
132 	{0, sizeof("db_creation")-1, "db_creation"} };
133 
134 /* XXX knows about contents of krb5_principal, and that tgt names
135  are of form TGT/REALM@REALM */
136 krb5_principal_data tgt_princ = {
137         0,					/* magic number */
138 	{0, 0, 0},				/* krb5_data realm */
139 	tgt_princ_entries,			/* krb5_data *data */
140 	2,					/* int length */
141 	KRB5_NT_SRV_INST			/* int type */
142 };
143 
144 krb5_principal_data db_create_princ = {
145         0,					/* magic number */
146 	{0, 0, 0},				/* krb5_data realm */
147 	db_creator_entries,			/* krb5_data *data */
148 	1,					/* int length */
149 	KRB5_NT_SRV_INST			/* int type */
150 };
151 
152 extern char *mkey_password;
153 
154 extern char *progname;
155 extern int exit_status;
156 extern kadm5_config_params global_params;
157 extern krb5_context util_context;
158 
159 void kdb5_create(argc, argv)
160    int argc;
161    char *argv[];
162 {
163     int optchar;
164 
165     krb5_error_code retval;
166     char *mkey_fullname;
167     char *pw_str = 0;
168     unsigned int pw_size = 0;
169     int do_stash = 0;
170     krb5_data pwd, seed;
171     kdb_log_context *log_ctx;
172     krb5_keyblock mkey;
173     krb5_data master_salt = { 0, NULL };
174 
175     if (strrchr(argv[0], '/'))
176 	argv[0] = strrchr(argv[0], '/')+1;
177 
178     while ((optchar = getopt(argc, argv, "s")) != -1) {
179 	switch(optchar) {
180 	case 's':
181 	    do_stash++;
182 	    break;
183 	case 'h':
184 	    if (!add_db_arg("hash=true")) {
185 		com_err(progname, ENOMEM, "while parsing command arguments\n");
186 		exit(1);
187 	    }
188 	    break;
189 	case '?':
190 	default:
191 	    usage();
192 	    return;
193 	}
194     }
195 
196     rblock.max_life = global_params.max_life;
197     rblock.max_rlife = global_params.max_rlife;
198     rblock.expiration = global_params.expiration;
199     rblock.flags = global_params.flags;
200     rblock.nkslist = global_params.num_keysalts;
201     rblock.kslist = global_params.keysalts;
202 
203     log_ctx = util_context->kdblog_context;
204 
205 /* SUNW14resync XXX */
206 #if 0
207     printf ("Loading random data\n");
208     retval = krb5_c_random_os_entropy (util_context, 1, NULL);
209     if (retval) {
210       com_err (argv[0], retval, "Loading random data");
211       exit_status++; return;
212     }
213 #endif
214     /* assemble & parse the master key name */
215 
216     if ((retval = krb5_db_setup_mkey_name(util_context,
217 					  global_params.mkey_name,
218 					  global_params.realm,
219 					  &mkey_fullname, &master_princ))) {
220 	com_err(argv[0], retval,
221 			gettext("while setting up master key name"));
222 	exit_status++; return;
223     }
224 
225     krb5_princ_set_realm_data(util_context, &db_create_princ, global_params.realm);
226     krb5_princ_set_realm_length(util_context, &db_create_princ, strlen(global_params.realm));
227     krb5_princ_set_realm_data(util_context, &tgt_princ, global_params.realm);
228     krb5_princ_set_realm_length(util_context, &tgt_princ, strlen(global_params.realm));
229     krb5_princ_component(util_context, &tgt_princ,1)->data = global_params.realm;
230     krb5_princ_component(util_context, &tgt_princ,1)->length = strlen(global_params.realm);
231 
232 	printf(gettext("Initializing database '%s' for realm '%s',\n"
233 			"master key name '%s'\n"),
234 	   global_params.dbname, global_params.realm, mkey_fullname);
235 
236     if (!mkey_password) {
237 	printf(gettext("You will be prompted for the "
238 			"database Master Password.\n"));
239 	printf(gettext("It is important that you NOT FORGET this password.\n"));
240 	fflush(stdout);
241 
242 	pw_size = 1024;
243 	pw_str = malloc(pw_size);
244 
245 	retval = krb5_read_password(util_context,
246 			    gettext("Enter KDC database master key"),
247 			    gettext("Re-enter KDC database "
248 				    "master key to verify"),
249 			    pw_str, &pw_size);
250 	if (retval) {
251 	    com_err(argv[0], retval,
252 		    gettext("while reading master key from keyboard"));
253 	    exit_status++; return;
254 	}
255 	mkey_password = pw_str;
256     }
257 
258     pwd.data = mkey_password;
259     pwd.length = strlen(mkey_password);
260     retval = krb5_principal2salt(util_context, master_princ, &master_salt);
261     if (retval) {
262 	com_err(argv[0], retval,
263 		gettext("while calculated master key salt"));
264 	exit_status++;
265 	goto cleanup;
266     }
267 
268     retval = krb5_c_string_to_key(util_context, global_params.enctype,
269 				  &pwd, &master_salt, &mkey);
270     if (retval) {
271 	com_err(argv[0], retval,
272 	    gettext("while transforming master key from password"));
273 	exit_status++;
274 	goto cleanup;
275     }
276 
277     retval = krb5_copy_keyblock(util_context, &mkey, &rblock.key);
278     if (retval) {
279 	com_err(argv[0], retval, gettext("while copying master key"));
280 	exit_status++;
281 	goto cleanup;
282     }
283 
284     seed.length = mkey.length;
285     seed.data = (char *)mkey.contents;
286 
287     if ((retval = krb5_c_random_seed(util_context, &seed))) {
288 	com_err(argv[0], retval,
289 		gettext("while initializing random key generator"));
290 	exit_status++;
291 	goto cleanup;
292     }
293     if ((retval = krb5_db_create(util_context, db5util_db_args))) {
294 	com_err(argv[0], retval,
295 		gettext("while creating database '%s'"),
296 		global_params.dbname);
297 	exit_status++;
298 	goto cleanup;
299     }
300 #if 0 /************** Begin IFDEF'ed OUT *******************************/
301     if (retval = krb5_db_fini(util_context)) {
302 	com_err(argv[0], retval,
303 		gettext("while closing current database"));
304 	exit_status++;
305 	goto cleanup;
306     }
307     if ((retval = krb5_db_set_name(util_context, global_params.dbname))) {
308 	com_err(argv[0], retval,
309 		gettext("while setting active database to '%s'"),
310                global_params.dbname);
311 	exit_status++;
312 	goto cleanup;
313     }
314     if ((retval = krb5_db_init(util_context))) {
315 	com_err(argv[0], retval,
316 		gettext("while initializing the database '%s'"),
317 	global_params.dbname);
318 	exit_status++;
319 	goto cleanup;
320     }
321 #endif /**************** END IFDEF'ed OUT *******************************/
322 
323     /* Solaris Kerberos: for iprop */
324     if (log_ctx && log_ctx->iproprole) {
325 	if (retval = ulog_map(util_context, &global_params, FKCOMMAND)) {
326 		com_err(argv[0], retval,
327 			gettext("while creating update log"));
328 		exit_status++;
329 		goto cleanup;
330 	}
331 
332 	/*
333 	 * We're reinitializing the update log in case one already
334 	 * existed, but this should never happen.
335 	 */
336 	(void) memset(log_ctx->ulog, 0, sizeof (kdb_hlog_t));
337 
338 	log_ctx->ulog->kdb_hmagic = KDB_HMAGIC;
339 	log_ctx->ulog->db_version_num = KDB_VERSION;
340 	log_ctx->ulog->kdb_state = KDB_STABLE;
341 	log_ctx->ulog->kdb_block = ULOG_BLOCK;
342 
343 	/*
344 	 * Since we're creating a new db we shouldn't worry about
345 	 * adding the initial principals since any slave might as well
346 	 * do full resyncs from this newly created db.
347 	 */
348 	log_ctx->iproprole = IPROP_NULL;
349     }
350 
351     if ((retval = add_principal(util_context, master_princ, MASTER_KEY, &rblock, &mkey)) ||
352 	(retval = add_principal(util_context, &tgt_princ, TGT_KEY, &rblock, &mkey))) {
353 	(void) krb5_db_fini(util_context);
354 	com_err(argv[0], retval, gettext("while adding entries to the database"));
355 	exit_status++;
356 	goto cleanup;
357     }
358     /*
359      * Always stash the master key so kadm5_create does not prompt for
360      * it; delete the file below if it was not requested.  DO NOT EXIT
361      * BEFORE DELETING THE KEYFILE if do_stash is not set.
362      */
363     retval = krb5_db_store_master_key(util_context,
364 				      global_params.stash_file,
365 				      master_princ,
366 				      &mkey,
367 				      mkey_password);
368 
369     if (retval) {
370 	com_err(argv[0], errno, gettext("while storing key"));
371 	printf(gettext("Warning: couldn't stash master key.\n"));
372     }
373 
374     if (pw_str)
375 	memset(pw_str, 0, pw_size);
376 
377     if (kadm5_create(&global_params)) {
378 	 if (!do_stash) unlink(global_params.stash_file);
379 	 exit_status++;
380 	 goto cleanup;
381     }
382     if (!do_stash) unlink(global_params.stash_file);
383 
384 /* Solaris Kerberos: deal with master_keyblock in better way */
385 cleanup:
386     if (pw_str) {
387 	if (mkey_password == pw_str)
388 		mkey_password = NULL;
389 	free(pw_str);
390     }
391     if (master_salt.data)
392 	free(master_salt.data);
393     krb5_free_keyblock(util_context, rblock.key);
394     krb5_free_keyblock_contents(util_context, &mkey);
395     (void) krb5_db_fini(util_context);
396 
397     return;
398 }
399 
400 static krb5_error_code
401 tgt_keysalt_iterate(ksent, ptr)
402     krb5_key_salt_tuple	*ksent;
403     krb5_pointer	ptr;
404 {
405     krb5_context	context;
406     krb5_error_code	kret;
407     struct iterate_args	*iargs;
408     krb5_keyblock	key;
409     krb5_int32		ind;
410     krb5_data	pwd;
411 
412     iargs = (struct iterate_args *) ptr;
413     kret = 0;
414 
415     context = iargs->ctx;
416 
417     /*
418      * Convert the master key password into a key for this particular
419      * encryption system.
420      */
421     pwd.data = mkey_password;
422     pwd.length = strlen(mkey_password);
423     kret = krb5_c_random_seed(context, &pwd);
424     if (kret)
425 	return kret;
426 
427     if (!(kret = krb5_dbe_create_key_data(iargs->ctx, iargs->dbentp))) {
428 	ind = iargs->dbentp->n_key_data-1;
429 	if (!(kret = krb5_c_make_random_key(context, ksent->ks_enctype,
430 					    &key))) {
431 	    kret = krb5_dbekd_encrypt_key_data(context,
432 					       iargs->rblock->key,
433 					       &key,
434 					       NULL,
435 					       1,
436 					       &iargs->dbentp->key_data[ind]);
437 	    krb5_free_keyblock_contents(context, &key);
438 	}
439     }
440 
441     return(kret);
442 }
443 
444 static krb5_error_code
445 add_principal(context, princ, op, pblock, mkey)
446     krb5_context context;
447     krb5_principal princ;
448     enum ap_op op;
449     struct realm_info *pblock;
450     krb5_keyblock *mkey;
451 {
452     krb5_error_code 	  retval;
453     krb5_db_entry 	  entry;
454 
455     krb5_timestamp	  now;
456     struct iterate_args	  iargs;
457 
458     int			  nentries = 1;
459 
460     memset((char *) &entry, 0, sizeof(entry));
461 
462     entry.len = KRB5_KDB_V1_BASE_LENGTH;
463     entry.attributes = pblock->flags;
464     entry.max_life = pblock->max_life;
465     entry.max_renewable_life = pblock->max_rlife;
466     entry.expiration = pblock->expiration;
467 
468     if ((retval = krb5_copy_principal(context, princ, &entry.princ)))
469 	goto error_out;
470 
471     if ((retval = krb5_timeofday(context, &now)))
472 	goto error_out;
473 
474     if ((retval = krb5_dbe_update_mod_princ_data(context, &entry,
475 						 now, &db_create_princ)))
476 	goto error_out;
477 
478     switch (op) {
479     case MASTER_KEY:
480 	if ((entry.key_data=(krb5_key_data*)malloc(sizeof(krb5_key_data)))
481 	    == NULL)
482 	    goto error_out;
483 	memset((char *) entry.key_data, 0, sizeof(krb5_key_data));
484 	entry.n_key_data = 1;
485 
486 	entry.attributes |= KRB5_KDB_DISALLOW_ALL_TIX;
487 	if ((retval = krb5_dbekd_encrypt_key_data(context, pblock->key,
488 						  mkey, NULL,
489 						  1, entry.key_data)))
490 	    goto error_out;
491 	break;
492     case TGT_KEY:
493 	iargs.ctx = context;
494 	iargs.rblock = pblock;
495 	iargs.dbentp = &entry;
496 	/*
497 	 * Iterate through the key/salt list, ignoring salt types.
498 	 */
499 	if ((retval = krb5_keysalt_iterate(pblock->kslist,
500 					   pblock->nkslist,
501 					   1,
502 					   tgt_keysalt_iterate,
503 					   (krb5_pointer) &iargs)))
504 	    return retval;
505 	break;
506     case NULL_KEY:
507 	return EOPNOTSUPP;
508     default:
509 	break;
510     }
511 
512     entry.mask = (KADM5_KEY_DATA | KADM5_PRINCIPAL | KADM5_ATTRIBUTES |
513 	KADM5_MAX_LIFE | KADM5_MAX_RLIFE | KADM5_TL_DATA |
514 	KADM5_PRINC_EXPIRE_TIME);
515 
516     retval = krb5_db_put_principal(context, &entry, &nentries);
517 
518 error_out:;
519     krb5_db_free_principal(context, &entry, 1);
520     return retval;
521 }
522