aboutsummaryrefslogtreecommitdiff
path: root/doc/kadm5/api-server-design.tex
blob: b330e9ce096b9605d4dfd6eb85ed30c84cc929cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
\documentstyle[12pt,fullpage,changebar]{article}

% $Id$

\setlength{\parskip}{.7\baselineskip}
\setlength{\parindent}{0pt}

\def\secure{OV*Secure}
\def\v#1{\verb+#1+}
\def\k#1{K$_#1$}

\title{OV*Secure Admin Server \\ Implementation Design}
\author{Barry Jaspan}
\date{DRAFT --- \today}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Make _ actually generate an _, and allow line-breaking after it.
\let\underscore=\_
\catcode`_=13
\def_{\underscore\penalty75\relax}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{document}

\maketitle

{\setlength{\parskip}{0pt}\tableofcontents}

\section{Overview}

The admin server is implemented as a nearly-stateless transaction
server, where each admin API function represents a single transaction.
No per-client or per-connection information is stored; only local
database handles are maintained between requests.

The admin API is exported via an RPC interface that hides all details
about network encoding, authentication, and encryption of data on the
wire.  The RPC mechanism does, however, allow the server to access the
underlying authentication credentials for authorization purposes.

The admin server accesses a total of three databases.

\begin{itemize}
\item The master Kerberos database is used to store all the
information that the Kerberos server understands, thus allowing the
greatest functionality with no modifications to a standard KDC.  

\item The admin principal database stores \secure{}-specific per-principal
information. 

\item The policy database stores \secure{} policy information.
\end{itemize}

The per-principal information stored in the admin principal database
consists of the principal's policy name and an array of the
principal's previous keys.  The old keys are stored encrypted in the
key of the special principal ``kadmin/history'' that is created by
ovsec_kadm_create.  Since a change in kadmin/history's key renders
every principal's key history array useless, it can only be changed
using the ovsec_kadm_edit utility; that program will reencrypt every
principal's key history in the new key.  The admin server refuses all
requests to change kdamin/history's key.

\section{Main}

The admin server starts by trapping all fatal signals and directing
them to a cleanup-and-exit function.  It then creates and exports the
RPC interface and enters its main loop.

The main loop dispatches all incoming requests to the RPC mechanism.
After 15 seconds of inactivity, the server closes all open databases;
each database will be automatically reopened by the API function
implementations as necessary.

\section{Remote Procedure Calls}

The RPC for the Admin system will be based on SUNRPC.  SUNRPC is used
because it is a well-known, portable RPC mechanism.  The underlying
external data representation (xdr) mechanisms for wire encapsulation
are well-known and extensible.

Authentication to the admin server will be handled by adding a GSS-API
authentication type within the existing SUNRPC structure.  This will
require code modifications to SUNRPC, but the API and wire protocol do
not need to change.  This may affect whether the RPC will use UDP or
TCP; although all the admin functions are stateless, the GSS-API
authentication binding will not be and it might be easier to use TCP
for this reason.

\section{Database Record Types}
\label{sec:db-types}

\subsection{Admin Principal, osa_princ_ent_t}

The admin principal database stores records of the type
osa_princ_ent_t (declared in $<$ovsec_admin/adb.h$>$), which is the
subset of the ovsec_kadm_principal_ent_t structure that is not stored
in the Kerberos database plus the necessary bookkeeping information.
The records are keyed by the ASCII representation of the principal's
name, including the trailing NULL.

\begin{verbatim}
typedef struct _osa_princ_ent_t {
        krb5_principal name;

        char * policy;
        u_int32 aux_attributes;

        u_int32 num_old_keys;
        u_int32 next_old_key;
        krb5_kvno admin_history_kvno;
        krb5_encrypted_keyblock *old_keys;
} osa_princ_ent_rec, *osa_princ_ent_t;
\end{verbatim}

The fields that are different from ovsec_kadm_principal_ent_t are:

\begin{description}
\item[num_old_keys] The number of previous keys in the old_keys array.
This value must be 0 $\le$ num_old_keys $<$ pw_history_num.

\item[next_old_key] The index into old_keys where the next key should
be inserted.  This value must be 0 $\le$ next_old_key $\le$
num_old_keys.

\item[admin_history_kvno] The key version number of the admin/history
principal's key used to encrypt the values in old_keys.  If the admin
server finds that admin/history's kvno is different from the value in
this field, an error message is logged.  (XXX where?)

\item[old_keys] The array of the principal's previous keys, each
encrypted in the admin/history key.  There are num_old_keys elements.
\end{description}

\subsection{Policy, osa_policy_ent_t}

The policy database stores records of the type osa_policy_ent_t
(declared in $<$ovsec_admin/adb.h$>$) , which is all of
ovsec_kadm_policy_ent_t plus necessary bookkeeping information.  The
records are keyed by the policy name.

\begin{verbatim}
typedef struct _osa_policy_ent_t {
        char *policy;

        u_int32 pw_min_life;
        u_int32 pw_max_life;
        u_int32 pw_min_length;
        u_int32 pw_min_classes;
        u_int32 pw_history_num;

        u_int32 refcnt;
} osa_policy_ent_rec, *osa_policy_ent_t;
\end{verbatim}

\subsection{Kerberos, krb5_db_entry}

The Kerberos database stores records of type krb5_db_entry, which is
defined in the $<$krb5/kdb.h$>$ header file.

\begin{verbatim}
typedef struct _krb5_encrypted_keyblock {
    krb5_keytype keytype;
    int length;
    krb5_octet *contents;
} krb5_encrypted_keyblock;

typedef struct _krb5_db_entry {
    krb5_principal principal;
    krb5_encrypted_keyblock key;
    krb5_kvno kvno;
    krb5_deltat max_life;
    krb5_deltat max_renewable_life;
    krb5_kvno mkvno;
    
    krb5_timestamp expiration;
    krb5_timestamp pw_expiration;
    krb5_timestamp last_pwd_change;
    krb5_timestamp last_success;   
    
    krb5_timestamp last_failed;
    krb5_kvno fail_auth_count;
    
    krb5_principal mod_name;
    krb5_timestamp mod_date;
    krb5_flags attributes;
    krb5_int32 salt_type:8,
               salt_length:24;
    krb5_octet *salt;
    krb5_encrypted_keyblock alt_key;
    krb5_int32 alt_salt_type:8,
               alt_salt_length:24;
    krb5_octet *alt_salt;
    
    krb5_int32 expansion[8];
} krb5_db_entry;
\end{verbatim}

The interpretation of most of these fields is the same as given in the
``Principals, ovsec_kadm_principal_ent_t'' section of the functional
specification.  The fields that are not defined there are not used by
\secure{}; however, the admin server preserves the value of any fields
it does not understand.

\section{Database Access Methods}

\subsection{Principal and Policy Databases}

This section describes the database abstraction used for the admin
principal and policy databases.  Since both databases export
equivalent functionality, the API is only described once.  The
character T is used to represent both ``princ'' and ``policy''. The
location of the principal database is defined by the \#define
PRINCIPAL_DB (``/krb5/ovsec_principal.db'') in $<$ovsec_admin/adb.h$>$. The
location of the policy database is defined by the \#define POLICY_DB
(``/krb5/ovsec_policy.db'') in $<$ovsec_admin/adb.h$>$.

Note that this is {\it only} a database abstraction.  All functional
intelligence, such as maintaining policy reference counts or sanity
checking, must be implemented above this layer.

Prototypes for the osa functions are supplied in
$<$ovsec_admin/adb.h$>$. The routines can be found (in the first
relase) in ``stage/lib/libadb.a''. They require linking with the
Berkely DB library (``stage/lib/libdb.a''). [Note: We needed to remove
the dbm compatibility routines from libdb.a because we want to leave
KDB library alone in case somebody wants to run a stock MIT KDC with
our admin server.]

The database routines use com_err for error codes.  The error code
table name is ``adb'' and the offsets are the same as the order
presented here. The error table header file is
$<$ovsec_admin/adb_err.h$>$. Callers of the OSA routines should first call
init_adb_err_tbl() to initialize the database table.

\begin{description}
\item[OSA_ADB_OK] Operation successful.
\item[OSA_ADB_FAILURE] General failure.
\item[OSA_ADB_DUP] Operation would create a duplicate database entry.
\item[OSA_ADB_NOENT] Named entry not in database.
\item[OSA_ADB_BAD_PRINC] The krb5_principal structure is invalid.
\item[OSA_ADB_BAD_POLICY] The specified policy name is invalid.
\item[OSA_ADB_XDR_FAILURE] The principal or policy structure cannot be
encoded for storage.
\end{description}

Database functions can also return system errors.  Unless otherwise
specified, database functions return OSA_ADB_OK.

\begin{verbatim}
osa_adb_ret_t
osa_adb_open_T(osa_adb_T_t *db, char *filename);
\end{verbatim}
%
Open the database named filename.  Returns OSA_ADB_FAILURE if it
cannot open the database.

\begin{verbatim}
osa_adb_ret_t
osa_adb_close_T(osa_adb_T_t db);
\end{verbatim}
%
Close an open database.

\begin{verbatim}
osa_adb_ret_t
osa_adb_create_T(osa_adb_T_t db, osa_T_ent_t entry);
\end{verbatim}
%
Adds the entry to the database.  All fields are defined.  Returns
OSA_ADB_DUP if it already exists.

\begin{verbatim}
osa_adb_ret_t
osa_adb_destroy_T(osa_adb_T_t db, osa_T_t name);
\end{verbatim}

Removes the named entry from the database.  Returns OSA_ADB_NOENT if
it does not exist.

\begin{verbatim}
osa_adb_ret_t
osa_adb_get_T(osa_adb_T_t db, osa_T_t name,
        osa_princ_ent_t *entry); 
\end{verbatim}

Looks up the named entry in the db, and returns it in *entry in
allocated storage that must be freed with osa_adb_free_T.  Returns
OSA_ADB_NOENT if name does not exist, OSA_ADB_MEM if memory cannot be
allocated.

\begin{verbatim}
osa_adb_ret_t
osadb_adb_put_T(osa_adb_T_t db, osa_T_ent_t entry);
\end{verbatim}

Modifies the existing entry named in entry.  All fields must be filled
in.  Returns OSA_DB_NOENT if the named entry does not exist.  Note
that this cannot be used to rename an entry; rename is implemented by
deleting the old name and creating the new one (NOT ATOMIC!).

\begin{verbatim}
void osa_adb_free_T(osa_T_ent_t);
\end{verbatim}

Frees the memory associated with an osa_T_ent_t allocated by
osa_adb_get_T.

\begin{verbatim}
typedef osa_adb_ret_t (*osa_adb_iter_T_func)(void *data,
				    osa_T_ent_t entry);

osa_adb_ret_t osa_adb_iter_T(osa_adb_T_t db, osa_adb_iter_T_func func, 
		    void *data);
\end{verbatim}

Iterates over every entry in the database.  For each entry ent in the
database db, the function (*func)(data, ent) is called.  If func
returns an error code, osa_adb_iter_T returns an error code.  If all
invokations of func return OSA_ADB_OK, osa_adb_iter_T returns
OSA_ADB_OK.  The function func is permitted to access the database,
but the consequences of modifying the database during the iteration
are undefined.

\subsection{Kerberos Database}

Kerberos uses dbm to store krb5_db_entry records.  It can be accessed
and modified in parallel with the Kerberos server, using functions
that are defined inside the KDC and the libkdb.a.

\subsubsection{Database Manipulation Functions}

The following functions are declared in \v{lib/kdb/kdb_dbm.c} in the
Kerberos sources and are available in libkdb.a.  They can return the
following error codes; error codes that can be returned by any
function are indicated with a ``*'' and are not listed specifically
for each function.

\begin{description}
\item[* KRB5_KDB_NOTINITED] The database is not open; call
krb5_dbm_db_init.
\item[* KRB5_KDB_CANTLOCK_DB] The necessary lock cannot be acquired.  Try
again later.
\item[* system errors] An error occurred accessing the database files.
\item[KRB5_KDB_DB_INUSE] The database was modified without the use
of proper locking.\footnote{This error occurs when the entire database
is swapped out from the under the process, say by a kdb5_edit restore.
It can only be returned by krb5_db_get_principal.  It is not yet clear
what a program should do when it gets this error.}
\item[KRB5_KDB_NOENTRY] The principal to be deleted is not
in the database.
\end{description}

\begin{verbatim}
krb5_dbm_db_init(void)
\end{verbatim}

Opens the Kerberos database file (but does not actually call
dbm_open).  This can be called even if the database is already open,
in which case it just returns success.

\begin{verbatim}
krb5_dbm_db_fini(void)
\end{verbatim}

Closes the database file; this MUST be called before the process
exits.  Returns KRB5_KDB_DBNOTINITED if the database isn't open, but
that isn't really a fatal error.

\begin{verbatim}
krb5_dbm_get_principal(krb5_principal searchfor, 
        krb5_db_entry *entries, int *nentries, krb5_boolean *more)
\end{verbatim}

Search the database for the principal searchfor and write the results
into *entries.  The interface is set up to handle wildcard gets, but
the code doesn't handle it: *nentries is assumed to be 1, and *more is
always returned as 0.

This function does not retry if the database cannot be locked; that is
up to the caller.  

Returns KRB5_KDB_DB_INUSE.

\begin{verbatim}
krb5_dbm_put_principal(krb5_db_entry *entries, int *nentries)
\end{verbatim}

Stores *nentries elements from the entries array into the database.
On return *nentries is set to the number of entries actually written;
the first *nentries entries will have been written, even if an error
pis returned.

This function does not retry if the database cannot be locked; that is
up to the caller.

\begin{verbatim}
krb5_dbm_db_delete_principal(krb5_principal searchfor, int *nentries)
\end{verbatim}

Removes the principal searchfor from the database.  nentries will be
set to 0 or 1 on output, indicating the number of entries deleted (the
code does not currently support wildcards).

Returns KRB5_KDB_NOENTRY.

\begin{verbatim}
typedef krb5_error_code (*iter_func)(krb5_pointer, krb5_db_entry *);

krb5_dbm_db_iterate(iter_func func, krb5_point func_arg)
\end{verbatim}

Calls (*func)(func_arg, entry) for every entry in the database.  If
func returns an error code, the iteration stops and that error code is
returned.

Returns func error codes.

\begin{verbatim}
void krb5_dbm_db_free_principal(krb5_db_entry *entries, int nentries)
\end{verbatim}

Frees entries returned by krb5_dbm_db_get_principal.  nentries entries
in the array entries will be freed.

\subsubsection{Initialization and Key Access}

Keys stored in the Kerberos database are encrypted in the Kerberos
master key.  The admin server will therefore have to acquire the key
before it can perform any key-changing operations, and will have to
decrypt and encrypt the keys retrieved from and placed into the
database via krb5_db_get_principal and _put_principal.  This section
describes the internal admin server API that will be used to perform
these functions.

\begin{verbatim}
krb5_principal master_princ;
krb5_encrypt_block master_encblock;
krb5_keyblock master_keyblock;

void kdc_init_master()
\end{verbatim}

kdc_init_master opens the database and acquires the master key.  It
also sets the global variables master_princ, master_encblock, and
master_keyblock:

\begin{itemize}
\item master_princ is set to the name of the Kerberos master principal
(\v{K/M@REALM}).

\item master_encblock is something I have no idea about.

\item master_keyblock is the Kerberos master key
\end{itemize}

\begin{verbatim}
krb5_error_code kdb_get_entry_and_key(krb5_principal principal,
                                      krb5_db_entry *entry,
                                      krb5_keyblock *key)
\end{verbatim}

kdb_get_entry_and_key retrieves the named principal's entry from the
database in entry, and decrypts its key into key.  The caller must
free entry with krb5_dbm_db_free_principal and free key-$>$contents with
free.\footnote{The caller should also \v{memset(key-$>$contents, 0,
key-$>$length)}.  There should be a function krb5_free_keyblock_contents
for this, but there is not.}

\begin{verbatim}
krb5_error_code kdb_put_entry_pw(krb5_db_entry *entry, char *pw)
\end{verbatim}

kdb_put_entry_pw stores entry in the database.  All the entry values
must already be set; this function does not change any of them except
the key.  pw, the NULL-terminated password string, is converted to a
key using string-to-key with the salt type specified in
entry-$>$salt_type.\footnote{The salt_type should be set based on the
command line arguments to the kadmin server (see the ``Command Line''
section of the functional specification).}

\section{Admin Principal and Policy Database Implementation}

The admin principal and policy databases will each be stored in a
single hash table, implemented by the Berkeley 4.4BSD db library.
Each record will consist of an entire osa_T_ent_t.  The key into the
hash table is the entry name (for principals, the ASCII representation
of the name).  The value is the T entry structure.  Since the key and
data must be self-contained, with no pointers, the Sun xdr mechanisms
will be used to marshal and unmarshal data in the database.

The server in the first release will be single-threaded in that a
request will run to completion (or error) before the next will run,
but multiple connections will be allowed simultaneously.

\section{ACLs, acl_check}

The ACL mechanism described in the ``Authorization ACLs'' section of
the functional specifications will be implemented by the acl_check
function.

\begin{verbatim}
enum access_t {
        ACCESS_DENIED = 0,
        ACCESS_OK = 1,
};

enum access_t acl_check(krb5_principal princ, char *priv);
\end{verbatim}

The priv argument must be one of ``get'', ``add'', ``delete'', or
``modify''.  acl_check returns 1 if the principal princ has the named
privilege, 0 if it does not.

\section{Function Details}

This section discusses specific design issues for Admin API functions
that are not addresed by the functional specifications.

\subsection{ovsec_kadm_create_principal}

If the named principal exists in either the Kerberos or admin
principal database, but not both, return OVSEC_KADM_BAD_DB.

The principal's initial key is not stored in the key history array at
creation time.

\subsection{ovsec_kadm_delete_principal}

If the named principal exists in either the Kerberos or admin
principal database, but not both, return OVSEC_KADM_BAD_DB.

\subsection{ovsec_kadm_modify_principal}

If the named principal exists in either the Kerberos or admin
principal database, but not both, return OVSEC_KADM_BAD_DB.

If pw_history_num changes and the new value $n$ is smaller than the
current value of num_old_keys, old_keys should end up with the $n$
most recent keys; these are found by counting backwards $n$ elements
in old_keys from next_old_key.  next_old_keys should then be reset to
0, the oldest of the saved keys, and num_old_keys set to $n$, the
new actual number of old keys in the array.  

\subsection{ovsec_kadm_chpass_principal, randkey_principal}

The algorithm for determining whether a password is in the principal's
key history is complicated by the use of the kadmin/history \k{h}
encrypting key.  

\begin{enumerate}
\item For ovsec_kadm_chpass_principal, convert the password to a key
using string-to-key and the salt method specified by the command line
arguments.

\item If the POLICY bit is set and pw_history_num is not zero, check
if the new key is in the history.
\begin{enumerate}
\item Retrieve the principal's current key and decrypt it with \k{M}.
If it is the same as the new key, return OVSEC_KADM_PASS_REUSE.
\item Retrieve the kadmin/history key \k{h} and decrypt it with \k{M}.
\item Encrypt the principal's new key in \k{h}.
\item If the principal's new key encrypted in \k{h} is in old_keys,
return OVSEC_KADM_PASS_REUSE.
\item Encrypt the principal's current key in \k{h} and store it in
old_keys.
\item Erase the memory containing \k{h}.
\end{enumerate}

\item Encrypt the principal's new key in \k{M} and store it in the
database.
\item Erase the memory containing \k{M}.
\end{enumerate}

To store the an encrypted key in old_keys, insert it as the
next_old_key element of old_keys, and increment next_old_key by one
modulo pw_history_num.

\subsection{ovsec_kadm_get_principal}

If the named principal exists in either the Kerberos or admin
principal database, but not both, return OVSEC_KADM_BAD_DB.

\end{document}