/* Allocate, fill, and return cksumlens array of checksum lengths. ** This array holds just the unique elements from the krb5_cksumarray[]. ** array[n] == 0 signals end of data. ** ** The krb5_cksumarray[] was an internal variable that has since been ** replaced by a more general method for storing the data. It should ** not be used. Instead we use real API calls and make a guess for ** what the highest assigned CKSUMTYPE_ constant is. As of 1.2.2 ** it is 0x000c (CKSUMTYPE_HMAC_SHA1_DES3). So we will use 0x0010. */ static size_t *populate_cksumlens(void) { int i, j, n; static size_t *cklens = NULL; #ifdef KRB5_MIT_OLD11 n = krb5_max_cksum; #else n = 0x0010; #endif /* KRB5_MIT_OLD11 */ #ifdef KRB5CHECKAUTH if (!cklens && !(cklens = (size_t *) calloc(sizeof(int), n + 1))) return NULL; for (i = 0; i < n; i++) { if (!valid_cksumtype(i)) continue; /* array has holes */ for (j = 0; j < n; j++) { if (cklens[j] == 0) { cklens[j] = krb5_checksum_size(NULL, i); break; /* krb5 elem was new: add */ } if (cklens[j] == krb5_checksum_size(NULL, i)) { break; /* ignore duplicate elements */ } } } #endif /* KRB5CHECKAUTH */ return cklens; }
int ksm_process_in_msg(struct snmp_secmod_incoming_params *parms) { long temp; krb5_cksumtype cksumtype; krb5_auth_context auth_context = NULL; krb5_error_code retcode; krb5_checksum checksum; krb5_data ap_req, ivector; krb5_flags flags; krb5_keyblock *subkey = NULL; #ifdef MIT_NEW_CRYPTO krb5_data input, output; krb5_boolean valid; krb5_enc_data in_crypt; #else /* MIT_NEW_CRYPTO */ krb5_encrypt_block eblock; #endif /* MIT_NEW_CRYPTO */ krb5_ticket *ticket = NULL; int retval = SNMPERR_SUCCESS, response = 0; size_t length = parms->wholeMsgLen - (u_int) (parms->secParams - parms->wholeMsg); u_char *current = parms->secParams, type; size_t cksumlength, blocksize; long hint; char *cname; struct ksm_secStateRef *ksm_state; struct ksm_cache_entry *entry; DEBUGMSGTL(("ksm", "Processing has begun\n")); checksum.contents = NULL; ap_req.data = NULL; ivector.length = 0; ivector.data = NULL; /* * First, parse the security parameters (because we need the subkey inside * of the ticket to do anything */ if ((current = asn_parse_sequence(current, &length, &type, (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR), "ksm first octet")) == NULL) { DEBUGMSGTL(("ksm", "Initial security paramter parsing failed\n")); retval = SNMPERR_ASN_PARSE_ERR; goto error; } if ((current = asn_parse_sequence(current, &length, &type, (ASN_SEQUENCE | ASN_CONSTRUCTOR), "ksm sequence")) == NULL) { DEBUGMSGTL(("ksm", "Security parameter sequence parsing failed\n")); retval = SNMPERR_ASN_PARSE_ERR; goto error; } if ((current = asn_parse_int(current, &length, &type, &temp, sizeof(temp))) == NULL) { DEBUGMSGTL(("ksm", "Security parameter checksum type parsing" "failed\n")); retval = SNMPERR_ASN_PARSE_ERR; goto error; } cksumtype = temp; #ifdef MIT_NEW_CRYPTO if (!krb5_c_valid_cksumtype(cksumtype)) { DEBUGMSGTL(("ksm", "Invalid checksum type (%d)\n", cksumtype)); retval = SNMPERR_KRB5; snmp_set_detail("Invalid checksum type"); goto error; } if (!krb5_c_is_keyed_cksum(cksumtype)) { DEBUGMSGTL(("ksm", "Checksum type %d is not a keyed checksum\n", cksumtype)); snmp_set_detail("Checksum is not a keyed checksum"); retval = SNMPERR_KRB5; goto error; } if (!krb5_c_is_coll_proof_cksum(cksumtype)) { DEBUGMSGTL(("ksm", "Checksum type %d is not a collision-proof " "checksum\n", cksumtype)); snmp_set_detail("Checksum is not a collision-proof checksum"); retval = SNMPERR_KRB5; goto error; } #else /* ! MIT_NEW_CRYPTO */ if (!valid_cksumtype(cksumtype)) { DEBUGMSGTL(("ksm", "Invalid checksum type (%d)\n", cksumtype)); retval = SNMPERR_KRB5; snmp_set_detail("Invalid checksum type"); goto error; } if (!is_keyed_cksum(cksumtype)) { DEBUGMSGTL(("ksm", "Checksum type %d is not a keyed checksum\n", cksumtype)); snmp_set_detail("Checksum is not a keyed checksum"); retval = SNMPERR_KRB5; goto error; } if (!is_coll_proof_cksum(cksumtype)) { DEBUGMSGTL(("ksm", "Checksum type %d is not a collision-proof " "checksum\n", cksumtype)); snmp_set_detail("Checksum is not a collision-proof checksum"); retval = SNMPERR_KRB5; goto error; } #endif /* MIT_NEW_CRYPTO */ checksum.checksum_type = cksumtype; cksumlength = length; if ((current = asn_parse_sequence(current, &cksumlength, &type, (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR), "ksm checksum")) == NULL) { DEBUGMSGTL(("ksm", "Security parameter checksum parsing failed\n")); retval = SNMPERR_ASN_PARSE_ERR; goto error; } checksum.contents = malloc(cksumlength); if (!checksum.contents) { DEBUGMSGTL(("ksm", "Unable to malloc %d bytes for checksum.\n", cksumlength)); retval = SNMPERR_MALLOC; goto error; } memcpy(checksum.contents, current, cksumlength); checksum.length = cksumlength; checksum.checksum_type = cksumtype; /* * Zero out the checksum so the validation works correctly */ memset(current, 0, cksumlength); current += cksumlength; length = parms->wholeMsgLen - (u_int) (current - parms->wholeMsg); if ((current = asn_parse_sequence(current, &length, &type, (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR), "ksm ap_req")) == NULL) { DEBUGMSGTL(("ksm", "KSM security parameter AP_REQ/REP parsing " "failed\n")); retval = SNMPERR_ASN_PARSE_ERR; goto error; } ap_req.length = length; ap_req.data = malloc(length); if (!ap_req.data) { DEBUGMSGTL(("ksm", "KSM unable to malloc %d bytes for AP_REQ/REP.\n", length)); retval = SNMPERR_MALLOC; goto error; } memcpy(ap_req.data, current, length); current += length; length = parms->wholeMsgLen - (u_int) (current - parms->wholeMsg); if ((current = asn_parse_int(current, &length, &type, &hint, sizeof(hint))) == NULL) { DEBUGMSGTL(("ksm", "KSM security parameter hint parsing failed\n")); retval = SNMPERR_ASN_PARSE_ERR; goto error; } /* * Okay! We've got it all! Now try decoding the damn ticket. * * But of course there's a WRINKLE! We need to figure out if we're * processing a AP_REQ or an AP_REP. How do we do that? We're going * to cheat, and look at the first couple of bytes (which is what * the Kerberos library routines do anyway). * * If there are ever new Kerberos message formats, we'll need to fix * this here. * * If it's a _response_, then we need to get the auth_context * from our cache. */ if (ap_req.length && (ap_req.data[0] == 0x6e || ap_req.data[0] == 0x4e)) { /* * We need to initalize the authorization context, and set the * replay cache in it (and initialize the replay cache if we * haven't already */ retcode = krb5_auth_con_init(kcontext, &auth_context); if (retcode) { DEBUGMSGTL(("ksm", "krb5_auth_con_init failed: %s\n", error_message(retcode))); retval = SNMPERR_KRB5; snmp_set_detail(error_message(retcode)); goto error; } if (!rcache) { krb5_data server; server.data = "host"; server.length = strlen(server.data); retcode = krb5_get_server_rcache(kcontext, &server, &rcache); if (retcode) { DEBUGMSGTL(("ksm", "krb5_get_server_rcache failed: %s\n", error_message(retcode))); retval = SNMPERR_KRB5; snmp_set_detail(error_message(retcode)); goto error; } } retcode = krb5_auth_con_setrcache(kcontext, auth_context, rcache); if (retcode) { DEBUGMSGTL(("ksm", "krb5_auth_con_setrcache failed: %s\n", error_message(retcode))); retval = SNMPERR_KRB5; snmp_set_detail(error_message(retcode)); goto error; } retcode = krb5_rd_req(kcontext, &auth_context, &ap_req, NULL, keytab, &flags, &ticket); krb5_auth_con_setrcache(kcontext, auth_context, NULL); if (retcode) { DEBUGMSGTL(("ksm", "krb5_rd_req() failed: %s\n", error_message(retcode))); retval = SNMPERR_KRB5; snmp_set_detail(error_message(retcode)); goto error; } retcode = krb5_unparse_name(kcontext, ticket->enc_part2->client, &cname); if (retcode == 0) { DEBUGMSGTL(("ksm", "KSM authenticated principal name: %s\n", cname)); free(cname); } /* * Check to make sure AP_OPTS_MUTUAL_REQUIRED was set */ if (!(flags & AP_OPTS_MUTUAL_REQUIRED)) { DEBUGMSGTL(("ksm", "KSM MUTUAL_REQUIRED not set in request!\n")); retval = SNMPERR_KRB5; snmp_set_detail("MUTUAL_REQUIRED not set in message"); goto error; } retcode = krb5_auth_con_getremotesubkey(kcontext, auth_context, &subkey); if (retcode) { DEBUGMSGTL(("ksm", "KSM remote subkey retrieval failed: %s\n", error_message(retcode))); retval = SNMPERR_KRB5; snmp_set_detail(error_message(retcode)); goto error; } } else if (ap_req.length && (ap_req.data[0] == 0x6f || ap_req.data[0] == 0x4f)) { /* * Looks like a response; let's see if we've got that auth_context * in our cache. */ krb5_ap_rep_enc_part *repl = NULL; response = 1; entry = ksm_get_cache(parms->pdu->msgid); if (!entry) { DEBUGMSGTL(("ksm", "KSM: Unable to find auth_context for PDU with " "message ID of %ld\n", parms->pdu->msgid)); retval = SNMPERR_KRB5; goto error; } auth_context = entry->auth_context; /* * In that case, let's call the rd_rep function */ retcode = krb5_rd_rep(kcontext, auth_context, &ap_req, &repl); if (repl) krb5_free_ap_rep_enc_part(kcontext, repl); if (retcode) { DEBUGMSGTL(("ksm", "KSM: krb5_rd_rep() failed: %s\n", error_message(retcode))); retval = SNMPERR_KRB5; goto error; } DEBUGMSGTL(("ksm", "KSM: krb5_rd_rep() decoded successfully.\n")); retcode = krb5_auth_con_getlocalsubkey(kcontext, auth_context, &subkey); if (retcode) { DEBUGMSGTL(("ksm", "Unable to retrieve local subkey: %s\n", error_message(retcode))); retval = SNMPERR_KRB5; snmp_set_detail("Unable to retrieve local subkey"); goto error; } } else { DEBUGMSGTL(("ksm", "Unknown Kerberos message type (%02x)\n", ap_req.data[0])); retval = SNMPERR_KRB5; snmp_set_detail("Unknown Kerberos message type"); goto error; } #ifdef MIT_NEW_CRYPTO input.data = (char *) parms->wholeMsg; input.length = parms->wholeMsgLen; retcode = krb5_c_verify_checksum(kcontext, subkey, KSM_KEY_USAGE_CHECKSUM, &input, &checksum, &valid); #else /* MIT_NEW_CRYPTO */ retcode = krb5_verify_checksum(kcontext, cksumtype, &checksum, parms->wholeMsg, parms->wholeMsgLen, (krb5_pointer) subkey->contents, subkey->length); #endif /* MIT_NEW_CRYPTO */ if (retcode) { DEBUGMSGTL(("ksm", "KSM checksum verification failed: %s\n", error_message(retcode))); retval = SNMPERR_KRB5; snmp_set_detail(error_message(retcode)); goto error; } /* * Don't ask me why they didn't simply return an error, but we have * to check to see if "valid" is false. */ #ifdef MIT_NEW_CRYPTO if (!valid) { DEBUGMSGTL(("ksm", "Computed checksum did not match supplied " "checksum!\n")); retval = SNMPERR_KRB5; snmp_set_detail ("Computed checksum did not match supplied checksum"); goto error; } #endif /* MIT_NEW_CRYPTO */ /* * Handle an encrypted PDU. Note that it's an OCTET_STRING of the * output of whatever Kerberos cryptosystem you're using (defined by * the encryption type). Note that this is NOT the EncryptedData * sequence - it's what goes in the "cipher" field of EncryptedData. */ if (parms->secLevel == SNMP_SEC_LEVEL_AUTHPRIV) { if ((current = asn_parse_sequence(current, &length, &type, (ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR), "ksm pdu")) == NULL) { DEBUGMSGTL(("ksm", "KSM sPDU octet decoding failed\n")); retval = SNMPERR_ASN_PARSE_ERR; goto error; } /* * The PDU is now pointed at by "current", and the length is in * "length". */ DEBUGMSGTL(("ksm", "KSM starting sPDU decode\n")); /* * We need to set up a blank initialization vector for the decryption. * Use a block of all zero's (which is dependent on the block size * of the encryption method). */ #ifdef MIT_NEW_CRYPTO retcode = krb5_c_block_size(kcontext, subkey->enctype, &blocksize); if (retcode) { DEBUGMSGTL(("ksm", "Unable to determine crypto block size: %s\n", error_message(retcode))); snmp_set_detail(error_message(retcode)); retval = SNMPERR_KRB5; goto error; } #else /* MIT_NEW_CRYPTO */ blocksize = krb5_enctype_array[subkey->enctype]->system->block_length; #endif /* MIT_NEW_CRYPTO */ ivector.data = malloc(blocksize); if (!ivector.data) { DEBUGMSGTL(("ksm", "Unable to allocate %d bytes for ivector\n", blocksize)); retval = SNMPERR_MALLOC; goto error; } ivector.length = blocksize; memset(ivector.data, 0, blocksize); #ifndef MIT_NEW_CRYPTO krb5_use_enctype(kcontext, &eblock, subkey->enctype); retcode = krb5_process_key(kcontext, &eblock, subkey); if (retcode) { DEBUGMSGTL(("ksm", "KSM key post-processing failed: %s\n", error_message(retcode))); snmp_set_detail(error_message(retcode)); retval = SNMPERR_KRB5; goto error; } #endif /* !MIT_NEW_CRYPTO */ if (length > *parms->scopedPduLen) { DEBUGMSGTL(("ksm", "KSM not enough room - have %d bytes to " "decrypt but only %d bytes available\n", length, *parms->scopedPduLen)); retval = SNMPERR_TOO_LONG; #ifndef MIT_NEW_CRYPTO krb5_finish_key(kcontext, &eblock); #endif /* ! MIT_NEW_CRYPTO */ goto error; } #ifdef MIT_NEW_CRYPTO in_crypt.ciphertext.data = (char *) current; in_crypt.ciphertext.length = length; in_crypt.enctype = subkey->enctype; output.data = (char *) *parms->scopedPdu; output.length = *parms->scopedPduLen; retcode = krb5_c_decrypt(kcontext, subkey, KSM_KEY_USAGE_ENCRYPTION, &ivector, &in_crypt, &output); #else /* MIT_NEW_CRYPTO */ retcode = krb5_decrypt(kcontext, (krb5_pointer) current, *parms->scopedPdu, length, &eblock, ivector.data); krb5_finish_key(kcontext, &eblock); #endif /* MIT_NEW_CRYPTO */ if (retcode) { DEBUGMSGTL(("ksm", "Decryption failed: %s\n", error_message(retcode))); snmp_set_detail(error_message(retcode)); retval = SNMPERR_KRB5; goto error; } *parms->scopedPduLen = length; } else { /* * Clear PDU */ *parms->scopedPdu = current; *parms->scopedPduLen = parms->wholeMsgLen - (current - parms->wholeMsg); } /* * A HUGE GROSS HACK */ *parms->maxSizeResponse = parms->maxMsgSize - 200; DEBUGMSGTL(("ksm", "KSM processing complete\n")); /* * Set the secName to the right value (a hack for now). But that's * only used for when we're processing a request, not a response. */ if (!response) { retcode = krb5_unparse_name(kcontext, ticket->enc_part2->client, &cname); if (retcode) { DEBUGMSGTL(("ksm", "KSM krb5_unparse_name failed: %s\n", error_message(retcode))); snmp_set_detail(error_message(retcode)); retval = SNMPERR_KRB5; goto error; } if (strlen(cname) > *parms->secNameLen + 1) { DEBUGMSGTL(("ksm", "KSM: Principal length (%d) is too long (%d)\n", strlen(cname), parms->secNameLen)); retval = SNMPERR_TOO_LONG; free(cname); goto error; } strcpy(parms->secName, cname); *parms->secNameLen = strlen(cname); free(cname); /* * Also, if we're not a response, keep around our auth_context so we * can encode the reply message correctly */ ksm_state = SNMP_MALLOC_STRUCT(ksm_secStateRef); if (!ksm_state) { DEBUGMSGTL(("ksm", "KSM unable to malloc memory for " "ksm_secStateRef\n")); retval = SNMPERR_MALLOC; goto error; } ksm_state->auth_context = auth_context; auth_context = NULL; ksm_state->cksumtype = cksumtype; *parms->secStateRef = ksm_state; } else { /* * We _still_ have to set the secName in process_in_msg(). Do * that now with what we were passed in before (we cached it, * remember?) */ memcpy(parms->secName, entry->secName, entry->secNameLen); *parms->secNameLen = entry->secNameLen; } /* * Just in case */ parms->secEngineID = (u_char *) ""; *parms->secEngineIDLen = 0; auth_context = NULL; /* So we don't try to free it on success */ error: if (retval == SNMPERR_ASN_PARSE_ERR && snmp_increment_statistic(STAT_SNMPINASNPARSEERRS) == 0) DEBUGMSGTL(("ksm", "Failed to increment statistics.\n")); if (subkey) krb5_free_keyblock(kcontext, subkey); if (checksum.contents) free(checksum.contents); if (ivector.data) free(ivector.data); if (ticket) krb5_free_ticket(kcontext, ticket); if (!response && auth_context) krb5_auth_con_free(kcontext, auth_context); if (ap_req.data) free(ap_req.data); return retval; }