1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 ** set password functions added by Paul W. Nelson, Thursby Software Systems, Inc.
4 */
5 
6 #include "k5-int.h"
7 #include "k5-unicode.h"
8 #include "int-proto.h"
9 #include "auth_con.h"
10 
11 
12 krb5_error_code
krb5int_mk_chpw_req(krb5_context context,krb5_auth_context auth_context,krb5_data * ap_req,const char * passwd,krb5_data * packet)13 krb5int_mk_chpw_req(krb5_context context,
14                     krb5_auth_context auth_context,
15                     krb5_data *ap_req,
16                     const char *passwd,
17                     krb5_data *packet)
18 {
19     krb5_error_code ret = 0;
20     krb5_data clearpw;
21     krb5_data cipherpw;
22     krb5_replay_data replay;
23     char *ptr;
24 
25     cipherpw.data = NULL;
26 
27     if ((ret = krb5_auth_con_setflags(context, auth_context,
28                                       KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
29         goto cleanup;
30 
31     clearpw = string2data((char *)passwd);
32 
33     if ((ret = krb5_mk_priv(context, auth_context,
34                             &clearpw, &cipherpw, &replay)))
35         goto cleanup;
36 
37     packet->length = 6 + ap_req->length + cipherpw.length;
38     packet->data = (char *) malloc(packet->length);
39     if (packet->data == NULL) {
40         ret = ENOMEM;
41         goto cleanup;
42     }
43     ptr = packet->data;
44 
45     /* length */
46 
47     store_16_be(packet->length, ptr);
48     ptr += 2;
49 
50     /* version == 0x0001 big-endian */
51 
52     *ptr++ = 0;
53     *ptr++ = 1;
54 
55     /* ap_req length, big-endian */
56 
57     store_16_be(ap_req->length, ptr);
58     ptr += 2;
59 
60     /* ap-req data */
61 
62     memcpy(ptr, ap_req->data, ap_req->length);
63     ptr += ap_req->length;
64 
65     /* krb-priv of password */
66 
67     memcpy(ptr, cipherpw.data, cipherpw.length);
68 
69 cleanup:
70     if (cipherpw.data != NULL)  /* allocated by krb5_mk_priv */
71         free(cipherpw.data);
72 
73     return(ret);
74 }
75 
76 /* Decode error_packet as a KRB-ERROR message and retrieve its e-data into
77  * *edata_out. */
78 static krb5_error_code
get_error_edata(krb5_context context,const krb5_data * error_packet,krb5_data ** edata_out)79 get_error_edata(krb5_context context, const krb5_data *error_packet,
80                 krb5_data **edata_out)
81 {
82     krb5_error_code ret;
83     krb5_error *krberror = NULL;
84 
85     *edata_out = NULL;
86 
87     ret = krb5_rd_error(context, error_packet, &krberror);
88     if (ret)
89         return ret;
90 
91     if (krberror->e_data.data == NULL) {
92         /* Return a krb5 error code based on the error number. */
93         ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code)krberror->error;
94         goto cleanup;
95     }
96 
97     ret = krb5_copy_data(context, &krberror->e_data, edata_out);
98 
99 cleanup:
100     krb5_free_error(context, krberror);
101     return ret;
102 }
103 
104 /* Decode a reply to produce the clear-text output. */
105 static krb5_error_code
get_clear_result(krb5_context context,krb5_auth_context auth_context,const krb5_data * packet,krb5_data ** clear_out,krb5_boolean * is_error_out)106 get_clear_result(krb5_context context, krb5_auth_context auth_context,
107                  const krb5_data *packet, krb5_data **clear_out,
108                  krb5_boolean *is_error_out)
109 {
110     krb5_error_code ret;
111     char *ptr, *end = packet->data + packet->length;
112     unsigned int plen, vno, aplen;
113     krb5_data ap_rep, cipher, error;
114     krb5_ap_rep_enc_part *ap_rep_enc;
115     krb5_replay_data replay;
116     krb5_key send_subkey = NULL;
117     krb5_data clear = empty_data();
118 
119     *clear_out = NULL;
120     *is_error_out = FALSE;
121 
122     /* Check for an unframed KRB-ERROR (expected for RFC 3244 requests; also
123      * received from MS AD for version 1 requests). */
124     if (krb5_is_krb_error(packet)) {
125         *is_error_out = TRUE;
126         return get_error_edata(context, packet, clear_out);
127     }
128 
129     if (packet->length < 6)
130         return KRB5KRB_AP_ERR_MODIFIED;
131 
132     /* Decode and verify the length. */
133     ptr = packet->data;
134     plen = (*ptr++ & 0xff);
135     plen = (plen << 8) | (*ptr++ & 0xff);
136     if (plen != packet->length)
137         return KRB5KRB_AP_ERR_MODIFIED;
138 
139     /* Decode and verify the version number. */
140     vno = (*ptr++ & 0xff);
141     vno = (vno << 8) | (*ptr++ & 0xff);
142     if (vno != 1 && vno != 0xff80)
143         return KRB5KDC_ERR_BAD_PVNO;
144 
145     /* Decode and check the AP-REP length. */
146     aplen = (*ptr++ & 0xff);
147     aplen = (aplen << 8) | (*ptr++ & 0xff);
148     if (aplen > end - ptr)
149         return KRB5KRB_AP_ERR_MODIFIED;
150 
151     /* A zero-length AP-REQ indicates a framed KRB-ERROR response.  (Expected
152      * for protocol version 1; specified but unusual for RFC 3244 requests.) */
153     if (aplen == 0) {
154         *is_error_out = TRUE;
155         error = make_data(ptr, end - ptr);
156         return get_error_edata(context, &error, clear_out);
157     }
158 
159     /* We have an AP-REP.  Save send_subkey to later smash recv_subkey. */
160     ret = krb5_auth_con_getsendsubkey_k(context, auth_context, &send_subkey);
161     if (ret)
162         return ret;
163 
164     /* Verify the AP-REP. */
165     ap_rep = make_data(ptr, aplen);
166     ptr += ap_rep.length;
167     ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
168     if (ret)
169         goto cleanup;
170     krb5_free_ap_rep_enc_part(context, ap_rep_enc);
171 
172     /* Smash recv_subkey to be send_subkey, per spec. */
173     ret = krb5_auth_con_setrecvsubkey_k(context, auth_context, send_subkey);
174     if (ret)
175         goto cleanup;
176 
177     /* Extract and decrypt the result. */
178     cipher = make_data(ptr, end - ptr);
179     ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay);
180     if (ret)
181         goto cleanup;
182 
183     ret = krb5_copy_data(context, &clear, clear_out);
184     if (ret)
185         goto cleanup;
186     *is_error_out = FALSE;
187 
188 cleanup:
189     krb5_k_free_key(context, send_subkey);
190     krb5_free_data_contents(context, &clear);
191     return ret;
192 }
193 
194 krb5_error_code
krb5int_rd_chpw_rep(krb5_context context,krb5_auth_context auth_context,krb5_data * packet,int * result_code_out,krb5_data * result_data_out)195 krb5int_rd_chpw_rep(krb5_context context, krb5_auth_context auth_context,
196                     krb5_data *packet, int *result_code_out,
197                     krb5_data *result_data_out)
198 {
199     krb5_error_code ret;
200     krb5_data result_data, *clear = NULL;
201     krb5_boolean is_error;
202     char *ptr;
203     int result_code;
204 
205     *result_code_out = 0;
206     *result_data_out = empty_data();
207 
208     ret = get_clear_result(context, auth_context, packet, &clear, &is_error);
209     if (ret)
210         return ret;
211 
212     if (clear->length < 2) {
213         ret = KRB5KRB_AP_ERR_MODIFIED;
214         goto cleanup;
215     }
216 
217     /* Decode and check the result code. */
218     ptr = clear->data;
219     result_code = (*ptr++ & 0xff);
220     result_code = (result_code << 8) | (*ptr++ & 0xff);
221     if (result_code < KRB5_KPASSWD_SUCCESS ||
222         result_code > KRB5_KPASSWD_INITIAL_FLAG_NEEDED) {
223         ret = KRB5KRB_AP_ERR_MODIFIED;
224         goto cleanup;
225     }
226 
227     /* Successful replies must not come from errors. */
228     if (is_error && result_code == KRB5_KPASSWD_SUCCESS) {
229         ret = KRB5KRB_AP_ERR_MODIFIED;
230         goto cleanup;
231     }
232 
233     result_data = make_data(ptr, clear->data + clear->length - ptr);
234     ret = krb5int_copy_data_contents(context, &result_data, result_data_out);
235     if (ret)
236         goto cleanup;
237     *result_code_out = result_code;
238 
239 cleanup:
240     krb5_free_data(context, clear);
241     return ret;
242 }
243 
244 krb5_error_code KRB5_CALLCONV
krb5_chpw_result_code_string(krb5_context context,int result_code,char ** code_string)245 krb5_chpw_result_code_string(krb5_context context, int result_code,
246                              char **code_string)
247 {
248     switch (result_code) {
249     case KRB5_KPASSWD_MALFORMED:
250         *code_string = _("Malformed request error");
251         break;
252     case KRB5_KPASSWD_HARDERROR:
253         *code_string = _("Server error");
254         break;
255     case KRB5_KPASSWD_AUTHERROR:
256         *code_string = _("Authentication error");
257         break;
258     case KRB5_KPASSWD_SOFTERROR:
259         *code_string = _("Password change rejected");
260         break;
261     case KRB5_KPASSWD_ACCESSDENIED:
262         *code_string = _("Access denied");
263         break;
264     case KRB5_KPASSWD_BAD_VERSION:
265         *code_string = _("Wrong protocol version");
266         break;
267     case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
268         *code_string = _("Initial password required");
269         break;
270     case 0:
271         *code_string = _("Success");
272         break;
273     default:
274         *code_string = _("Password change failed");
275         break;
276     }
277 
278     return 0;
279 }
280 
281 krb5_error_code
krb5int_mk_setpw_req(krb5_context context,krb5_auth_context auth_context,krb5_data * ap_req,krb5_principal targprinc,const char * passwd,krb5_data * packet)282 krb5int_mk_setpw_req(krb5_context context,
283                      krb5_auth_context auth_context,
284                      krb5_data *ap_req,
285                      krb5_principal targprinc,
286                      const char *passwd,
287                      krb5_data *packet)
288 {
289     krb5_error_code ret;
290     krb5_data   cipherpw;
291     krb5_data   *encoded_setpw;
292     struct krb5_setpw_req req;
293 
294     char *ptr;
295 
296     cipherpw.data = NULL;
297     cipherpw.length = 0;
298 
299     if ((ret = krb5_auth_con_setflags(context, auth_context,
300                                       KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
301         return(ret);
302 
303     req.target = targprinc;
304     req.password = string2data((char *)passwd);
305     ret = encode_krb5_setpw_req(&req, &encoded_setpw);
306     if (ret) {
307         return ret;
308     }
309 
310     if ((ret = krb5_mk_priv(context, auth_context, encoded_setpw, &cipherpw, NULL)) != 0) {
311         krb5_free_data(context, encoded_setpw);
312         return(ret);
313     }
314     krb5_free_data(context, encoded_setpw);
315 
316 
317     packet->length = 6 + ap_req->length + cipherpw.length;
318     packet->data = (char *) malloc(packet->length);
319     if (packet->data  == NULL) {
320         ret = ENOMEM;
321         goto cleanup;
322     }
323     ptr = packet->data;
324     /*
325     ** build the packet -
326     */
327     /* put in the length */
328     store_16_be(packet->length, ptr);
329     ptr += 2;
330     /* put in the version */
331     *ptr++ = (char)0xff;
332     *ptr++ = (char)0x80;
333     /* the ap_req length is big endian */
334     store_16_be(ap_req->length, ptr);
335     ptr += 2;
336     /* put in the request data */
337     memcpy(ptr, ap_req->data, ap_req->length);
338     ptr += ap_req->length;
339     /*
340     ** put in the "private" password data -
341     */
342     memcpy(ptr, cipherpw.data, cipherpw.length);
343     ret = 0;
344 cleanup:
345     if (cipherpw.data)
346         krb5_free_data_contents(context, &cipherpw);
347     if ((ret != 0) && packet->data) {
348         free(packet->data);
349         packet->data = NULL;
350     }
351     return ret;
352 }
353 
354 /*
355  * Active Directory policy information is communicated in the result string
356  * field as a packed 30-byte sequence, starting with two zero bytes (so that
357  * the string appears as zero-length when interpreted as UTF-8).  The bytes
358  * correspond to the fields in the following structure, with each field in
359  * big-endian byte order.
360  */
361 struct ad_policy_info {
362     uint16_t zero_bytes;
363     uint32_t min_length_password;
364     uint32_t password_history;
365     uint32_t password_properties; /* see defines below */
366     uint64_t expire;              /* in seconds * 10,000,000 */
367     uint64_t min_passwordage;     /* in seconds * 10,000,000 */
368 };
369 
370 #define AD_POLICY_INFO_LENGTH      30
371 #define AD_POLICY_TIME_TO_DAYS     (86400ULL * 10000000ULL)
372 
373 #define AD_POLICY_COMPLEX          0x00000001
374 #define AD_POLICY_NO_ANON_CHANGE   0x00000002
375 #define AD_POLICY_NO_CLEAR_CHANGE  0x00000004
376 #define AD_POLICY_LOCKOUT_ADMINS   0x00000008
377 #define AD_POLICY_STORE_CLEARTEXT  0x00000010
378 #define AD_POLICY_REFUSE_CHANGE    0x00000020
379 
380 /* If buf already contains one or more sentences, add spaces to separate them
381  * from the next sentence. */
382 static void
add_spaces(struct k5buf * buf)383 add_spaces(struct k5buf *buf)
384 {
385     if (buf->len > 0)
386         k5_buf_add(buf, "  ");
387 }
388 
389 static krb5_error_code
decode_ad_policy_info(const krb5_data * data,char ** msg_out)390 decode_ad_policy_info(const krb5_data *data, char **msg_out)
391 {
392     struct ad_policy_info policy;
393     uint64_t password_days;
394     const char *p;
395     struct k5buf buf;
396 
397     *msg_out = NULL;
398     if (data->length != AD_POLICY_INFO_LENGTH)
399         return 0;
400 
401     p = data->data;
402     policy.zero_bytes = load_16_be(p);
403     p += 2;
404 
405     /* first two bytes are zeros */
406     if (policy.zero_bytes != 0)
407         return 0;
408 
409     /* Read in the rest of structure */
410     policy.min_length_password = load_32_be(p);
411     p += 4;
412     policy.password_history = load_32_be(p);
413     p += 4;
414     policy.password_properties = load_32_be(p);
415     p += 4;
416     policy.expire = load_64_be(p);
417     p += 8;
418     policy.min_passwordage = load_64_be(p);
419     p += 8;
420 
421     /* Check that we processed exactly the expected number of bytes. */
422     assert(p == data->data + AD_POLICY_INFO_LENGTH);
423 
424     k5_buf_init_dynamic(&buf);
425 
426     /*
427      * Update src/tests/misc/test_chpw_message.c if changing these strings!
428      */
429 
430     if (policy.password_properties & AD_POLICY_COMPLEX) {
431         k5_buf_add(&buf, _("The password must include numbers or symbols.  "
432                            "Don't include any part of your name in the "
433                            "password."));
434     }
435     if (policy.min_length_password > 0) {
436         add_spaces(&buf);
437         k5_buf_add_fmt(&buf, ngettext("The password must contain at least %d "
438                                       "character.",
439                                       "The password must contain at least %d "
440                                       "characters.",
441                                       policy.min_length_password),
442                        policy.min_length_password);
443     }
444     if (policy.password_history) {
445         add_spaces(&buf);
446         k5_buf_add_fmt(&buf, ngettext("The password must be different from "
447                                       "the previous password.",
448                                       "The password must be different from "
449                                       "the previous %d passwords.",
450                                       policy.password_history),
451                        policy.password_history);
452     }
453     if (policy.min_passwordage) {
454         password_days = policy.min_passwordage / AD_POLICY_TIME_TO_DAYS;
455         if (password_days == 0)
456             password_days = 1;
457         add_spaces(&buf);
458         k5_buf_add_fmt(&buf, ngettext("The password can only be changed once "
459                                       "a day.",
460                                       "The password can only be changed every "
461                                       "%d days.", (int)password_days),
462                        (int)password_days);
463     }
464 
465     if (k5_buf_status(&buf) != 0)
466         return ENOMEM;
467 
468     if (buf.len > 0)
469         *msg_out = buf.data;
470     else
471         k5_buf_free(&buf);
472     return 0;
473 }
474 
475 krb5_error_code KRB5_CALLCONV
krb5_chpw_message(krb5_context context,const krb5_data * server_string,char ** message_out)476 krb5_chpw_message(krb5_context context, const krb5_data *server_string,
477                   char **message_out)
478 {
479     krb5_error_code ret;
480     krb5_data *string;
481     char *msg;
482 
483     *message_out = NULL;
484 
485     /* If server_string contains an AD password policy, construct a message
486      * based on that. */
487     ret = decode_ad_policy_info(server_string, &msg);
488     if (ret == 0 && msg != NULL) {
489         *message_out = msg;
490         return 0;
491     }
492 
493     /* If server_string contains a valid UTF-8 string, return that. */
494     if (server_string->length > 0 &&
495         memchr(server_string->data, 0, server_string->length) == NULL &&
496         krb5int_utf8_normalize(server_string, &string,
497                                KRB5_UTF8_APPROX) == 0) {
498         *message_out = string->data; /* already null terminated */
499         free(string);
500         return 0;
501     }
502 
503     /* server_string appears invalid, so try to be helpful. */
504     msg = strdup(_("Try a more complex password, or contact your "
505                    "administrator."));
506     if (msg == NULL)
507         return ENOMEM;
508 
509     *message_out = msg;
510     return 0;
511 }
512