/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* plugins/preauth/securid_sam2/securid2.c */ /* * Copyright (C) 2010 by the Massachusetts Institute of Technology. * All rights reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. */ /* * Copyright (c) 2002 Naval Research Laboratory (NRL/CCS) * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the software, * derivative works or modified versions, and any portions thereof. * * NRL ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION AND * DISCLAIMS ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER * RESULTING FROM THE USE OF THIS SOFTWARE. */ #include "k5-int.h" #include #include #include #include #include #include #include #include "extern.h" #define KRB5_SAM_SECURID_NEXT_CHALLENGE_MAGIC 0x5ec1d000 struct securid_track_data { SDI_HANDLE handle; char state; char passcode[LENPRNST+1]; long hostid; }; #define SECURID_STATE_NEW_PIN 1 /* Ask for a new pin */ #define SECURID_STATE_NEW_PIN_AGAIN 2 /* Ask for new pin again */ #define SECURID_STATE_NEXT_CODE 3 /* Ask for the next pin code */ #define SECURID_STATE_INITIAL 4 static char *PASSCODE_message = "SecurID Passcode"; static char *NEXT_PASSCODE_message = "Next Passcode"; static char *NEW_PIN_AGAIN_message = "New PIN Again"; static char PIN_message[64]; /* Max length should be 50 chars */ /* * krb5_error_code get_securid_key(): * inputs: context: from KDC process * client: database entry of client executing * SecurID SAM preauthentication * outputs: client_securid_key: pointer to krb5_keyblock * which is key for the client's SecurID * database entry. * returns: 0 on success * KRB5 error codes otherwise * * builds pricipal name with final instance of "SECURID" and * finds the database entry, decrypts the key out of the database * and passes the key back to the calling process */ static krb5_error_code get_securid_key(krb5_context context, krb5_db_entry *client, krb5_keyblock *client_securid_key) { krb5_db_entry *sam_securid_entry = NULL; krb5_key_data *client_securid_key_data = NULL; int sam_type = PA_SAM_TYPE_SECURID; krb5_error_code retval = 0; if (!client_securid_key) return KRB5_PREAUTH_NO_KEY; retval = sam_get_db_entry(context, client->princ, &sam_type, &sam_securid_entry); if (retval) return KRB5_PREAUTH_NO_KEY; /* Find key with key_type = salt_type = kvno = -1. This finds the */ /* latest kvno in the list. */ retval = krb5_dbe_find_enctype(context, sam_securid_entry, -1, -1, -1, &client_securid_key_data); if (retval) { com_err("krb5kdc", retval, "while getting key from client's SAM SecurID entry"); goto cleanup; } retval = krb5_dbe_decrypt_key_data(context, NULL, client_securid_key_data, client_securid_key, NULL); if (retval) { com_err("krb5kdc", retval, "while decrypting key from client's SAM SecurID entry"); goto cleanup; } cleanup: if (sam_securid_entry) krb5_db_free_principal(context, sam_securid_entry); return retval; } krb5_error_code securid_make_sam_challenge_2_and_cksum(krb5_context context, krb5_sam_challenge_2 *sc2, krb5_sam_challenge_2_body *sc2b, krb5_keyblock *cksum_key) { krb5_error_code retval; krb5_checksum **cksum_array = NULL; krb5_checksum *cksum = NULL; krb5_cksumtype cksumtype; krb5_data *encoded_challenge_body = NULL; if (!cksum_key) return KRB5_PREAUTH_NO_KEY; if (!sc2 || !sc2b) return KRB5KDC_ERR_PREAUTH_FAILED; retval = encode_krb5_sam_challenge_2_body(sc2b, &encoded_challenge_body); if (retval || !encoded_challenge_body) { encoded_challenge_body = NULL; goto cksum_cleanup; } cksum_array = calloc(2, sizeof(krb5_checksum *)); if (!cksum_array) { retval = ENOMEM; goto cksum_cleanup; } cksum = (krb5_checksum *)k5alloc(sizeof(krb5_checksum), &retval); if (retval) goto cksum_cleanup; cksum_array[0] = cksum; cksum_array[1] = NULL; retval = krb5int_c_mandatory_cksumtype(context, cksum_key->enctype, &cksumtype); if (retval) goto cksum_cleanup; retval = krb5_c_make_checksum(context, cksumtype, cksum_key, KRB5_KEYUSAGE_PA_SAM_CHALLENGE_CKSUM, encoded_challenge_body, cksum); if (retval) goto cksum_cleanup; sc2->sam_cksum = cksum_array; sc2->sam_challenge_2_body = *encoded_challenge_body; return 0; cksum_cleanup: krb5_free_data(context, encoded_challenge_body); free(cksum_array); free(cksum); return retval; } static krb5_error_code securid_decrypt_track_data_2(krb5_context context, krb5_db_entry *client, krb5_data *enc_track_data, krb5_data *output) { krb5_error_code retval; krb5_keyblock sam_key; krb5_enc_data tmp_enc_data; sam_key.contents = NULL; retval = get_securid_key(context, client, &sam_key); if (retval != 0) return retval; tmp_enc_data.ciphertext = *enc_track_data; tmp_enc_data.enctype = ENCTYPE_UNKNOWN; tmp_enc_data.kvno = 0; output->length = tmp_enc_data.ciphertext.length; free(output->data); output->data = k5alloc(output->length, &retval); if (output->data == NULL) goto cleanup; retval = krb5_c_decrypt(context, &sam_key, KRB5_KEYUSAGE_PA_SAM_CHALLENGE_TRACKID, 0, &tmp_enc_data, output); cleanup: krb5_free_keyblock_contents(context, &sam_key); if (retval) { output->length = 0; free(output->data); output->data = NULL; return retval; } return 0; } static krb5_error_code securid_encrypt_track_data_2(krb5_context context, krb5_db_entry *client, krb5_data *track_data, krb5_data *output) { krb5_error_code retval; size_t olen; krb5_keyblock sam_key; krb5_enc_data tmp_enc_data; output->data = NULL; retval = get_securid_key(context,client, &sam_key); if (retval != 0) return retval; retval = krb5_c_encrypt_length(context, sam_key.enctype, track_data->length, &olen); if (retval != 0) goto cleanup; assert(olen <= 65536); output->length = olen; output->data = k5alloc(output->length, &retval); if (retval) goto cleanup; tmp_enc_data.ciphertext = *output; tmp_enc_data.enctype = sam_key.enctype; tmp_enc_data.kvno = 0; retval = krb5_c_encrypt(context, &sam_key, KRB5_KEYUSAGE_PA_SAM_CHALLENGE_TRACKID, 0, track_data, &tmp_enc_data); cleanup: krb5_free_keyblock_contents(context, &sam_key); if (retval) { output->length = 0; free(output->data); output->data = NULL; return retval; } return 0; } krb5_error_code get_securid_edata_2(krb5_context context, krb5_db_entry *client, krb5_keyblock *client_key, krb5_sam_challenge_2_body *sc2b, krb5_sam_challenge_2 *sc2) { krb5_error_code retval; krb5_data scratch; char *user = NULL; char *def_user = ""; struct securid_track_data sid_track_data; krb5_data tmp_data; scratch.data = NULL; sc2b->sam_track_id.data = NULL; retval = krb5_unparse_name(context, client->princ, &user); if (retval) goto cleanup; sc2b->sam_flags = KRB5_SAM_SEND_ENCRYPTED_SAD; sc2b->sam_type_name.length = 0; sc2b->sam_challenge_label.length = 0; sc2b->sam_challenge.length = 0; sc2b->sam_response_prompt.data = PASSCODE_message; sc2b->sam_response_prompt.length = strlen(sc2b->sam_response_prompt.data); sc2b->sam_pk_for_sad.length = 0; sc2b->sam_type = PA_SAM_TYPE_SECURID; sid_track_data.state = SECURID_STATE_INITIAL; sid_track_data.hostid = gethostid(); tmp_data.data = (char *)&sid_track_data; tmp_data.length = sizeof(sid_track_data); retval = securid_encrypt_track_data_2(context, client, &tmp_data, &sc2b->sam_track_id); if (retval != 0) { com_err("krb5kdc", retval, "while encrypting nonce track data"); goto cleanup; } scratch.data = (char *)&sc2b->sam_nonce; scratch.length = sizeof(sc2b->sam_nonce); retval = krb5_c_random_make_octets(context, &scratch); if (retval) { com_err("krb5kdc", retval, "while generating nonce data in get_securid_edata_2 (%s)", user ? user : def_user); goto cleanup; } /* Get the client's key */ sc2b->sam_etype = client_key->enctype; retval = securid_make_sam_challenge_2_and_cksum(context, sc2, sc2b, client_key); if (retval) { com_err("krb5kdc", retval, "while making SAM_CHALLENGE_2 checksum (%s)", user ? user : def_user); } cleanup: free(user); if (retval) { krb5_free_data_contents(context, &sc2b->sam_track_id); sc2b->sam_track_id.data = NULL; } return retval; } krb5_error_code verify_securid_data_2(krb5_context context, krb5_db_entry *client, krb5_sam_response_2 *sr2, krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *pa, krb5_sam_challenge_2 **sc2_out) { krb5_error_code retval; int new_pin = 0; krb5_key_data *client_key_data = NULL; krb5_keyblock client_key; krb5_data scratch; krb5_enc_sam_response_enc_2 *esre2 = NULL; struct securid_track_data sid_track_data, *trackp = NULL; krb5_data tmp_data; SDI_HANDLE sd_handle = SDI_HANDLE_NONE; krb5_sam_challenge_2 *sc2p = NULL; char *cp, *user = NULL; char *securid_user = NULL; char passcode[LENPRNST+1]; char max_pin_len, min_pin_len, alpha_pin; memset(&client_key, 0, sizeof(client_key)); memset(&scratch, 0, sizeof(scratch)); *sc2_out = NULL; retval = krb5_unparse_name(context, client->princ, &user); if (retval != 0) { com_err("krb5kdc", retval, "while unparsing client name in verify_securid_data_2"); return retval; } if ((sr2->sam_enc_nonce_or_sad.ciphertext.data == NULL) || (sr2->sam_enc_nonce_or_sad.ciphertext.length <= 0)) { retval = KRB5KDC_ERR_PREAUTH_FAILED; krb5_set_error_message(context, retval, "No preauth data supplied in " "verify_securid_data_2 (%s)", user); goto cleanup; } retval = krb5_dbe_find_enctype(context, client, sr2->sam_enc_nonce_or_sad.enctype, KRB5_KDB_SALTTYPE_NORMAL, sr2->sam_enc_nonce_or_sad.kvno, &client_key_data); if (retval) { com_err("krb5kdc", retval, "while getting client key in verify_securid_data_2 (%s)", user); goto cleanup; } retval = krb5_dbe_decrypt_key_data(context, NULL, client_key_data, &client_key, NULL); if (retval != 0) { com_err("krb5kdc", retval, "while decrypting client key in verify_securid_data_2 (%s)", user); goto cleanup; } scratch.length = sr2->sam_enc_nonce_or_sad.ciphertext.length; scratch.data = k5alloc(scratch.length, &retval); if (retval) goto cleanup; retval = krb5_c_decrypt(context, &client_key, KRB5_KEYUSAGE_PA_SAM_RESPONSE, 0, &sr2->sam_enc_nonce_or_sad, &scratch); if (retval) { com_err("krb5kdc", retval, "while decrypting SAD in verify_securid_data_2 (%s)", user); goto cleanup; } retval = decode_krb5_enc_sam_response_enc_2(&scratch, &esre2); if (retval) { com_err("krb5kdc", retval, "while decoding SAD in verify_securid_data_2 (%s)", user); esre2 = NULL; goto cleanup; } if (sr2->sam_nonce != esre2->sam_nonce) { com_err("krb5kdc", KRB5KDC_ERR_PREAUTH_FAILED, "while checking nonce in verify_securid_data_2 (%s)", user); retval = KRB5KDC_ERR_PREAUTH_FAILED; goto cleanup; } if (esre2->sam_sad.length == 0 || esre2->sam_sad.data == NULL) { com_err("krb5kdc", KRB5KDC_ERR_PREAUTH_FAILED, "No SecurID passcode in verify_securid_data_2 (%s)", user); retval = KRB5KDC_ERR_PREAUTH_FAILED; goto cleanup; } /* Copy out SAD to null-terminated buffer */ memset(passcode, 0, sizeof(passcode)); if (esre2->sam_sad.length > (sizeof(passcode) - 1)) { retval = KRB5KDC_ERR_PREAUTH_FAILED; com_err("krb5kdc", retval, "SecurID passcode/PIN too long (%d bytes) in " "verify_securid_data_2 (%s)", esre2->sam_sad.length, user); goto cleanup; } memcpy(passcode, esre2->sam_sad.data, esre2->sam_sad.length); securid_user = strdup(user); if (!securid_user) { retval = ENOMEM; com_err("krb5kdc", ENOMEM, "while copying user name in verify_securid_data_2 (%s)", user); goto cleanup; } cp = strchr(securid_user, '@'); if (cp != NULL) *cp = '\0'; /* Check for any track_id data that may have state from a previous attempt * at SecurID authentication. */ if (sr2->sam_track_id.data && (sr2->sam_track_id.length > 0)) { krb5_data track_id_data; memset(&track_id_data, 0, sizeof(track_id_data)); retval = securid_decrypt_track_data_2(context, client, &sr2->sam_track_id, &track_id_data); if (retval) { com_err("krb5kdc", retval, "while decrypting SecurID trackID in " "verify_securid_data_2 (%s)", user); goto cleanup; } if (track_id_data.length < sizeof (struct securid_track_data)) { retval = KRB5KDC_ERR_PREAUTH_FAILED; com_err("krb5kdc", retval, "Length of track data incorrect"); goto cleanup; } trackp = (struct securid_track_data *)track_id_data.data; if(trackp->hostid != gethostid()) { krb5_klog_syslog(LOG_INFO, "Unexpected challenge response"); retval = KRB5KDC_ERR_DISCARD; goto cleanup; } switch(trackp->state) { case SECURID_STATE_INITIAL: goto initial; break; case SECURID_STATE_NEW_PIN_AGAIN: { int pin1_len, pin2_len; trackp->handle = ntohl(trackp->handle); pin2_len = strlen(passcode); pin1_len = strlen(trackp->passcode); if ((pin1_len != pin2_len) || (memcmp(passcode, trackp->passcode, pin1_len) != 0)) { retval = KRB5KDC_ERR_PREAUTH_FAILED; krb5_klog_syslog(LOG_INFO, "New SecurID PIN Failed for user " "%s: PIN mis-match", user); break; } retval = SD_Pin(trackp->handle, passcode); SD_Close(trackp->handle); if (retval == ACM_NEW_PIN_ACCEPTED) { enc_tkt_reply->flags|= TKT_FLG_HW_AUTH; enc_tkt_reply->flags|= TKT_FLG_PRE_AUTH; krb5_klog_syslog(LOG_INFO, "SecurID PIN Accepted for %s in " "verify_securid_data_2", securid_user); retval = 0; } else { retval = KRB5KDC_ERR_PREAUTH_FAILED; krb5_klog_syslog(LOG_INFO, "SecurID PIN Failed for user %s (AceServer " "returns %d) in verify_securid_data_2", user, retval); } break; } case SECURID_STATE_NEW_PIN: { krb5_sam_challenge_2_body sc2b; sc2p = k5alloc(sizeof *sc2p, &retval); if (retval) goto cleanup; memset(sc2p, 0, sizeof(*sc2p)); memset(&sc2b, 0, sizeof(sc2b)); sc2b.sam_type = PA_SAM_TYPE_SECURID; sc2b.sam_response_prompt.data = NEW_PIN_AGAIN_message; sc2b.sam_response_prompt.length = strlen(sc2b.sam_response_prompt.data); sc2b.sam_flags = KRB5_SAM_SEND_ENCRYPTED_SAD; sc2b.sam_etype = client_key.enctype; tmp_data.data = (char *)&sc2b.sam_nonce; tmp_data.length = sizeof(sc2b.sam_nonce); if ((retval = krb5_c_random_make_octets(context, &tmp_data))) { com_err("krb5kdc", retval, "while making nonce for SecurID new " "PIN2 SAM_CHALLENGE_2 (%s)", user); goto cleanup; } sid_track_data.state = SECURID_STATE_NEW_PIN_AGAIN; sid_track_data.handle = trackp->handle; sid_track_data.hostid = gethostid(); /* Should we complain if sizes don't work ?? */ memcpy(sid_track_data.passcode, passcode, sizeof(sid_track_data.passcode)); tmp_data.data = (char *)&sid_track_data; tmp_data.length = sizeof(sid_track_data); if ((retval = securid_encrypt_track_data_2(context, client, &tmp_data, &sc2b.sam_track_id))) { com_err("krb5kdc", retval, "while encrypting NEW PIN2 SecurID " "track data for SAM_CHALLENGE_2 (%s)", securid_user); goto cleanup; } retval = securid_make_sam_challenge_2_and_cksum(context, sc2p, &sc2b, &client_key); if (retval) { com_err("krb5kdc", retval, "while making cksum for " "SAM_CHALLENGE_2 (new PIN2) (%s)", securid_user); goto cleanup; } krb5_klog_syslog(LOG_INFO, "Requesting verification of new PIN for user %s", securid_user); *sc2_out = sc2p; sc2p = NULL; /*sc2_out may be set even on error path*/ retval = KRB5KDC_ERR_PREAUTH_REQUIRED; goto cleanup; } case SECURID_STATE_NEXT_CODE: trackp->handle = ntohl(trackp->handle); retval = SD_Next(trackp->handle, passcode); SD_Close(trackp->handle); if (retval == ACM_OK) { enc_tkt_reply->flags |= TKT_FLG_HW_AUTH | TKT_FLG_PRE_AUTH; krb5_klog_syslog(LOG_INFO, "Next SecurID Code Accepted for " "user %s", securid_user); retval = 0; } else { krb5_klog_syslog(LOG_INFO, "Next SecurID Code Failed for user " "%s (AceServer returns %d) in " "verify_securid_data_2", user, retval); retval = KRB5KDC_ERR_PREAUTH_FAILED; } break; } } else { /* No track data, this is first of N attempts */ initial: retval = SD_Init(&sd_handle); if (retval) { com_err("krb5kdc", KRB5KDC_ERR_PREAUTH_FAILED, "SD_Init() returns error %d in verify_securid_data_2 (%s)", retval, securid_user); retval = KRB5KDC_ERR_PREAUTH_FAILED; goto cleanup; } retval = SD_Lock(sd_handle, securid_user); if (retval != ACM_OK) { SD_Close(sd_handle); retval = KRB5KDC_ERR_PREAUTH_FAILED; krb5_klog_syslog(LOG_INFO, "SD_Lock() failed (AceServer returns %d) for %s", retval, securid_user); goto cleanup; } retval = SD_Check(sd_handle, passcode, securid_user); switch (retval) { case ACM_OK: SD_Close(sd_handle); enc_tkt_reply->flags|= TKT_FLG_HW_AUTH; enc_tkt_reply->flags|= TKT_FLG_PRE_AUTH; krb5_klog_syslog(LOG_INFO, "SecurID passcode accepted for user %s", user); retval = 0; break; case ACM_ACCESS_DENIED: SD_Close(sd_handle); retval = KRB5KDC_ERR_PREAUTH_FAILED; krb5_klog_syslog(LOG_INFO, "AceServer returns Access Denied for " "user %s (SAM2)", user); goto cleanup; case ACM_NEW_PIN_REQUIRED: new_pin = 1; /*fall through*/ case ACM_NEXT_CODE_REQUIRED: { krb5_sam_challenge_2_body sc2b; sc2p = k5alloc(sizeof *sc2p, &retval); if (retval) goto cleanup; memset(sc2p, 0, sizeof(*sc2p)); memset(&sc2b, 0, sizeof(sc2b)); sc2b.sam_type = PA_SAM_TYPE_SECURID; sc2b.sam_response_prompt.data = NEXT_PASSCODE_message; sc2b.sam_response_prompt.length = strlen(sc2b.sam_response_prompt.data); sc2b.sam_flags = KRB5_SAM_SEND_ENCRYPTED_SAD; sc2b.sam_etype = client_key.enctype; if (new_pin) { if ((AceGetMaxPinLen(sd_handle, &max_pin_len) == ACE_SUCCESS) && (AceGetMinPinLen(sd_handle, &min_pin_len) == ACE_SUCCESS) && (AceGetAlphanumeric(sd_handle, &alpha_pin) == ACE_SUCCESS)) { sprintf(PIN_message, "New PIN must contain %d to %d %sdigits", min_pin_len, max_pin_len, (alpha_pin == 0) ? "" : "alphanumeric "); sc2b.sam_challenge_label.data = PIN_message; sc2b.sam_challenge_label.length = strlen(sc2b.sam_challenge_label.data); } else { sc2b.sam_challenge_label.length = 0; } } tmp_data.data = (char *)&sc2b.sam_nonce; tmp_data.length = sizeof(sc2b.sam_nonce); if ((retval = krb5_c_random_make_octets(context, &tmp_data))) { com_err("krb5kdc", retval, "while making nonce for SecurID SAM_CHALLENGE_2 (%s)", user); goto cleanup; } if (new_pin) sid_track_data.state = SECURID_STATE_NEW_PIN; else sid_track_data.state = SECURID_STATE_NEXT_CODE; sid_track_data.handle = htonl(sd_handle); sid_track_data.hostid = gethostid(); tmp_data.data = (char *)&sid_track_data; tmp_data.length = sizeof(sid_track_data); retval = securid_encrypt_track_data_2(context, client, &tmp_data, &sc2b.sam_track_id); if (retval) { com_err("krb5kdc", retval, "while encrypting SecurID track " "data for SAM_CHALLENGE_2 (%s)", securid_user); goto cleanup; } retval = securid_make_sam_challenge_2_and_cksum(context, sc2p, &sc2b, &client_key); if (retval) { com_err("krb5kdc", retval, "while making cksum for SAM_CHALLENGE_2 (%s)", securid_user); } if (new_pin) krb5_klog_syslog(LOG_INFO, "New SecurID PIN required for " "user %s", securid_user); else krb5_klog_syslog(LOG_INFO, "Next SecurID passcode required " "for user %s", securid_user); *sc2_out = sc2p; sc2p = NULL; retval = KRB5KDC_ERR_PREAUTH_REQUIRED; /*sc2_out is permitted as an output on error path*/ goto cleanup; } default: com_err("krb5kdc", KRB5KDC_ERR_PREAUTH_FAILED, "AceServer returns unknown error code %d " "in verify_securid_data_2\n", retval); retval = KRB5KDC_ERR_PREAUTH_FAILED; goto cleanup; } } /* no track_id data */ cleanup: krb5_free_keyblock_contents(context, &client_key); free(scratch.data); krb5_free_enc_sam_response_enc_2(context, esre2); free(user); free(securid_user); free(trackp); krb5_free_sam_challenge_2(context, sc2p); return retval; }