libads/kerberos_verify.c

説明を見る。
00001 /* 
00002    Unix SMB/CIFS implementation.
00003    kerberos utility library
00004    Copyright (C) Andrew Tridgell 2001
00005    Copyright (C) Remus Koos 2001
00006    Copyright (C) Luke Howard 2003   
00007    Copyright (C) Guenther Deschner 2003, 2005
00008    Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
00009    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
00010    Copyright (C) Jeremy Allison 2007
00011    
00012    This program is free software; you can redistribute it and/or modify
00013    it under the terms of the GNU General Public License as published by
00014    the Free Software Foundation; either version 2 of the License, or
00015    (at your option) any later version.
00016    
00017    This program is distributed in the hope that it will be useful,
00018    but WITHOUT ANY WARRANTY; without even the implied warranty of
00019    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020    GNU General Public License for more details.
00021    
00022    You should have received a copy of the GNU General Public License
00023    along with this program; if not, write to the Free Software
00024    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00025 */
00026 
00027 #include "includes.h"
00028 
00029 #ifdef HAVE_KRB5
00030 
00031 #if !defined(HAVE_KRB5_PRINC_COMPONENT)
00032 const krb5_data *krb5_princ_component(krb5_context, krb5_principal, int );
00033 #endif
00034 
00035 /**********************************************************************************
00036  Try to verify a ticket using the system keytab... the system keytab has kvno -1 entries, so
00037  it's more like what microsoft does... see comment in utils/net_ads.c in the
00038  ads_keytab_add_entry function for details.
00039 ***********************************************************************************/
00040 
00041 static BOOL ads_keytab_verify_ticket(krb5_context context,
00042                                         krb5_auth_context auth_context,
00043                                         const DATA_BLOB *ticket,
00044                                         krb5_ticket **pp_tkt,
00045                                         krb5_keyblock **keyblock,
00046                                         krb5_error_code *perr)
00047 {
00048         krb5_error_code ret = 0;
00049         BOOL auth_ok = False;
00050         krb5_keytab keytab = NULL;
00051         krb5_kt_cursor kt_cursor;
00052         krb5_keytab_entry kt_entry;
00053         char *valid_princ_formats[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
00054         char *entry_princ_s = NULL;
00055         fstring my_name, my_fqdn;
00056         int i;
00057         int number_matched_principals = 0;
00058         krb5_data packet;
00059 
00060         *pp_tkt = NULL;
00061         *keyblock = NULL;
00062         *perr = 0;
00063 
00064         /* Generate the list of principal names which we expect
00065          * clients might want to use for authenticating to the file
00066          * service.  We allow name$,{host,cifs}/{name,fqdn,name.REALM}. */
00067 
00068         fstrcpy(my_name, global_myname());
00069 
00070         my_fqdn[0] = '\0';
00071         name_to_fqdn(my_fqdn, global_myname());
00072 
00073         asprintf(&valid_princ_formats[0], "%s$@%s", my_name, lp_realm());
00074         asprintf(&valid_princ_formats[1], "host/%s@%s", my_name, lp_realm());
00075         asprintf(&valid_princ_formats[2], "host/%s@%s", my_fqdn, lp_realm());
00076         asprintf(&valid_princ_formats[3], "host/%s.%s@%s", my_name, lp_realm(), lp_realm());
00077         asprintf(&valid_princ_formats[4], "cifs/%s@%s", my_name, lp_realm());
00078         asprintf(&valid_princ_formats[5], "cifs/%s@%s", my_fqdn, lp_realm());
00079         asprintf(&valid_princ_formats[6], "cifs/%s.%s@%s", my_name, lp_realm(), lp_realm());
00080 
00081         ZERO_STRUCT(kt_entry);
00082         ZERO_STRUCT(kt_cursor);
00083 
00084         ret = krb5_kt_default(context, &keytab);
00085         if (ret) {
00086                 DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_default failed (%s)\n", error_message(ret)));
00087                 goto out;
00088         }
00089 
00090         /* Iterate through the keytab.  For each key, if the principal
00091          * name case-insensitively matches one of the allowed formats,
00092          * try verifying the ticket using that principal. */
00093 
00094         ret = krb5_kt_start_seq_get(context, keytab, &kt_cursor);
00095         if (ret) {
00096                 DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_start_seq_get failed (%s)\n", error_message(ret)));
00097                 goto out;
00098         }
00099   
00100         while (!auth_ok && (krb5_kt_next_entry(context, keytab, &kt_entry, &kt_cursor) == 0)) {
00101                 ret = smb_krb5_unparse_name(context, kt_entry.principal, &entry_princ_s);
00102                 if (ret) {
00103                         DEBUG(1, ("ads_keytab_verify_ticket: smb_krb5_unparse_name failed (%s)\n",
00104                                 error_message(ret)));
00105                         goto out;
00106                 }
00107 
00108                 for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
00109 
00110                         if (!strequal(entry_princ_s, valid_princ_formats[i])) {
00111                                 continue;
00112                         }
00113 
00114                         number_matched_principals++;
00115                         packet.length = ticket->length;
00116                         packet.data = (char *)ticket->data;
00117                         *pp_tkt = NULL;
00118 
00119                         ret = krb5_rd_req_return_keyblock_from_keytab(context, &auth_context, &packet,
00120                                                                       kt_entry.principal, keytab,
00121                                                                       NULL, pp_tkt, keyblock);
00122 
00123                         if (ret) {
00124                                 DEBUG(10,("ads_keytab_verify_ticket: "
00125                                         "krb5_rd_req_return_keyblock_from_keytab(%s) failed: %s\n",
00126                                         entry_princ_s, error_message(ret)));
00127 
00128                                 /* workaround for MIT: 
00129                                 * as krb5_ktfile_get_entry will explicitly
00130                                 * close the krb5_keytab as soon as krb5_rd_req
00131                                 * has successfully decrypted the ticket but the
00132                                 * ticket is not valid yet (due to clockskew)
00133                                 * there is no point in querying more keytab
00134                                 * entries - Guenther */
00135                                         
00136                                 if (ret == KRB5KRB_AP_ERR_TKT_NYV || 
00137                                     ret == KRB5KRB_AP_ERR_TKT_EXPIRED ||
00138                                     ret == KRB5KRB_AP_ERR_SKEW) {
00139                                         break;
00140                                 }
00141                         } else {
00142                                 DEBUG(3,("ads_keytab_verify_ticket: "
00143                                         "krb5_rd_req_return_keyblock_from_keytab succeeded for principal %s\n",
00144                                         entry_princ_s));
00145                                 auth_ok = True;
00146                                 break;
00147                         }
00148                 }
00149 
00150                 /* Free the name we parsed. */
00151                 SAFE_FREE(entry_princ_s);
00152 
00153                 /* Free the entry we just read. */
00154                 smb_krb5_kt_free_entry(context, &kt_entry);
00155                 ZERO_STRUCT(kt_entry);
00156         }
00157         krb5_kt_end_seq_get(context, keytab, &kt_cursor);
00158 
00159         ZERO_STRUCT(kt_cursor);
00160 
00161   out:
00162         
00163         for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
00164                 SAFE_FREE(valid_princ_formats[i]);
00165         }
00166         
00167         if (!auth_ok) {
00168                 if (!number_matched_principals) {
00169                         DEBUG(3, ("ads_keytab_verify_ticket: no keytab principals matched expected file service name.\n"));
00170                 } else {
00171                         DEBUG(3, ("ads_keytab_verify_ticket: krb5_rd_req failed for all %d matched keytab principals\n",
00172                                 number_matched_principals));
00173                 }
00174         }
00175 
00176         SAFE_FREE(entry_princ_s);
00177 
00178         {
00179                 krb5_keytab_entry zero_kt_entry;
00180                 ZERO_STRUCT(zero_kt_entry);
00181                 if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
00182                         smb_krb5_kt_free_entry(context, &kt_entry);
00183                 }
00184         }
00185 
00186         {
00187                 krb5_kt_cursor zero_csr;
00188                 ZERO_STRUCT(zero_csr);
00189                 if ((memcmp(&kt_cursor, &zero_csr, sizeof(krb5_kt_cursor)) != 0) && keytab) {
00190                         krb5_kt_end_seq_get(context, keytab, &kt_cursor);
00191                 }
00192         }
00193 
00194         if (keytab) {
00195                 krb5_kt_close(context, keytab);
00196         }
00197         *perr = ret;
00198         return auth_ok;
00199 }
00200 
00201 /**********************************************************************************
00202  Try to verify a ticket using the secrets.tdb.
00203 ***********************************************************************************/
00204 
00205 static krb5_error_code ads_secrets_verify_ticket(krb5_context context,
00206                                                 krb5_auth_context auth_context,
00207                                                 krb5_principal host_princ,
00208                                                 const DATA_BLOB *ticket,
00209                                                 krb5_ticket **pp_tkt,
00210                                                 krb5_keyblock **keyblock,
00211                                                 krb5_error_code *perr)
00212 {
00213         krb5_error_code ret = 0;
00214         BOOL auth_ok = False;
00215         char *password_s = NULL;
00216         krb5_data password;
00217         krb5_enctype enctypes[4] = { ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5, 0, 0 };
00218         krb5_data packet;
00219         int i;
00220 
00221         *pp_tkt = NULL;
00222         *keyblock = NULL;
00223         *perr = 0;
00224 
00225 #if defined(ENCTYPE_ARCFOUR_HMAC)
00226         enctypes[2] = ENCTYPE_ARCFOUR_HMAC;
00227 #endif
00228 
00229         if (!secrets_init()) {
00230                 DEBUG(1,("ads_secrets_verify_ticket: secrets_init failed\n"));
00231                 *perr = KRB5_CONFIG_CANTOPEN;
00232                 return False;
00233         }
00234 
00235         password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
00236         if (!password_s) {
00237                 DEBUG(1,("ads_secrets_verify_ticket: failed to fetch machine password\n"));
00238                 *perr = KRB5_LIBOS_CANTREADPWD;
00239                 return False;
00240         }
00241 
00242         password.data = password_s;
00243         password.length = strlen(password_s);
00244 
00245         /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */
00246 
00247         packet.length = ticket->length;
00248         packet.data = (char *)ticket->data;
00249 
00250         /* We need to setup a auth context with each possible encoding type in turn. */
00251         for (i=0;enctypes[i];i++) {
00252                 krb5_keyblock *key = NULL;
00253 
00254                 if (!(key = SMB_MALLOC_P(krb5_keyblock))) {
00255                         ret = ENOMEM;
00256                         goto out;
00257                 }
00258         
00259                 if (create_kerberos_key_from_string(context, host_princ, &password, key, enctypes[i])) {
00260                         SAFE_FREE(key);
00261                         continue;
00262                 }
00263 
00264                 krb5_auth_con_setuseruserkey(context, auth_context, key);
00265 
00266                 if (!(ret = krb5_rd_req(context, &auth_context, &packet, 
00267                                         NULL,
00268                                         NULL, NULL, pp_tkt))) {
00269                         DEBUG(10,("ads_secrets_verify_ticket: enc type [%u] decrypted message !\n",
00270                                 (unsigned int)enctypes[i] ));
00271                         auth_ok = True;
00272                         krb5_copy_keyblock(context, key, keyblock);
00273                         krb5_free_keyblock(context, key);
00274                         break;
00275                 }
00276 
00277                 DEBUG((ret != KRB5_BAD_ENCTYPE) ? 3 : 10,
00278                                 ("ads_secrets_verify_ticket: enc type [%u] failed to decrypt with error %s\n",
00279                                 (unsigned int)enctypes[i], error_message(ret)));
00280 
00281                 /* successfully decrypted but ticket is just not valid at the moment */
00282                 if (ret == KRB5KRB_AP_ERR_TKT_NYV || 
00283                     ret == KRB5KRB_AP_ERR_TKT_EXPIRED ||
00284                     ret == KRB5KRB_AP_ERR_SKEW) {
00285                         break;
00286                 }
00287 
00288                 krb5_free_keyblock(context, key);
00289 
00290         }
00291 
00292  out:
00293         SAFE_FREE(password_s);
00294         *perr = ret;
00295         return auth_ok;
00296 }
00297 
00298 /**********************************************************************************
00299  Verify an incoming ticket and parse out the principal name and 
00300  authorization_data if available.
00301 ***********************************************************************************/
00302 
00303 NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx,
00304                            const char *realm,
00305                            time_t time_offset,
00306                            const DATA_BLOB *ticket,
00307                            char **principal,
00308                            PAC_DATA **pac_data,
00309                            DATA_BLOB *ap_rep,
00310                            DATA_BLOB *session_key)
00311 {
00312         NTSTATUS sret = NT_STATUS_LOGON_FAILURE;
00313         NTSTATUS pac_ret;
00314         DATA_BLOB auth_data;
00315         krb5_context context = NULL;
00316         krb5_auth_context auth_context = NULL;
00317         krb5_data packet;
00318         krb5_ticket *tkt = NULL;
00319         krb5_rcache rcache = NULL;
00320         krb5_keyblock *keyblock = NULL;
00321         time_t authtime;
00322         krb5_error_code ret = 0;
00323         
00324         krb5_principal host_princ = NULL;
00325         krb5_const_principal client_principal = NULL;
00326         char *host_princ_s = NULL;
00327         BOOL auth_ok = False;
00328         BOOL got_replay_mutex = False;
00329         BOOL got_auth_data = False;
00330 
00331         ZERO_STRUCT(packet);
00332         ZERO_STRUCT(auth_data);
00333 
00334         *principal = NULL;
00335         *pac_data = NULL;
00336         *ap_rep = data_blob(NULL,0);
00337         *session_key = data_blob(NULL,0);
00338 
00339         initialize_krb5_error_table();
00340         ret = krb5_init_context(&context);
00341         if (ret) {
00342                 DEBUG(1,("ads_verify_ticket: krb5_init_context failed (%s)\n", error_message(ret)));
00343                 return NT_STATUS_LOGON_FAILURE;
00344         }
00345 
00346         if (time_offset != 0) {
00347                 krb5_set_real_time(context, time(NULL) + time_offset, 0);
00348         }
00349 
00350         ret = krb5_set_default_realm(context, realm);
00351         if (ret) {
00352                 DEBUG(1,("ads_verify_ticket: krb5_set_default_realm failed (%s)\n", error_message(ret)));
00353                 goto out;
00354         }
00355 
00356         /* This whole process is far more complex than I would
00357            like. We have to go through all this to allow us to store
00358            the secret internally, instead of using /etc/krb5.keytab */
00359 
00360         ret = krb5_auth_con_init(context, &auth_context);
00361         if (ret) {
00362                 DEBUG(1,("ads_verify_ticket: krb5_auth_con_init failed (%s)\n", error_message(ret)));
00363                 goto out;
00364         }
00365 
00366         asprintf(&host_princ_s, "%s$", global_myname());
00367         if (!host_princ_s) {
00368                 goto out;
00369         }
00370 
00371         strlower_m(host_princ_s);
00372         ret = smb_krb5_parse_name(context, host_princ_s, &host_princ);
00373         if (ret) {
00374                 DEBUG(1,("ads_verify_ticket: smb_krb5_parse_name(%s) failed (%s)\n",
00375                                         host_princ_s, error_message(ret)));
00376                 goto out;
00377         }
00378 
00379 
00380         /* Lock a mutex surrounding the replay as there is no locking in the MIT krb5
00381          * code surrounding the replay cache... */
00382 
00383         if (!grab_server_mutex("replay cache mutex")) {
00384                 DEBUG(1,("ads_verify_ticket: unable to protect replay cache with mutex.\n"));
00385                 ret = KRB5_CC_IO;
00386                 goto out;
00387         }
00388 
00389         got_replay_mutex = True;
00390 
00391         /*
00392          * JRA. We must set the rcache here. This will prevent replay attacks.
00393          */
00394 
00395         ret = krb5_get_server_rcache(context, krb5_princ_component(context, host_princ, 0), &rcache);
00396         if (ret) {
00397                 DEBUG(1,("ads_verify_ticket: krb5_get_server_rcache failed (%s)\n", error_message(ret)));
00398                 goto out;
00399         }
00400 
00401         ret = krb5_auth_con_setrcache(context, auth_context, rcache);
00402         if (ret) {
00403                 DEBUG(1,("ads_verify_ticket: krb5_auth_con_setrcache failed (%s)\n", error_message(ret)));
00404                 goto out;
00405         }
00406 
00407         if (lp_use_kerberos_keytab()) {
00408                 auth_ok = ads_keytab_verify_ticket(context, auth_context, ticket, &tkt, &keyblock, &ret);
00409         }
00410         if (!auth_ok) {
00411                 auth_ok = ads_secrets_verify_ticket(context, auth_context, host_princ,
00412                                                     ticket, &tkt, &keyblock, &ret);
00413         }
00414 
00415         release_server_mutex();
00416         got_replay_mutex = False;
00417 
00418 #if 0
00419         /* Heimdal leaks here, if we fix the leak, MIT crashes */
00420         if (rcache) {
00421                 krb5_rc_close(context, rcache);
00422         }
00423 #endif
00424 
00425         if (!auth_ok) {
00426                 DEBUG(3,("ads_verify_ticket: krb5_rd_req with auth failed (%s)\n", 
00427                          error_message(ret)));
00428                 /* Try map the error return in case it's something like
00429                  * a clock skew error.
00430                  */
00431                 sret = krb5_to_nt_status(ret);
00432                 if (NT_STATUS_IS_OK(sret) || NT_STATUS_EQUAL(sret,NT_STATUS_UNSUCCESSFUL)) {
00433                         sret = NT_STATUS_LOGON_FAILURE;
00434                 }
00435                 DEBUG(10,("ads_verify_ticket: returning error %s\n",
00436                         nt_errstr(sret) ));
00437                 goto out;
00438         } 
00439         
00440         authtime = get_authtime_from_tkt(tkt);
00441         client_principal = get_principal_from_tkt(tkt);
00442 
00443         ret = krb5_mk_rep(context, auth_context, &packet);
00444         if (ret) {
00445                 DEBUG(3,("ads_verify_ticket: Failed to generate mutual authentication reply (%s)\n",
00446                         error_message(ret)));
00447                 goto out;
00448         }
00449 
00450         *ap_rep = data_blob(packet.data, packet.length);
00451         if (packet.data) {
00452                 kerberos_free_data_contents(context, &packet);
00453                 ZERO_STRUCT(packet);
00454         }
00455 
00456         get_krb5_smb_session_key(context, auth_context, session_key, True);
00457         dump_data_pw("SMB session key (from ticket)\n", session_key->data, session_key->length);
00458 
00459 #if 0
00460         file_save("/tmp/ticket.dat", ticket->data, ticket->length);
00461 #endif
00462 
00463         /* continue when no PAC is retrieved or we couldn't decode the PAC 
00464            (like accounts that have the UF_NO_AUTH_DATA_REQUIRED flag set, or
00465            Kerberos tickets encrypted using a DES key) - Guenther */
00466 
00467         got_auth_data = get_auth_data_from_tkt(mem_ctx, &auth_data, tkt);
00468         if (!got_auth_data) {
00469                 DEBUG(3,("ads_verify_ticket: did not retrieve auth data. continuing without PAC\n"));
00470         }
00471 
00472         if (got_auth_data && pac_data != NULL) {
00473 
00474                 pac_ret = decode_pac_data(mem_ctx, &auth_data, context, keyblock, client_principal, authtime, pac_data);
00475                 if (!NT_STATUS_IS_OK(pac_ret)) {
00476                         DEBUG(3,("ads_verify_ticket: failed to decode PAC_DATA: %s\n", nt_errstr(pac_ret)));
00477                         *pac_data = NULL;
00478                 }
00479                 data_blob_free(&auth_data);
00480         }
00481 
00482 #if 0
00483 #if defined(HAVE_KRB5_TKT_ENC_PART2)
00484         /* MIT */
00485         if (tkt->enc_part2) {
00486                 file_save("/tmp/authdata.dat",
00487                           tkt->enc_part2->authorization_data[0]->contents,
00488                           tkt->enc_part2->authorization_data[0]->length);
00489         }
00490 #else
00491         /* Heimdal */
00492         if (tkt->ticket.authorization_data) {
00493                 file_save("/tmp/authdata.dat",
00494                           tkt->ticket.authorization_data->val->ad_data.data,
00495                           tkt->ticket.authorization_data->val->ad_data.length);
00496         }
00497 #endif
00498 #endif
00499 
00500         if ((ret = smb_krb5_unparse_name(context, client_principal, principal))) {
00501                 DEBUG(3,("ads_verify_ticket: smb_krb5_unparse_name failed (%s)\n", 
00502                          error_message(ret)));
00503                 sret = NT_STATUS_LOGON_FAILURE;
00504                 goto out;
00505         }
00506 
00507         sret = NT_STATUS_OK;
00508 
00509  out:
00510 
00511         if (got_replay_mutex) {
00512                 release_server_mutex();
00513         }
00514 
00515         if (!NT_STATUS_IS_OK(sret)) {
00516                 data_blob_free(&auth_data);
00517         }
00518 
00519         if (!NT_STATUS_IS_OK(sret)) {
00520                 data_blob_free(ap_rep);
00521         }
00522 
00523         if (host_princ) {
00524                 krb5_free_principal(context, host_princ);
00525         }
00526 
00527         if (keyblock) {
00528                 krb5_free_keyblock(context, keyblock);
00529         }
00530 
00531         if (tkt != NULL) {
00532                 krb5_free_ticket(context, tkt);
00533         }
00534 
00535         SAFE_FREE(host_princ_s);
00536 
00537         if (auth_context) {
00538                 krb5_auth_con_free(context, auth_context);
00539         }
00540 
00541         if (context) {
00542                 krb5_free_context(context);
00543         }
00544 
00545         return sret;
00546 }
00547 
00548 #endif /* HAVE_KRB5 */

Sambaに対してSat Aug 29 21:23:01 2009に生成されました。  doxygen 1.4.7