r10066: This is the second in my patches to work on Samba4's kerberos support,
[tprouty/samba.git] / source / auth / kerberos / kerberos_verify.c
1 /* 
2    Unix SMB/CIFS implementation.
3    kerberos utility library
4    Copyright (C) Andrew Tridgell 2001
5    Copyright (C) Remus Koos 2001
6    Copyright (C) Luke Howard 2003   
7    Copyright (C) Guenther Deschner 2003
8    Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
9    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
10    
11    This program is free software; you can redistribute it and/or modify
12    it under the terms of the GNU General Public License as published by
13    the Free Software Foundation; either version 2 of the License, or
14    (at your option) any later version.
15    
16    This program is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19    GNU General Public License for more details.
20    
21    You should have received a copy of the GNU General Public License
22    along with this program; if not, write to the Free Software
23    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 */
25
26 #include "includes.h"
27 #include "system/network.h"
28 #include "system/kerberos.h"
29 #include "auth/kerberos/kerberos.h"
30 #include "asn_1.h"
31 #include "lib/ldb/include/ldb.h"
32 #include "secrets.h"
33 #include "pstring.h"
34
35 #ifdef HAVE_KRB5
36
37 DATA_BLOB unwrap_pac(TALLOC_CTX *mem_ctx, DATA_BLOB *auth_data)
38 {
39         DATA_BLOB out;
40         DATA_BLOB pac_contents = data_blob(NULL, 0);
41         struct asn1_data data;
42         int data_type;
43         if (!auth_data->length) {
44                 return data_blob(NULL, 0);
45         }
46
47         asn1_load(&data, *auth_data);
48         asn1_start_tag(&data, ASN1_SEQUENCE(0));
49         asn1_start_tag(&data, ASN1_SEQUENCE(0));
50         asn1_start_tag(&data, ASN1_CONTEXT(0));
51         asn1_read_Integer(&data, &data_type);
52         asn1_end_tag(&data);
53         asn1_start_tag(&data, ASN1_CONTEXT(1));
54         asn1_read_OctetString(&data, &pac_contents);
55         asn1_end_tag(&data);
56         asn1_end_tag(&data);
57         asn1_end_tag(&data);
58         asn1_free(&data);
59
60         out = data_blob_talloc(mem_ctx, pac_contents.data, pac_contents.length);
61
62         data_blob_free(&pac_contents);
63
64         return out;
65 }
66
67 /**********************************************************************************
68  Try to verify a ticket using the system keytab... the system keytab has kvno -1 entries, so
69  it's more like what microsoft does... see comment in utils/net_ads.c in the
70  ads_keytab_add_entry function for details.
71 ***********************************************************************************/
72
73 static krb5_error_code ads_keytab_verify_ticket(TALLOC_CTX *mem_ctx, krb5_context context, 
74                                                 krb5_auth_context *auth_context,
75                                                 const char *service,
76                                                 const krb5_data *p_packet, 
77                                                 krb5_flags *ap_req_options,
78                                                 krb5_ticket **pp_tkt,
79                                                 krb5_keyblock **keyblock)
80 {
81         krb5_error_code ret = 0;
82         krb5_keytab keytab = NULL;
83         krb5_kt_cursor kt_cursor;
84         krb5_keytab_entry kt_entry;
85         char *valid_princ_formats[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
86         char *entry_princ_s = NULL;
87         const char *my_name, *my_fqdn;
88         int i;
89         int number_matched_principals = 0;
90         const char *last_error_message;
91
92         /* Generate the list of principal names which we expect
93          * clients might want to use for authenticating to the file
94          * service.  We allow name$,{host,service}/{name,fqdn,name.REALM}.
95          * (where service is specified by the caller) */
96
97         my_name = lp_netbios_name();
98
99         my_fqdn = name_to_fqdn(mem_ctx, my_name);
100
101         asprintf(&valid_princ_formats[0], "%s$@%s", my_name, lp_realm());
102         asprintf(&valid_princ_formats[1], "host/%s@%s", my_name, lp_realm());
103         asprintf(&valid_princ_formats[2], "host/%s@%s", my_fqdn, lp_realm());
104         asprintf(&valid_princ_formats[3], "host/%s.%s@%s", my_name, lp_realm(), lp_realm());
105         asprintf(&valid_princ_formats[4], "%s/%s@%s", service, my_name, lp_realm());
106         asprintf(&valid_princ_formats[5], "%s/%s@%s", service, my_fqdn, lp_realm());
107         asprintf(&valid_princ_formats[6], "%s/%s.%s@%s", service, my_name, lp_realm(), lp_realm());
108
109         ZERO_STRUCT(kt_entry);
110         ZERO_STRUCT(kt_cursor);
111
112         ret = krb5_kt_default(context, &keytab);
113         if (ret) {
114                 last_error_message = smb_get_krb5_error_message(context, ret, mem_ctx);
115                 DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_default failed (%s)\n", 
116                           last_error_message));
117                 goto out;
118         }
119
120         /* Iterate through the keytab.  For each key, if the principal
121          * name case-insensitively matches one of the allowed formats,
122          * try verifying the ticket using that principal. */
123
124         ret = krb5_kt_start_seq_get(context, keytab, &kt_cursor);
125         if (ret == KRB5_KT_END || ret == ENOENT ) {
126                 last_error_message = smb_get_krb5_error_message(context, ret, mem_ctx);
127         } else if (ret) {
128                 last_error_message = smb_get_krb5_error_message(context, ret, mem_ctx);
129                 DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_start_seq_get failed (%s)\n", 
130                           last_error_message));
131         } else {
132                 ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; /* Pick an error... */
133                 last_error_message = "No principals in Keytab";
134                 while (ret && (krb5_kt_next_entry(context, keytab, &kt_entry, &kt_cursor) == 0)) {
135                         krb5_error_code upn_ret;
136                         upn_ret = krb5_unparse_name(context, kt_entry.principal, &entry_princ_s);
137                         if (upn_ret) {
138                                 last_error_message = smb_get_krb5_error_message(context, ret, mem_ctx);
139                                 DEBUG(1, ("ads_keytab_verify_ticket: krb5_unparse_name failed (%s)\n", 
140                                           last_error_message));
141                                 ret = upn_ret;
142                                 break;
143                         }
144                         for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
145                                 if (!strequal(entry_princ_s, valid_princ_formats[i])) {
146                                         continue;
147                                 }
148
149                                 number_matched_principals++;
150                                 *pp_tkt = NULL;
151                                 ret = krb5_rd_req_return_keyblock(context, auth_context, p_packet, 
152                                                                   kt_entry.principal, keytab, 
153                                                                   ap_req_options, pp_tkt, keyblock);
154                                 if (ret) {
155                                         last_error_message = smb_get_krb5_error_message(context, ret, mem_ctx);
156                                         DEBUG(10, ("ads_keytab_verify_ticket: krb5_rd_req(%s) failed: %s\n",
157                                                    entry_princ_s, last_error_message));
158                                 } else {
159                                         DEBUG(3,("ads_keytab_verify_ticket: krb5_rd_req succeeded for principal %s\n",
160                                                  entry_princ_s));
161                                         break;
162                                 }
163                         }
164
165                         /* Free the name we parsed. */
166                         krb5_free_unparsed_name(context, entry_princ_s);
167                         entry_princ_s = NULL;
168
169                         /* Free the entry we just read. */
170                         smb_krb5_kt_free_entry(context, &kt_entry);
171                         ZERO_STRUCT(kt_entry);
172                 }
173                 krb5_kt_end_seq_get(context, keytab, &kt_cursor);
174         }
175
176         ZERO_STRUCT(kt_cursor);
177
178   out:
179
180         if (ret) {
181                 if (!number_matched_principals) {
182                         DEBUG(3, ("ads_keytab_verify_ticket: no keytab principals matched expected file service name.\n"));
183                 } else {
184                         DEBUG(3, ("ads_keytab_verify_ticket: krb5_rd_req failed for all %d matched keytab principals\n",
185                                 number_matched_principals));
186                 }
187                 DEBUG(3, ("ads_keytab_verify_ticket: last error: %s\n", last_error_message));
188         }
189
190         if (entry_princ_s) {
191                 krb5_free_unparsed_name(context, entry_princ_s);
192         }
193
194         {
195                 krb5_keytab_entry zero_kt_entry;
196                 ZERO_STRUCT(zero_kt_entry);
197                 if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
198                         smb_krb5_kt_free_entry(context, &kt_entry);
199                 }
200         }
201
202         {
203                 krb5_kt_cursor zero_csr;
204                 ZERO_STRUCT(zero_csr);
205                 if ((memcmp(&kt_cursor, &zero_csr, sizeof(krb5_kt_cursor)) != 0) && keytab) {
206                         krb5_kt_end_seq_get(context, keytab, &kt_cursor);
207                 }
208         }
209
210         if (keytab) {
211                 krb5_kt_close(context, keytab);
212         }
213
214         return ret;
215 }
216
217 /**********************************************************************************
218  Verify an incoming ticket and parse out the principal name and 
219  authorization_data if available.
220 ***********************************************************************************/
221
222  NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx, 
223                             struct smb_krb5_context *smb_krb5_context,
224                             krb5_auth_context *auth_context,
225                             const char *realm, const char *service, 
226                             const DATA_BLOB *enc_ticket, 
227                             krb5_ticket **tkt,
228                             DATA_BLOB *ap_rep,
229                             krb5_keyblock **keyblock)
230 {
231         krb5_keyblock *local_keyblock;
232         krb5_data packet;
233         krb5_principal salt_princ;
234         int ret;
235         krb5_flags ap_req_options = 0;
236
237         NTSTATUS creds_nt_status, status;
238         struct cli_credentials *machine_account;
239
240         machine_account = cli_credentials_init(mem_ctx);
241         cli_credentials_set_conf(machine_account);
242         creds_nt_status = cli_credentials_set_machine_account(machine_account);
243         
244         if (!NT_STATUS_IS_OK(creds_nt_status)) {
245                 DEBUG(3, ("Could not obtain machine account credentials from the local database\n"));
246                 talloc_free(machine_account);
247                 machine_account = NULL;
248         } else {
249                 ret = salt_principal_from_credentials(mem_ctx, machine_account, 
250                                                       smb_krb5_context, 
251                                                       &salt_princ);
252                 if (ret) {
253                         DEBUG(1,("ads_verify_ticket: maksing salt principal failed (%s)\n",
254                                  error_message(ret)));
255                         return NT_STATUS_INTERNAL_ERROR;
256                 }
257         }
258         
259         /* This whole process is far more complex than I would
260            like. We have to go through all this to allow us to store
261            the secret internally, instead of using /etc/krb5.keytab */
262
263         /*
264          * TODO: Actually hook in the replay cache in Heimdal, then
265          * re-add calls to setup a replay cache here, in our private
266          * directory.  This will eventually prevent replay attacks
267          */
268
269         packet.length = enc_ticket->length;
270         packet.data = (krb5_pointer)enc_ticket->data;
271
272         ret = ads_keytab_verify_ticket(mem_ctx, smb_krb5_context->krb5_context, auth_context, 
273                                        service, &packet, &ap_req_options, tkt, &local_keyblock);
274         if (ret && machine_account) {
275                 krb5_keytab keytab;
276                 krb5_principal server;
277                 status = create_memory_keytab(mem_ctx, machine_account, smb_krb5_context, 
278                                               &keytab);
279                 if (!NT_STATUS_IS_OK(status)) {
280                         return status;
281                 }
282                 ret = principal_from_credentials(mem_ctx, machine_account, smb_krb5_context, 
283                                                  &server);
284                 if (ret == 0) {
285                         ret = krb5_rd_req_return_keyblock(smb_krb5_context->krb5_context, auth_context, &packet,
286                                                           server,
287                                                           keytab, &ap_req_options, tkt,
288                                                           &local_keyblock);
289                 }
290         }
291
292         if (ret) {
293                 DEBUG(3,("ads_secrets_verify_ticket: failed to decrypt with error %s\n",
294                          smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx)));
295                 return NT_STATUS_LOGON_FAILURE;
296         }
297         *keyblock = local_keyblock;
298
299         if (ap_req_options & AP_OPTS_MUTUAL_REQUIRED) {
300                 krb5_data packet_out;
301                 ret = krb5_mk_rep(smb_krb5_context->krb5_context, *auth_context, &packet_out);
302                 if (ret) {
303                         krb5_free_ticket(smb_krb5_context->krb5_context, *tkt);
304                         
305                         DEBUG(3,("ads_verify_ticket: Failed to generate mutual authentication reply (%s)\n",
306                                  smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, mem_ctx)));
307                         return NT_STATUS_LOGON_FAILURE;
308                 }
309                 
310                 *ap_rep = data_blob_talloc(mem_ctx, packet_out.data, packet_out.length);
311                 krb5_free_data_contents(smb_krb5_context->krb5_context, &packet_out);
312         } else {
313                 *ap_rep = data_blob(NULL, 0);
314         }
315
316         return NT_STATUS_OK;
317 }
318
319 #endif /* HAVE_KRB5 */