CVE-2022-2031 s4:kpasswd: Do not accept TGTs as kpasswd tickets
[samba.git] / source4 / kdc / kpasswd-service-mit.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    Samba kpasswd implementation
5
6    Copyright (c) 2016      Andreas Schneider <asn@samba.org>
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "includes.h"
23 #include "samba/service_task.h"
24 #include "param/param.h"
25 #include "auth/auth.h"
26 #include "auth/gensec/gensec.h"
27 #include "gensec_krb5_helpers.h"
28 #include "kdc/kdc-server.h"
29 #include "kdc/kpasswd_glue.h"
30 #include "kdc/kpasswd-service.h"
31 #include "kdc/kpasswd-helper.h"
32 #include "../lib/util/asn1.h"
33
34 #define RFC3244_VERSION 0xff80
35
36 krb5_error_code decode_krb5_setpw_req(const krb5_data *code,
37                                       krb5_data **password_out,
38                                       krb5_principal *target_out);
39
40 /*
41  * A fallback for when MIT refuses to parse a setpw structure without the
42  * (optional) target principal and realm
43  */
44 static bool decode_krb5_setpw_req_simple(TALLOC_CTX *mem_ctx,
45                                          const DATA_BLOB *decoded_data,
46                                          DATA_BLOB *clear_data)
47 {
48         struct asn1_data *asn1 = NULL;
49         bool ret;
50
51         asn1 = asn1_init(mem_ctx, 3);
52         if (asn1 == NULL) {
53                 return false;
54         }
55
56         ret = asn1_load(asn1, *decoded_data);
57         if (!ret) {
58                 goto out;
59         }
60
61         ret = asn1_start_tag(asn1, ASN1_SEQUENCE(0));
62         if (!ret) {
63                 goto out;
64         }
65         ret = asn1_start_tag(asn1, ASN1_CONTEXT(0));
66         if (!ret) {
67                 goto out;
68         }
69         ret = asn1_read_OctetString(asn1, mem_ctx, clear_data);
70         if (!ret) {
71                 goto out;
72         }
73
74         ret = asn1_end_tag(asn1);
75         if (!ret) {
76                 goto out;
77         }
78         ret = asn1_end_tag(asn1);
79
80 out:
81         asn1_free(asn1);
82
83         return ret;
84 }
85
86 static krb5_error_code kpasswd_change_password(struct kdc_server *kdc,
87                                                TALLOC_CTX *mem_ctx,
88                                                const struct gensec_security *gensec_security,
89                                                struct auth_session_info *session_info,
90                                                DATA_BLOB *password,
91                                                DATA_BLOB *kpasswd_reply,
92                                                const char **error_string)
93 {
94         NTSTATUS status;
95         NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
96         enum samPwdChangeReason reject_reason;
97         const char *reject_string = NULL;
98         struct samr_DomInfo1 *dominfo;
99         bool ok;
100         int ret;
101
102         /*
103          * We're doing a password change (rather than a password set), so check
104          * that we were given an initial ticket.
105          */
106         ret = gensec_krb5_initial_ticket(gensec_security);
107         if (ret != 1) {
108                 *error_string = "Expected an initial ticket";
109                 return KRB5_KPASSWD_INITIAL_FLAG_NEEDED;
110         }
111
112         status = samdb_kpasswd_change_password(mem_ctx,
113                                                kdc->task->lp_ctx,
114                                                kdc->task->event_ctx,
115                                                kdc->samdb,
116                                                session_info,
117                                                password,
118                                                &reject_reason,
119                                                &dominfo,
120                                                &reject_string,
121                                                &result);
122         if (!NT_STATUS_IS_OK(status)) {
123                 ok = kpasswd_make_error_reply(mem_ctx,
124                                               KRB5_KPASSWD_ACCESSDENIED,
125                                               reject_string,
126                                               kpasswd_reply);
127                 if (!ok) {
128                         *error_string = "Failed to create reply";
129                         return KRB5_KPASSWD_HARDERROR;
130                 }
131                 /* We want to send an an authenticated packet. */
132                 return 0;
133         }
134
135         ok = kpasswd_make_pwchange_reply(mem_ctx,
136                                          result,
137                                          reject_reason,
138                                          dominfo,
139                                          kpasswd_reply);
140         if (!ok) {
141                 *error_string = "Failed to create reply";
142                 return KRB5_KPASSWD_HARDERROR;
143         }
144
145         return 0;
146 }
147
148 static krb5_error_code kpasswd_set_password(struct kdc_server *kdc,
149                                             TALLOC_CTX *mem_ctx,
150                                             const struct gensec_security *gensec_security,
151                                             struct auth_session_info *session_info,
152                                             DATA_BLOB *decoded_data,
153                                             DATA_BLOB *kpasswd_reply,
154                                             const char **error_string)
155 {
156         krb5_context context = kdc->smb_krb5_context->krb5_context;
157         DATA_BLOB clear_data;
158         krb5_data k_dec_data;
159         krb5_data *k_clear_data = NULL;
160         krb5_principal target_principal = NULL;
161         krb5_error_code code;
162         DATA_BLOB password;
163         char *target_realm = NULL;
164         char *target_name = NULL;
165         char *target_principal_string = NULL;
166         bool is_service_principal = false;
167         bool ok;
168         size_t num_components;
169         enum samPwdChangeReason reject_reason = SAM_PWD_CHANGE_NO_ERROR;
170         struct samr_DomInfo1 *dominfo = NULL;
171         NTSTATUS status;
172
173         k_dec_data.length = decoded_data->length;
174         k_dec_data.data   = (char *)decoded_data->data;
175
176         code = decode_krb5_setpw_req(&k_dec_data,
177                                      &k_clear_data,
178                                      &target_principal);
179         if (code == 0) {
180                 clear_data.data = (uint8_t *)k_clear_data->data;
181                 clear_data.length = k_clear_data->length;
182         } else {
183                 target_principal = NULL;
184
185                 /*
186                  * The MIT decode failed, so fall back to trying the simple
187                  * case, without target_principal.
188                  */
189                 ok = decode_krb5_setpw_req_simple(mem_ctx,
190                                                   decoded_data,
191                                                   &clear_data);
192                 if (!ok) {
193                         DBG_WARNING("decode_krb5_setpw_req failed: %s\n",
194                                     error_message(code));
195                         ok = kpasswd_make_error_reply(mem_ctx,
196                                                       KRB5_KPASSWD_MALFORMED,
197                                                       "Failed to decode packet",
198                                                       kpasswd_reply);
199                         if (!ok) {
200                                 *error_string = "Failed to create reply";
201                                 return KRB5_KPASSWD_HARDERROR;
202                         }
203                         return 0;
204                 }
205         }
206
207         ok = convert_string_talloc_handle(mem_ctx,
208                                           lpcfg_iconv_handle(kdc->task->lp_ctx),
209                                           CH_UTF8,
210                                           CH_UTF16,
211                                           clear_data.data,
212                                           clear_data.length,
213                                           (void **)&password.data,
214                                           &password.length);
215         if (k_clear_data != NULL) {
216                 krb5_free_data(context, k_clear_data);
217         }
218         if (!ok) {
219                 DBG_WARNING("String conversion failed\n");
220                 *error_string = "String conversion failed";
221                 return KRB5_KPASSWD_HARDERROR;
222         }
223
224         if (target_principal != NULL) {
225                 target_realm = smb_krb5_principal_get_realm(
226                         mem_ctx, context, target_principal);
227                 code = krb5_unparse_name_flags(context,
228                                                target_principal,
229                                                KRB5_PRINCIPAL_UNPARSE_NO_REALM,
230                                                &target_name);
231                 if (code != 0) {
232                         DBG_WARNING("Failed to parse principal\n");
233                         *error_string = "String conversion failed";
234                         return KRB5_KPASSWD_HARDERROR;
235                 }
236         }
237
238         if ((target_name != NULL && target_realm == NULL) ||
239             (target_name == NULL && target_realm != NULL)) {
240                 krb5_free_principal(context, target_principal);
241                 TALLOC_FREE(target_realm);
242                 SAFE_FREE(target_name);
243
244                 ok = kpasswd_make_error_reply(mem_ctx,
245                                               KRB5_KPASSWD_MALFORMED,
246                                               "Realm and principal must be "
247                                               "both present, or neither "
248                                               "present",
249                                               kpasswd_reply);
250                 if (!ok) {
251                         *error_string = "Failed to create reply";
252                         return KRB5_KPASSWD_HARDERROR;
253                 }
254                 return 0;
255         }
256
257         if (target_name != NULL && target_realm != NULL) {
258                 TALLOC_FREE(target_realm);
259                 SAFE_FREE(target_name);
260         } else {
261                 krb5_free_principal(context, target_principal);
262                 TALLOC_FREE(target_realm);
263                 SAFE_FREE(target_name);
264
265                 return kpasswd_change_password(kdc,
266                                                mem_ctx,
267                                                gensec_security,
268                                                session_info,
269                                                &password,
270                                                kpasswd_reply,
271                                                error_string);
272         }
273
274         num_components = krb5_princ_size(context, target_principal);
275         if (num_components >= 2) {
276                 is_service_principal = true;
277                 code = krb5_unparse_name_flags(context,
278                                                target_principal,
279                                                KRB5_PRINCIPAL_UNPARSE_SHORT,
280                                                &target_principal_string);
281         } else {
282                 code = krb5_unparse_name(context,
283                                          target_principal,
284                                          &target_principal_string);
285         }
286         krb5_free_principal(context, target_principal);
287         if (code != 0) {
288                 ok = kpasswd_make_error_reply(mem_ctx,
289                                               KRB5_KPASSWD_MALFORMED,
290                                               "Failed to parse principal",
291                                               kpasswd_reply);
292                 if (!ok) {
293                         *error_string = "Failed to create reply";
294                         return KRB5_KPASSWD_HARDERROR;
295                 }
296         }
297
298         status = kpasswd_samdb_set_password(mem_ctx,
299                                             kdc->task->event_ctx,
300                                             kdc->task->lp_ctx,
301                                             session_info,
302                                             is_service_principal,
303                                             target_principal_string,
304                                             &password,
305                                             &reject_reason,
306                                             &dominfo);
307         if (!NT_STATUS_IS_OK(status)) {
308                 DBG_ERR("kpasswd_samdb_set_password failed - %s\n",
309                         nt_errstr(status));
310         }
311
312         ok = kpasswd_make_pwchange_reply(mem_ctx,
313                                          status,
314                                          reject_reason,
315                                          dominfo,
316                                          kpasswd_reply);
317         if (!ok) {
318                 *error_string = "Failed to create reply";
319                 return KRB5_KPASSWD_HARDERROR;
320         }
321
322         return 0;
323 }
324
325 krb5_error_code kpasswd_handle_request(struct kdc_server *kdc,
326                                        TALLOC_CTX *mem_ctx,
327                                        struct gensec_security *gensec_security,
328                                        uint16_t verno,
329                                        DATA_BLOB *decoded_data,
330                                        DATA_BLOB *kpasswd_reply,
331                                        const char **error_string)
332 {
333         struct auth_session_info *session_info;
334         NTSTATUS status;
335         krb5_error_code code;
336
337         status = gensec_session_info(gensec_security,
338                                      mem_ctx,
339                                      &session_info);
340         if (!NT_STATUS_IS_OK(status)) {
341                 *error_string = talloc_asprintf(mem_ctx,
342                                                 "gensec_session_info failed - "
343                                                 "%s",
344                                                 nt_errstr(status));
345                 return KRB5_KPASSWD_HARDERROR;
346         }
347
348         /*
349          * Since the kpasswd service shares its keys with the krbtgt, we might
350          * have received a TGT rather than a kpasswd ticket. We need to check
351          * the ticket type to ensure that TGTs cannot be misused in this manner.
352          */
353         code = kpasswd_check_non_tgt(session_info,
354                                      error_string);
355         if (code != 0) {
356                 DBG_WARNING("%s\n", *error_string);
357                 return code;
358         }
359
360         switch(verno) {
361         case 1: {
362                 DATA_BLOB password;
363                 bool ok;
364
365                 ok = convert_string_talloc_handle(mem_ctx,
366                                                   lpcfg_iconv_handle(kdc->task->lp_ctx),
367                                                   CH_UTF8,
368                                                   CH_UTF16,
369                                                   (const char *)decoded_data->data,
370                                                   decoded_data->length,
371                                                   (void **)&password.data,
372                                                   &password.length);
373                 if (!ok) {
374                         *error_string = "String conversion failed!";
375                         DBG_WARNING("%s\n", *error_string);
376                         return KRB5_KPASSWD_HARDERROR;
377                 }
378
379                 return kpasswd_change_password(kdc,
380                                                mem_ctx,
381                                                gensec_security,
382                                                session_info,
383                                                &password,
384                                                kpasswd_reply,
385                                                error_string);
386         }
387         case RFC3244_VERSION: {
388                 return kpasswd_set_password(kdc,
389                                             mem_ctx,
390                                             gensec_security,
391                                             session_info,
392                                             decoded_data,
393                                             kpasswd_reply,
394                                             error_string);
395         }
396         default:
397                 return KRB5_KPASSWD_BAD_VERSION;
398         }
399
400         return 0;
401 }