xref: /freebsd/crypto/krb5/doc/appdev/init_creds.rst (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1Initial credentials
2===================
3
4Software that performs tasks such as logging users into a computer
5when they type their Kerberos password needs to get initial
6credentials (usually ticket granting tickets) from Kerberos.  Such
7software shares some behavior with the :ref:`kinit(1)` program.
8
9Whenever a program grants access to a resource (such as a local login
10session on a desktop computer) based on a user successfully getting
11initial Kerberos credentials, it must verify those credentials against
12a secure shared secret (e.g., a host keytab) to ensure that the user
13credentials actually originate from a legitimate KDC.  Failure to
14perform this verification is a critical vulnerability, because a
15malicious user can execute the "Zanarotti attack": the user constructs
16a fake response that appears to come from the legitimate KDC, but
17whose contents come from an attacker-controlled KDC.
18
19Some applications read a Kerberos password over the network (ideally
20over a secure channel), which they then verify against the KDC.  While
21this technique may be the only practical way to integrate Kerberos
22into some existing legacy systems, its use is contrary to the original
23design goals of Kerberos.
24
25The function :c:func:`krb5_get_init_creds_password` will get initial
26credentials for a client using a password.  An application that needs
27to verify the credentials can call :c:func:`krb5_verify_init_creds`.
28Here is an example of code to obtain and verify TGT credentials, given
29strings *princname* and *password* for the client principal name and
30password::
31
32    krb5_error_code ret;
33    krb5_creds creds;
34    krb5_principal client_princ = NULL;
35
36    memset(&creds, 0, sizeof(creds));
37    ret = krb5_parse_name(context, princname, &client_princ);
38    if (ret)
39        goto cleanup;
40    ret = krb5_get_init_creds_password(context, &creds, client_princ,
41                                       password, NULL, NULL, 0, NULL, NULL);
42    if (ret)
43        goto cleanup;
44    ret = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL, NULL);
45
46    cleanup:
47    krb5_free_principal(context, client_princ);
48    krb5_free_cred_contents(context, &creds);
49    return ret;
50
51Options for get_init_creds
52--------------------------
53
54The function :c:func:`krb5_get_init_creds_password` takes an options
55parameter (which can be a null pointer).  Use the function
56:c:func:`krb5_get_init_creds_opt_alloc` to allocate an options
57structure, and :c:func:`krb5_get_init_creds_opt_free` to free it.  For
58example::
59
60    krb5_error_code ret;
61    krb5_get_init_creds_opt *opt = NULL;
62    krb5_creds creds;
63
64    memset(&creds, 0, sizeof(creds));
65    ret = krb5_get_init_creds_opt_alloc(context, &opt);
66    if (ret)
67        goto cleanup;
68    krb5_get_init_creds_opt_set_tkt_life(opt, 24 * 60 * 60);
69    ret = krb5_get_init_creds_password(context, &creds, client_princ,
70                                       password, NULL, NULL, 0, NULL, opt);
71    if (ret)
72        goto cleanup;
73
74    cleanup:
75    krb5_get_init_creds_opt_free(context, opt);
76    krb5_free_cred_contents(context, &creds);
77    return ret;
78
79Getting anonymous credentials
80-----------------------------
81
82As of release 1.8, it is possible to obtain fully anonymous or
83partially anonymous (realm-exposed) credentials, if the KDC supports
84it.  The MIT KDC supports issuing fully anonymous credentials as of
85release 1.8 if configured appropriately (see :ref:`anonymous_pkinit`),
86but does not support issuing realm-exposed anonymous credentials at
87this time.
88
89To obtain fully anonymous credentials, call
90:c:func:`krb5_get_init_creds_opt_set_anonymous` on the options
91structure to set the anonymous flag, and specify a client principal
92with the KDC's realm and a single empty data component (the principal
93obtained by parsing ``@``\ *realmname*).  Authentication will take
94place using anonymous PKINIT; if successful, the client principal of
95the resulting tickets will be
96``WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS``.  Here is an example::
97
98    krb5_get_init_creds_opt_set_anonymous(opt, 1);
99    ret = krb5_build_principal(context, &client_princ, strlen(myrealm),
100                               myrealm, "", (char *)NULL);
101    if (ret)
102        goto cleanup;
103    ret = krb5_get_init_creds_password(context, &creds, client_princ,
104                                       password, NULL, NULL, 0, NULL, opt);
105    if (ret)
106        goto cleanup;
107
108To obtain realm-exposed anonymous credentials, set the anonymous flag
109on the options structure as above, but specify a normal client
110principal in order to prove membership in the realm.  Authentication
111will take place as it normally does; if successful, the client
112principal of the resulting tickets will be ``WELLKNOWN/ANONYMOUS@``\
113*realmname*.
114
115User interaction
116----------------
117
118Authenticating a user usually requires the entry of secret
119information, such as a password.  A password can be supplied directly
120to :c:func:`krb5_get_init_creds_password` via the *password*
121parameter, or the application can supply prompter and/or responder
122callbacks instead.  If callbacks are used, the user can also be
123queried for other secret information such as a PIN, informed of
124impending password expiration, or prompted to change a password which
125has expired.
126
127Prompter callback
128~~~~~~~~~~~~~~~~~
129
130A prompter callback can be specified via the *prompter* and *data*
131parameters to :c:func:`krb5_get_init_creds_password`.  The prompter
132will be invoked each time the krb5 library has a question to ask or
133information to present.  When the prompter callback is invoked, the
134*banner* argument (if not null) is intended to be displayed to the
135user, and the questions to be answered are specified in the *prompts*
136array.  Each prompt contains a text question in the *prompt* field, a
137*hidden* bit to indicate whether the answer should be hidden from
138display, and a storage area for the answer in the *reply* field.  The
139callback should fill in each question's ``reply->data`` with the
140answer, up to a maximum number of ``reply->length`` bytes, and then
141reset ``reply->length`` to the length of the answer.
142
143A prompter callback can call :c:func:`krb5_get_prompt_types` to get an
144array of type constants corresponding to the prompts, to get
145programmatic information about the semantic meaning of the questions.
146:c:func:`krb5_get_prompt_types` may return a null pointer if no prompt
147type information is available.
148
149Text-based applications can use a built-in text prompter
150implementation by supplying :c:func:`krb5_prompter_posix` as the
151*prompter* parameter and a null pointer as the *data* parameter.  For
152example::
153
154    ret = krb5_get_init_creds_password(context, &creds, client_princ,
155                                       NULL, krb5_prompter_posix, NULL, 0,
156                                       NULL, NULL);
157
158Responder callback
159~~~~~~~~~~~~~~~~~~
160
161A responder callback can be specified through the init_creds options
162using the :c:func:`krb5_get_init_creds_opt_set_responder` function.
163Responder callbacks can present a more sophisticated user interface
164for authentication secrets.  The responder callback is usually invoked
165only once per authentication, with a list of questions produced by all
166of the allowed preauthentication mechanisms.
167
168When the responder callback is invoked, the *rctx* argument can be
169accessed to obtain the list of questions and to answer them.  The
170:c:func:`krb5_responder_list_questions` function retrieves an array of
171question types.  For each question type, the
172:c:func:`krb5_responder_get_challenge` function retrieves additional
173information about the question, if applicable, and the
174:c:func:`krb5_responder_set_answer` function sets the answer.
175
176Responder question types, challenges, and answers are UTF-8 strings.
177The question type is a well-known string; the meaning of the challenge
178and answer depend on the question type.  If an application does not
179understand a question type, it cannot interpret the challenge or
180provide an answer.  Failing to answer a question typically results in
181the prompter callback being used as a fallback.
182
183Password question
184#################
185
186The :c:macro:`KRB5_RESPONDER_QUESTION_PASSWORD` (or ``"password"``)
187question type requests the user's password.  This question does not
188have a challenge, and the response is simply the password string.
189
190One-time password question
191##########################
192
193The :c:macro:`KRB5_RESPONDER_QUESTION_OTP` (or ``"otp"``) question
194type requests a choice among one-time password tokens and the PIN and
195value for the chosen token.  The challenge and answer are JSON-encoded
196strings, but an application can use convenience functions to avoid
197doing any JSON processing itself.
198
199The :c:func:`krb5_responder_otp_get_challenge` function decodes the
200challenge into a krb5_responder_otp_challenge structure.  The
201:c:func:`krb5_responder_otp_set_answer` function selects one of the
202token information elements from the challenge and supplies the value
203and pin for that token.
204
205PKINIT password or PIN question
206###############################
207
208The :c:macro:`KRB5_RESPONDER_QUESTION_PKINIT` (or ``"pkinit"``) question
209type requests PINs for hardware devices and/or passwords for encrypted
210credentials which are stored on disk, potentially also supplying
211information about the state of the hardware devices.  The challenge and
212answer are JSON-encoded strings, but an application can use convenience
213functions to avoid doing any JSON processing itself.
214
215The :c:func:`krb5_responder_pkinit_get_challenge` function decodes the
216challenges into a krb5_responder_pkinit_challenge structure.  The
217:c:func:`krb5_responder_pkinit_set_answer` function can be used to
218supply the PIN or password for a particular client credential, and can
219be called multiple times.
220
221Example
222#######
223
224Here is an example of using a responder callback::
225
226    static krb5_error_code
227    my_responder(krb5_context context, void *data,
228                 krb5_responder_context rctx)
229    {
230        krb5_error_code ret;
231        krb5_responder_otp_challenge *chl;
232
233        if (krb5_responder_get_challenge(context, rctx,
234                                         KRB5_RESPONDER_QUESTION_PASSWORD)) {
235            ret = krb5_responder_set_answer(context, rctx,
236                                            KRB5_RESPONDER_QUESTION_PASSWORD,
237                                            "open sesame");
238            if (ret)
239                return ret;
240        }
241        ret = krb5_responder_otp_get_challenge(context, rctx, &chl);
242        if (ret == 0 && chl != NULL) {
243            ret = krb5_responder_otp_set_answer(context, rctx, 0, "1234",
244                                                NULL);
245            krb5_responder_otp_challenge_free(context, rctx, chl);
246            if (ret)
247                return ret;
248        }
249        return 0;
250    }
251
252    static krb5_error_code
253    get_creds(krb5_context context, krb5_principal client_princ)
254    {
255        krb5_error_code ret;
256        krb5_get_init_creds_opt *opt = NULL;
257        krb5_creds creds;
258
259        memset(&creds, 0, sizeof(creds));
260        ret = krb5_get_init_creds_opt_alloc(context, &opt);
261        if (ret)
262            goto cleanup;
263        ret = krb5_get_init_creds_opt_set_responder(context, opt, my_responder,
264                                                    NULL);
265        if (ret)
266            goto cleanup;
267        ret = krb5_get_init_creds_password(context, &creds, client_princ,
268                                           NULL, NULL, NULL, 0, NULL, opt);
269
270    cleanup:
271        krb5_get_init_creds_opt_free(context, opt);
272        krb5_free_cred_contents(context, &creds);
273        return ret;
274    }
275
276Verifying initial credentials
277-----------------------------
278
279Use the function :c:func:`krb5_verify_init_creds` to verify initial
280credentials.  It takes an options structure (which can be a null
281pointer).  Use :c:func:`krb5_verify_init_creds_opt_init` to initialize
282the caller-allocated options structure, and
283:c:func:`krb5_verify_init_creds_opt_set_ap_req_nofail` to set the
284"nofail" option.  For example::
285
286    krb5_verify_init_creds_opt vopt;
287
288    krb5_verify_init_creds_opt_init(&vopt);
289    krb5_verify_init_creds_opt_set_ap_req_nofail(&vopt, 1);
290    ret = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL, &vopt);
291
292The confusingly named "nofail" option, when set, means that the
293verification must actually succeed in order for
294:c:func:`krb5_verify_init_creds` to indicate success.  The default
295state of this option (cleared) means that if there is no key material
296available to verify the user credentials, the verification will
297succeed anyway.  (The default can be changed by a configuration file
298setting.)
299
300This accommodates a use case where a large number of unkeyed shared
301desktop workstations need to allow users to log in using Kerberos.
302The security risks from this practice are mitigated by the absence of
303valuable state on the shared workstations---any valuable resources
304that the users would access reside on networked servers.
305