4 * This code implements the Kerberos Security Model (KSM) for SNMP.
6 * Security number - 2066432
9 #include <net-snmp/net-snmp-config.h>
11 #include <sys/types.h>
19 #if TIME_WITH_SYS_TIME
21 # include <sys/timeb.h>
23 # include <sys/time.h>
28 # include <sys/time.h>
38 #ifdef HAVE_NETINET_IN_H
39 #include <netinet/in.h>
51 #include <net-snmp/output_api.h>
52 #include <net-snmp/config_api.h>
53 #include <net-snmp/utilities.h>
55 #include <net-snmp/library/asn1.h>
56 #include <net-snmp/library/snmp_api.h>
57 #include <net-snmp/library/callback.h>
58 #include <net-snmp/library/keytools.h>
59 #include <net-snmp/library/snmpv3.h>
60 #include <net-snmp/library/lcd_time.h>
61 #include <net-snmp/library/scapi.h>
62 #include <net-snmp/library/callback.h>
63 #include <net-snmp/library/snmp_secmod.h>
64 #include <net-snmp/library/snmpksm.h>
66 static krb5_context kcontext = NULL;
67 static krb5_rcache rcache = NULL;
69 static int ksm_session_init(netsnmp_session *);
70 static void ksm_free_state_ref(void *);
71 static int ksm_free_pdu(netsnmp_pdu *);
72 static int ksm_clone_pdu(netsnmp_pdu *, netsnmp_pdu *);
74 static int ksm_insert_cache(long, krb5_auth_context, u_char *,
76 static void ksm_decrement_ref_count(long);
77 static void ksm_increment_ref_count(long);
78 static struct ksm_cache_entry *ksm_get_cache(long);
83 * Our information stored for the response PDU.
86 struct ksm_secStateRef {
87 krb5_auth_context auth_context;
88 krb5_cksumtype cksumtype;
92 * A KSM outgoing pdu cache entry
95 struct ksm_cache_entry {
98 krb5_auth_context auth_context;
101 struct ksm_cache_entry *next;
105 * Poor man's hash table
108 static struct ksm_cache_entry *ksm_hash_table[HASHSIZE];
111 * Initialize all of the state required for Kerberos (right now, just call
112 * krb5_init_context).
118 krb5_error_code retval;
119 struct snmp_secmod_def *def;
122 if (kcontext == NULL) {
123 retval = krb5_init_context(&kcontext);
126 DEBUGMSGTL(("ksm", "krb5_init_context failed (%s), not "
127 "registering KSM\n", error_message(retval)));
132 for (i = 0; i < HASHSIZE; i++)
133 ksm_hash_table[i] = NULL;
135 def = SNMP_MALLOC_STRUCT(snmp_secmod_def);
138 DEBUGMSGTL(("ksm", "Unable to malloc snmp_secmod struct, not "
139 "registering KSM\n"));
143 def->encode_reverse = ksm_rgenerate_out_msg;
144 def->decode = ksm_process_in_msg;
145 def->session_open = ksm_session_init;
146 def->pdu_free_state_ref = ksm_free_state_ref;
147 def->pdu_free = ksm_free_pdu;
148 def->pdu_clone = ksm_clone_pdu;
150 register_sec_mod(2066432, "ksm", def);
154 * These routines implement a simple cache for information we need to
155 * process responses. When we send out a request, it contains an AP_REQ;
156 * we get back an AP_REP, and we need the authorization context from the
157 * AP_REQ to decrypt the AP_REP. But because right now there's nothing
158 * that gets preserved across calls to rgenerate_out_msg to process_in_msg,
159 * we cache these internally based on the message ID (we also cache the
160 * passed-in security name, for reasons that are mostly stupid).
164 ksm_insert_cache(long msgid, krb5_auth_context auth_context,
165 u_char * secName, size_t secNameLen)
167 struct ksm_cache_entry *entry;
171 entry = SNMP_MALLOC_STRUCT(ksm_cache_entry);
174 return SNMPERR_MALLOC;
176 entry->msgid = msgid;
177 entry->auth_context = auth_context;
180 retval = memdup(&entry->secName, secName, secNameLen);
182 if (retval != SNMPERR_SUCCESS) {
187 entry->secNameLen = secNameLen;
189 bucket = msgid % HASHSIZE;
191 entry->next = ksm_hash_table[bucket];
192 ksm_hash_table[bucket] = entry;
194 return SNMPERR_SUCCESS;
197 static struct ksm_cache_entry *
198 ksm_get_cache(long msgid)
200 struct ksm_cache_entry *entry;
203 bucket = msgid % HASHSIZE;
205 for (entry = ksm_hash_table[bucket]; entry != NULL;
207 if (entry->msgid == msgid)
214 ksm_decrement_ref_count(long msgid)
216 struct ksm_cache_entry *entry, *entry1;
219 bucket = msgid % HASHSIZE;
221 if (ksm_hash_table[bucket] && ksm_hash_table[bucket]->msgid == msgid) {
222 entry = ksm_hash_table[bucket];
225 * If the reference count is zero, then free it
228 if (--entry->refcount <= 0) {
229 DEBUGMSGTL(("ksm", "Freeing entry for msgid %ld\n", msgid));
230 krb5_auth_con_free(kcontext, entry->auth_context);
231 free(entry->secName);
232 ksm_hash_table[bucket] = entry->next;
238 } else if (ksm_hash_table[bucket])
239 for (entry1 = ksm_hash_table[bucket], entry = entry1->next;
240 entry != NULL; entry1 = entry, entry = entry->next)
241 if (entry->msgid == msgid) {
243 if (--entry->refcount <= 0) {
244 DEBUGMSGTL(("ksm", "Freeing entry for msgid %ld\n",
246 krb5_auth_con_free(kcontext, entry->auth_context);
247 free(entry->secName);
248 entry1->next = entry->next;
256 "KSM: Unable to decrement cache entry for msgid %ld.\n",
261 ksm_increment_ref_count(long msgid)
263 struct ksm_cache_entry *entry = ksm_get_cache(msgid);
266 DEBUGMSGTL(("ksm", "Unable to find cache entry for msgid %ld "
267 "for increment\n", msgid));
275 * Initialize specific session information (right now, just set up things to
276 * not do an engineID probe)
280 ksm_session_init(netsnmp_session * sess)
283 "KSM: Reached our session initialization callback\n"));
285 sess->flags |= SNMP_FLAGS_DONT_PROBE;
287 return SNMPERR_SUCCESS;
291 * Free our state information (this is only done on the agent side)
295 ksm_free_state_ref(void *ptr)
297 struct ksm_secStateRef *ref = (struct ksm_secStateRef *) ptr;
299 DEBUGMSGTL(("ksm", "KSM: Freeing state reference\n"));
301 krb5_auth_con_free(kcontext, ref->auth_context);
307 * This is called when the PDU is freed; this will decrement reference counts
308 * for entries in our state cache.
312 ksm_free_pdu(netsnmp_pdu *pdu)
314 ksm_decrement_ref_count(pdu->msgid);
316 DEBUGMSGTL(("ksm", "Decrementing cache entry for PDU msgid %ld\n",
319 return SNMPERR_SUCCESS;
323 * This is called when a PDU is cloned (to increase reference counts)
327 ksm_clone_pdu(netsnmp_pdu *pdu, netsnmp_pdu *pdu2)
329 ksm_increment_ref_count(pdu->msgid);
331 DEBUGMSGTL(("ksm", "Incrementing cache entry for PDU msgid %ld\n",
334 return SNMPERR_SUCCESS;
337 /****************************************************************************
339 * ksm_generate_out_msg
342 * (See list below...)
345 * SNMPERR_GENERIC On success.
350 * Generate an outgoing message.
352 ****************************************************************************/
355 ksm_rgenerate_out_msg(struct snmp_secmod_outgoing_params *parms)
357 krb5_auth_context auth_context = NULL;
358 krb5_error_code retcode;
359 krb5_ccache cc = NULL;
360 int retval = SNMPERR_SUCCESS;
361 krb5_data outdata, ivector;
362 krb5_keyblock *subkey = NULL;
363 #ifdef MIT_NEW_CRYPTO
365 krb5_enc_data output;
366 #else /* MIT_NEW_CRYPTO */
367 krb5_encrypt_block eblock;
368 #endif /* MIT_NEW_CRYPTO */
369 size_t blocksize, encrypted_length;
370 unsigned char *encrypted_data = NULL;
372 u_char *cksum_pointer, *endp = *parms->wholeMsg;
373 krb5_cksumtype cksumtype = CKSUMTYPE_RSA_MD5_DES;
374 krb5_checksum pdu_checksum;
375 u_char **wholeMsg = parms->wholeMsg;
376 size_t *offset = parms->wholeMsgOffset, seq_offset;
377 struct ksm_secStateRef *ksm_state = (struct ksm_secStateRef *)
381 DEBUGMSGTL(("ksm", "Starting KSM processing\n"));
387 pdu_checksum.contents = NULL;
391 * If we don't have a ksm_state, then we're a request. Get a
392 * credential cache and build a ap_req.
394 retcode = krb5_cc_default(kcontext, &cc);
397 DEBUGMSGTL(("ksm", "KSM: krb5_cc_default failed: %s\n",
398 error_message(retcode)));
399 snmp_set_detail(error_message(retcode));
400 retval = SNMPERR_KRB5;
404 DEBUGMSGTL(("ksm", "KSM: Set credential cache successfully\n"));
407 * This seems odd, since we don't need this until later (or earlier,
408 * depending on how you look at it), but because the most likely
409 * errors are Kerberos at this point, I'll get this now to save
410 * time not encoding the rest of the packet.
412 * Also, we need the subkey to encrypt the PDU (if required).
416 krb5_mk_req(kcontext, &auth_context,
417 AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
418 (char *) "host", parms->session->peername, NULL,
422 DEBUGMSGTL(("ksm", "KSM: krb5_mk_req failed: %s\n",
423 error_message(retcode)));
424 snmp_set_detail(error_message(retcode));
425 retval = SNMPERR_KRB5;
429 DEBUGMSGTL(("ksm", "KSM: ticket retrieved successfully\n"));
434 * Grab the auth_context from our security state reference
437 auth_context = ksm_state->auth_context;
440 * Bundle up an AP_REP. Note that we do this only when we
441 * have a security state reference (which means we're in an agent
442 * and we're sending a response).
445 DEBUGMSGTL(("ksm", "KSM: Starting reply processing.\n"));
447 retcode = krb5_mk_rep(kcontext, auth_context, &outdata);
450 DEBUGMSGTL(("ksm", "KSM: krb5_mk_rep failed: %s\n",
451 error_message(retcode)));
452 snmp_set_detail(error_message(retcode));
453 retval = SNMPERR_KRB5;
457 DEBUGMSGTL(("ksm", "KSM: Finished with krb5_mk_rep()\n"));
461 * If we have to encrypt the PDU, do that now
464 if (parms->secLevel == SNMP_SEC_LEVEL_AUTHPRIV) {
466 DEBUGMSGTL(("ksm", "KSM: Starting PDU encryption.\n"));
471 * If we're on the manager, it's a local subkey (because that's in
474 * If we're on the agent, it's a remote subkey (because that comes
475 * FROM the received AP_REQ).
479 retcode = krb5_auth_con_getremotesubkey(kcontext, auth_context,
482 retcode = krb5_auth_con_getlocalsubkey(kcontext, auth_context,
487 "KSM: krb5_auth_con_getlocalsubkey failed: %s\n",
488 error_message(retcode)));
489 snmp_set_detail(error_message(retcode));
490 retval = SNMPERR_KRB5;
495 * Note that here we need to handle different things between the
496 * old and new crypto APIs. First, we need to get the final encrypted
500 #ifdef MIT_NEW_CRYPTO
501 retcode = krb5_c_encrypt_length(kcontext, subkey->enctype,
507 "Encryption length calculation failed: %s\n",
508 error_message(retcode)));
509 snmp_set_detail(error_message(retcode));
510 retval = SNMPERR_KRB5;
513 #else /* MIT_NEW_CRYPTO */
515 krb5_use_enctype(kcontext, &eblock, subkey->enctype);
516 retcode = krb5_process_key(kcontext, &eblock, subkey);
519 DEBUGMSGTL(("ksm", "krb5_process_key failed: %s\n",
520 error_message(retcode)));
521 snmp_set_detail(error_message(retcode));
522 retval = SNMPERR_KRB5;
526 encrypted_length = krb5_encrypt_size(parms->scopedPduLen,
527 eblock.crypto_entry);
528 #endif /* MIT_NEW_CRYPTO */
530 encrypted_data = malloc(encrypted_length);
532 if (!encrypted_data) {
534 "KSM: Unable to malloc %d bytes for encrypt "
535 "buffer: %s\n", parms->scopedPduLen,
537 retval = SNMPERR_MALLOC;
538 #ifndef MIT_NEW_CRYPTO
539 krb5_finish_key(kcontext, &eblock);
540 #endif /* ! MIT_NEW_CRYPTO */
546 * We need to set up a blank initialization vector for the encryption.
547 * Use a block of all zero's (which is dependent on the block size
548 * of the encryption method).
551 #ifdef MIT_NEW_CRYPTO
553 retcode = krb5_c_block_size(kcontext, subkey->enctype, &blocksize);
557 "Unable to determine crypto block size: %s\n",
558 error_message(retcode)));
559 snmp_set_detail(error_message(retcode));
560 retval = SNMPERR_KRB5;
563 #else /* MIT_NEW_CRYPTO */
566 krb5_enctype_array[subkey->enctype]->system->block_length;
568 #endif /* MIT_NEW_CRYPTO */
570 ivector.data = malloc(blocksize);
573 DEBUGMSGTL(("ksm", "Unable to allocate %d bytes for ivector\n",
575 retval = SNMPERR_MALLOC;
579 ivector.length = blocksize;
580 memset(ivector.data, 0, blocksize);
583 * Finally! Do the encryption!
586 #ifdef MIT_NEW_CRYPTO
588 input.data = (char *) parms->scopedPdu;
589 input.length = parms->scopedPduLen;
590 output.ciphertext.data = (char *) encrypted_data;
591 output.ciphertext.length = encrypted_length;
594 krb5_c_encrypt(kcontext, subkey, KSM_KEY_USAGE_ENCRYPTION,
595 &ivector, &input, &output);
597 #else /* MIT_NEW_CRYPTO */
599 retcode = krb5_encrypt(kcontext, (krb5_pointer) parms->scopedPdu,
600 (krb5_pointer) encrypted_data,
601 parms->scopedPduLen, &eblock, ivector.data);
603 krb5_finish_key(kcontext, &eblock);
605 #endif /* MIT_NEW_CRYPTO */
608 DEBUGMSGTL(("ksm", "KSM: krb5_encrypt failed: %s\n",
609 error_message(retcode)));
610 retval = SNMPERR_KRB5;
611 snmp_set_detail(error_message(retcode));
617 rc = asn_realloc_rbuild_string(wholeMsg, parms->wholeMsgLen,
619 (u_char) (ASN_UNIVERSAL |
626 DEBUGMSGTL(("ksm", "Building encrypted payload failed.\n"));
627 retval = SNMPERR_TOO_LONG;
631 DEBUGMSGTL(("ksm", "KSM: Encryption complete.\n"));
635 * Plaintext PDU (not encrypted)
638 if (*parms->wholeMsgLen < parms->scopedPduLen) {
639 DEBUGMSGTL(("ksm", "Not enough room for plaintext PDU.\n"));
640 retval = SNMPERR_TOO_LONG;
646 * Start encoding the msgSecurityParameters
648 * For now, use 0 for the response hint
651 DEBUGMSGTL(("ksm", "KSM: scopedPdu added to payload\n"));
653 seq_offset = *offset;
655 rc = asn_realloc_rbuild_int(wholeMsg, parms->wholeMsgLen,
657 (u_char) (ASN_UNIVERSAL |
660 (long *) &zero, sizeof(zero));
663 DEBUGMSGTL(("ksm", "Building ksm security parameters failed.\n"));
664 retval = SNMPERR_TOO_LONG;
668 rc = asn_realloc_rbuild_string(wholeMsg, parms->wholeMsgLen,
670 (u_char) (ASN_UNIVERSAL |
673 (u_char *) outdata.data,
677 DEBUGMSGTL(("ksm", "Building ksm AP_REQ failed.\n"));
678 retval = SNMPERR_TOO_LONG;
683 * Hardcode checksum type FOR NOW! XXX (but make sure the response
684 * checksum type is the same as the request).
686 * Not sure what we're supposed to do about checksum negotiation.
690 cksumtype = ksm_state->cksumtype;
692 if (!is_keyed_cksum(cksumtype)) {
693 DEBUGMSGTL(("ksm", "Checksum type %d is not a keyed checksum\n",
695 snmp_set_detail("Checksum is not a keyed checksum");
696 retval = SNMPERR_KRB5;
700 if (!is_coll_proof_cksum(cksumtype)) {
701 DEBUGMSGTL(("ksm", "Checksum type %d is not a collision-proof "
702 "checksum\n", cksumtype));
703 snmp_set_detail("Checksum is not a collision-proof checksum");
704 retval = SNMPERR_KRB5;
707 #ifdef MIT_NEW_CRYPTO
708 retcode = krb5_c_checksum_length(kcontext, cksumtype, &blocksize);
711 DEBUGMSGTL(("ksm", "Unable to determine checksum length: %s\n",
712 error_message(retcode)));
713 snmp_set_detail(error_message(retcode));
714 retval = SNMPERR_KRB5;
718 pdu_checksum.length = blocksize;
720 #else /* MIT_NEW_CRYPTO */
722 pdu_checksum.length = krb5_checksum_size(kcontext, cksumtype);
723 pdu_checksum.checksum_type = cksumtype;
725 #endif /* MIT_NEW_CRYPTO */
728 * Note that here, we're just leaving blank space for the checksum;
729 * we remember where that is, and we'll fill it in later.
732 *offset += pdu_checksum.length;
733 memset(*wholeMsg + *parms->wholeMsgLen - *offset, 0, pdu_checksum.length);
735 cksum_pointer = *wholeMsg + *parms->wholeMsgLen - *offset;
737 rc = asn_realloc_rbuild_header(wholeMsg, parms->wholeMsgLen,
738 parms->wholeMsgOffset, 1,
739 (u_char) (ASN_UNIVERSAL |
742 pdu_checksum.length);
745 DEBUGMSGTL(("ksm", "Building ksm security parameters failed.\n"));
746 retval = SNMPERR_TOO_LONG;
750 rc = asn_realloc_rbuild_int(wholeMsg, parms->wholeMsgLen,
751 parms->wholeMsgOffset, 1,
752 (u_char) (ASN_UNIVERSAL |
759 DEBUGMSGTL(("ksm", "Building ksm security parameters failed.\n"));
760 retval = SNMPERR_TOO_LONG;
764 rc = asn_realloc_rbuild_sequence(wholeMsg, parms->wholeMsgLen,
765 parms->wholeMsgOffset, 1,
766 (u_char) (ASN_SEQUENCE |
768 *offset - seq_offset);
771 DEBUGMSGTL(("ksm", "Building ksm security parameters failed.\n"));
772 retval = SNMPERR_TOO_LONG;
776 rc = asn_realloc_rbuild_header(wholeMsg, parms->wholeMsgLen,
777 parms->wholeMsgOffset, 1,
778 (u_char) (ASN_UNIVERSAL |
781 *offset - seq_offset);
784 DEBUGMSGTL(("ksm", "Building ksm security parameters failed.\n"));
785 retval = SNMPERR_TOO_LONG;
789 DEBUGMSGTL(("ksm", "KSM: Security parameter encoding completed\n"));
792 * We're done with the KSM security parameters - now we do the global
793 * header and wrap up the whole PDU.
796 if (*parms->wholeMsgLen < parms->globalDataLen) {
797 DEBUGMSGTL(("ksm", "Building global data failed.\n"));
798 retval = SNMPERR_TOO_LONG;
802 *offset += parms->globalDataLen;
803 memcpy(*wholeMsg + *parms->wholeMsgLen - *offset,
804 parms->globalData, parms->globalDataLen);
806 rc = asn_realloc_rbuild_sequence(wholeMsg, parms->wholeMsgLen,
808 (u_char) (ASN_SEQUENCE |
813 DEBUGMSGTL(("ksm", "Building master packet sequence.\n"));
814 retval = SNMPERR_TOO_LONG;
818 DEBUGMSGTL(("ksm", "KSM: PDU master packet encoding complete.\n"));
821 * Now we need to checksum the entire PDU (since it's built).
824 pdu_checksum.contents = malloc(pdu_checksum.length);
826 if (!pdu_checksum.contents) {
827 DEBUGMSGTL(("ksm", "Unable to malloc %d bytes for checksum\n",
828 pdu_checksum.length));
829 retval = SNMPERR_MALLOC;
834 * If we didn't encrypt the packet, we haven't yet got the subkey.
840 retcode = krb5_auth_con_getremotesubkey(kcontext, auth_context,
843 retcode = krb5_auth_con_getlocalsubkey(kcontext, auth_context,
846 DEBUGMSGTL(("ksm", "krb5_auth_con_getlocalsubkey failed: %s\n",
847 error_message(retcode)));
848 snmp_set_detail(error_message(retcode));
849 retval = SNMPERR_KRB5;
853 #ifdef MIT_NEW_CRYPTO
855 input.data = (char *) (*wholeMsg + *parms->wholeMsgLen - *offset);
856 input.length = *offset;
857 retcode = krb5_c_make_checksum(kcontext, cksumtype, subkey,
858 KSM_KEY_USAGE_CHECKSUM, &input,
861 #else /* MIT_NEW_CRYPTO */
863 retcode = krb5_calculate_checksum(kcontext, cksumtype, *wholeMsg +
864 *parms->wholeMsgLen - *offset,
866 (krb5_pointer) subkey->contents,
867 subkey->length, &pdu_checksum);
869 #endif /* MIT_NEW_CRYPTO */
872 DEBUGMSGTL(("ksm", "Calculate checksum failed: %s\n",
873 error_message(retcode)));
874 retval = SNMPERR_KRB5;
875 snmp_set_detail(error_message(retcode));
879 DEBUGMSGTL(("ksm", "KSM: Checksum calculation complete.\n"));
881 memcpy(cksum_pointer, pdu_checksum.contents, pdu_checksum.length);
883 DEBUGMSGTL(("ksm", "KSM: Writing checksum of %d bytes at offset %d\n",
884 pdu_checksum.length, cksum_pointer - (*wholeMsg + 1)));
886 DEBUGMSGTL(("ksm", "KSM: Checksum:"));
888 for (i = 0; i < pdu_checksum.length; i++)
889 DEBUGMSG(("ksm", " %02x",
890 (unsigned int) pdu_checksum.contents[i]));
892 DEBUGMSG(("ksm", "\n"));
895 * If we're _not_ called as part of a response (null ksm_state),
896 * then save the auth_context for later using our cache routines.
900 if ((retval = ksm_insert_cache(parms->pdu->msgid, auth_context,
901 (u_char *) parms->secName,
902 parms->secNameLen)) !=
908 DEBUGMSGTL(("ksm", "KSM processing complete!\n"));
912 if (pdu_checksum.contents)
913 #ifdef MIT_NEW_CRYPTO
914 krb5_free_checksum_contents(kcontext, &pdu_checksum);
915 #else /* MIT_NEW_CRYPTO */
916 free(pdu_checksum.contents);
917 #endif /* MIT_NEW_CRYPTO */
923 krb5_free_keyblock(kcontext, subkey);
926 free(encrypted_data);
929 krb5_cc_close(kcontext, cc);
931 if (auth_context && !ksm_state)
932 krb5_auth_con_free(kcontext, auth_context);
937 /****************************************************************************
942 * (See list below...)
945 * KSM_ERR_NO_ERROR On success.
947 * KSM_ERR_GENERIC_ERROR
948 * KSM_ERR_UNSUPPORTED_SECURITY_LEVEL
951 * Processes an incoming message.
953 ****************************************************************************/
956 ksm_process_in_msg(struct snmp_secmod_incoming_params *parms)
959 krb5_cksumtype cksumtype;
960 krb5_auth_context auth_context = NULL;
961 krb5_error_code retcode;
962 krb5_checksum checksum;
963 krb5_data ap_req, ivector;
965 krb5_keyblock *subkey = NULL;
966 #ifdef MIT_NEW_CRYPTO
967 krb5_data input, output;
969 krb5_enc_data in_crypt;
970 #else /* MIT_NEW_CRYPTO */
971 krb5_encrypt_block eblock;
972 #endif /* MIT_NEW_CRYPTO */
973 krb5_ticket *ticket = NULL;
974 int retval = SNMPERR_SUCCESS, response = 0;
976 parms->wholeMsgLen - (u_int) (parms->secParams - parms->wholeMsg);
977 u_char *current = parms->secParams, type;
978 size_t cksumlength, blocksize;
981 struct ksm_secStateRef *ksm_state;
982 struct ksm_cache_entry *entry;
984 DEBUGMSGTL(("ksm", "Processing has begun\n"));
986 checksum.contents = NULL;
992 * First, parse the security parameters (because we need the subkey inside
993 * of the ticket to do anything
996 if ((current = asn_parse_sequence(current, &length, &type,
997 (ASN_UNIVERSAL | ASN_PRIMITIVE |
999 "ksm first octet")) == NULL) {
1000 DEBUGMSGTL(("ksm", "Initial security paramter parsing failed\n"));
1002 retval = SNMPERR_ASN_PARSE_ERR;
1006 if ((current = asn_parse_sequence(current, &length, &type,
1007 (ASN_SEQUENCE | ASN_CONSTRUCTOR),
1008 "ksm sequence")) == NULL) {
1010 "Security parameter sequence parsing failed\n"));
1012 retval = SNMPERR_ASN_PARSE_ERR;
1016 if ((current = asn_parse_int(current, &length, &type, &temp,
1017 sizeof(temp))) == NULL) {
1018 DEBUGMSGTL(("ksm", "Security parameter checksum type parsing"
1021 retval = SNMPERR_ASN_PARSE_ERR;
1027 if (!valid_cksumtype(cksumtype)) {
1028 DEBUGMSGTL(("ksm", "Invalid checksum type (%d)\n", cksumtype));
1030 retval = SNMPERR_KRB5;
1031 snmp_set_detail("Invalid checksum type");
1035 if (!is_keyed_cksum(cksumtype)) {
1036 DEBUGMSGTL(("ksm", "Checksum type %d is not a keyed checksum\n",
1038 snmp_set_detail("Checksum is not a keyed checksum");
1039 retval = SNMPERR_KRB5;
1043 if (!is_coll_proof_cksum(cksumtype)) {
1044 DEBUGMSGTL(("ksm", "Checksum type %d is not a collision-proof "
1045 "checksum\n", cksumtype));
1046 snmp_set_detail("Checksum is not a collision-proof checksum");
1047 retval = SNMPERR_KRB5;
1051 checksum.checksum_type = cksumtype;
1053 cksumlength = length;
1055 if ((current = asn_parse_sequence(current, &cksumlength, &type,
1056 (ASN_UNIVERSAL | ASN_PRIMITIVE |
1057 ASN_OCTET_STR), "ksm checksum")) ==
1060 "Security parameter checksum parsing failed\n"));
1062 retval = SNMPERR_ASN_PARSE_ERR;
1066 checksum.contents = malloc(cksumlength);
1067 if (!checksum.contents) {
1068 DEBUGMSGTL(("ksm", "Unable to malloc %d bytes for checksum.\n",
1070 retval = SNMPERR_MALLOC;
1074 memcpy(checksum.contents, current, cksumlength);
1076 checksum.length = cksumlength;
1077 checksum.checksum_type = cksumtype;
1080 * Zero out the checksum so the validation works correctly
1083 memset(current, 0, cksumlength);
1085 current += cksumlength;
1086 length = parms->wholeMsgLen - (u_int) (current - parms->wholeMsg);
1088 if ((current = asn_parse_sequence(current, &length, &type,
1089 (ASN_UNIVERSAL | ASN_PRIMITIVE |
1090 ASN_OCTET_STR), "ksm ap_req")) ==
1092 DEBUGMSGTL(("ksm", "KSM security parameter AP_REQ/REP parsing "
1095 retval = SNMPERR_ASN_PARSE_ERR;
1099 ap_req.length = length;
1100 ap_req.data = malloc(length);
1103 "KSM unable to malloc %d bytes for AP_REQ/REP.\n",
1105 retval = SNMPERR_MALLOC;
1109 memcpy(ap_req.data, current, length);
1112 length = parms->wholeMsgLen - (u_int) (current - parms->wholeMsg);
1114 if ((current = asn_parse_int(current, &length, &type, &hint,
1115 sizeof(hint))) == NULL) {
1117 "KSM security parameter hint parsing failed\n"));
1119 retval = SNMPERR_ASN_PARSE_ERR;
1124 * Okay! We've got it all! Now try decoding the damn ticket.
1126 * But of course there's a WRINKLE! We need to figure out if we're
1127 * processing a AP_REQ or an AP_REP. How do we do that? We're going
1128 * to cheat, and look at the first couple of bytes (which is what
1129 * the Kerberos library routines do anyway).
1131 * If there are ever new Kerberos message formats, we'll need to fix
1134 * If it's a _response_, then we need to get the auth_context
1139 && (ap_req.data[0] == 0x6e || ap_req.data[0] == 0x4e)) {
1142 * We need to initalize the authorization context, and set the
1143 * replay cache in it (and initialize the replay cache if we
1147 retcode = krb5_auth_con_init(kcontext, &auth_context);
1150 DEBUGMSGTL(("ksm", "krb5_auth_con_init failed: %s\n",
1151 error_message(retcode)));
1152 retval = SNMPERR_KRB5;
1153 snmp_set_detail(error_message(retcode));
1159 server.data = "host";
1160 server.length = strlen(server.data);
1162 retcode = krb5_get_server_rcache(kcontext, &server, &rcache);
1165 DEBUGMSGTL(("ksm", "krb5_get_server_rcache failed: %s\n",
1166 error_message(retcode)));
1167 retval = SNMPERR_KRB5;
1168 snmp_set_detail(error_message(retcode));
1173 retcode = krb5_auth_con_setrcache(kcontext, auth_context, rcache);
1176 DEBUGMSGTL(("ksm", "krb5_auth_con_setrcache failed: %s\n",
1177 error_message(retcode)));
1178 retval = SNMPERR_KRB5;
1179 snmp_set_detail(error_message(retcode));
1183 retcode = krb5_rd_req(kcontext, &auth_context, &ap_req, NULL,
1184 NULL, &flags, &ticket);
1186 krb5_auth_con_setrcache(kcontext, auth_context, NULL);
1189 DEBUGMSGTL(("ksm", "krb5_rd_req() failed: %s\n",
1190 error_message(retcode)));
1191 retval = SNMPERR_KRB5;
1192 snmp_set_detail(error_message(retcode));
1197 krb5_unparse_name(kcontext, ticket->enc_part2->client, &cname);
1200 DEBUGMSGTL(("ksm", "KSM authenticated principal name: %s\n",
1206 * Check to make sure AP_OPTS_MUTUAL_REQUIRED was set
1209 if (!(flags & AP_OPTS_MUTUAL_REQUIRED)) {
1211 "KSM MUTUAL_REQUIRED not set in request!\n"));
1212 retval = SNMPERR_KRB5;
1213 snmp_set_detail("MUTUAL_REQUIRED not set in message");
1218 krb5_auth_con_getremotesubkey(kcontext, auth_context, &subkey);
1221 DEBUGMSGTL(("ksm", "KSM remote subkey retrieval failed: %s\n",
1222 error_message(retcode)));
1223 retval = SNMPERR_KRB5;
1224 snmp_set_detail(error_message(retcode));
1228 } else if (ap_req.length && (ap_req.data[0] == 0x6f ||
1229 ap_req.data[0] == 0x4f)) {
1231 * Looks like a response; let's see if we've got that auth_context
1235 krb5_ap_rep_enc_part *repl = NULL;
1239 entry = ksm_get_cache(parms->pdu->msgid);
1243 "KSM: Unable to find auth_context for PDU with "
1244 "message ID of %ld\n", parms->pdu->msgid));
1245 retval = SNMPERR_KRB5;
1249 auth_context = entry->auth_context;
1252 * In that case, let's call the rd_rep function
1255 retcode = krb5_rd_rep(kcontext, auth_context, &ap_req, &repl);
1258 krb5_free_ap_rep_enc_part(kcontext, repl);
1261 DEBUGMSGTL(("ksm", "KSM: krb5_rd_rep() failed: %s\n",
1262 error_message(retcode)));
1263 retval = SNMPERR_KRB5;
1267 DEBUGMSGTL(("ksm", "KSM: krb5_rd_rep() decoded successfully.\n"));
1270 krb5_auth_con_getlocalsubkey(kcontext, auth_context, &subkey);
1273 DEBUGMSGTL(("ksm", "Unable to retrieve local subkey: %s\n",
1274 error_message(retcode)));
1275 retval = SNMPERR_KRB5;
1276 snmp_set_detail("Unable to retrieve local subkey");
1281 DEBUGMSGTL(("ksm", "Unknown Kerberos message type (%02x)\n",
1283 retval = SNMPERR_KRB5;
1284 snmp_set_detail("Unknown Kerberos message type");
1288 #ifdef MIT_NEW_CRYPTO
1289 input.data = (char *) parms->wholeMsg;
1290 input.length = parms->wholeMsgLen;
1293 krb5_c_verify_checksum(kcontext, subkey, KSM_KEY_USAGE_CHECKSUM,
1294 &input, &checksum, &valid);
1295 #else /* MIT_NEW_CRYPTO */
1296 retcode = krb5_verify_checksum(kcontext, cksumtype, &checksum,
1297 parms->wholeMsg, parms->wholeMsgLen,
1298 (krb5_pointer) subkey->contents,
1300 #endif /* MIT_NEW_CRYPTO */
1303 DEBUGMSGTL(("ksm", "KSM checksum verification failed: %s\n",
1304 error_message(retcode)));
1305 retval = SNMPERR_KRB5;
1306 snmp_set_detail(error_message(retcode));
1311 * Don't ask me why they didn't simply return an error, but we have
1312 * to check to see if "valid" is false.
1315 #ifdef MIT_NEW_CRYPTO
1317 DEBUGMSGTL(("ksm", "Computed checksum did not match supplied "
1319 retval = SNMPERR_KRB5;
1321 ("Computed checksum did not match supplied checksum");
1324 #endif /* MIT_NEW_CRYPTO */
1327 * Handle an encrypted PDU. Note that it's an OCTET_STRING of the
1328 * output of whatever Kerberos cryptosystem you're using (defined by
1329 * the encryption type). Note that this is NOT the EncryptedData
1330 * sequence - it's what goes in the "cipher" field of EncryptedData.
1333 if (parms->secLevel == SNMP_SEC_LEVEL_AUTHPRIV) {
1335 if ((current = asn_parse_sequence(current, &length, &type,
1336 (ASN_UNIVERSAL | ASN_PRIMITIVE |
1337 ASN_OCTET_STR), "ksm pdu")) ==
1339 DEBUGMSGTL(("ksm", "KSM sPDU octet decoding failed\n"));
1340 retval = SNMPERR_ASN_PARSE_ERR;
1345 * The PDU is now pointed at by "current", and the length is in
1349 DEBUGMSGTL(("ksm", "KSM starting sPDU decode\n"));
1352 * We need to set up a blank initialization vector for the decryption.
1353 * Use a block of all zero's (which is dependent on the block size
1354 * of the encryption method).
1357 #ifdef MIT_NEW_CRYPTO
1359 retcode = krb5_c_block_size(kcontext, subkey->enctype, &blocksize);
1363 "Unable to determine crypto block size: %s\n",
1364 error_message(retcode)));
1365 snmp_set_detail(error_message(retcode));
1366 retval = SNMPERR_KRB5;
1369 #else /* MIT_NEW_CRYPTO */
1372 krb5_enctype_array[subkey->enctype]->system->block_length;
1374 #endif /* MIT_NEW_CRYPTO */
1376 ivector.data = malloc(blocksize);
1378 if (!ivector.data) {
1379 DEBUGMSGTL(("ksm", "Unable to allocate %d bytes for ivector\n",
1381 retval = SNMPERR_MALLOC;
1385 ivector.length = blocksize;
1386 memset(ivector.data, 0, blocksize);
1388 #ifndef MIT_NEW_CRYPTO
1390 krb5_use_enctype(kcontext, &eblock, subkey->enctype);
1392 retcode = krb5_process_key(kcontext, &eblock, subkey);
1395 DEBUGMSGTL(("ksm", "KSM key post-processing failed: %s\n",
1396 error_message(retcode)));
1397 snmp_set_detail(error_message(retcode));
1398 retval = SNMPERR_KRB5;
1401 #endif /* !MIT_NEW_CRYPTO */
1403 if (length > *parms->scopedPduLen) {
1404 DEBUGMSGTL(("ksm", "KSM not enough room - have %d bytes to "
1405 "decrypt but only %d bytes available\n", length,
1406 *parms->scopedPduLen));
1407 retval = SNMPERR_TOO_LONG;
1408 #ifndef MIT_NEW_CRYPTO
1409 krb5_finish_key(kcontext, &eblock);
1410 #endif /* ! MIT_NEW_CRYPTO */
1413 #ifdef MIT_NEW_CRYPTO
1414 in_crypt.ciphertext.data = (char *) current;
1415 in_crypt.ciphertext.length = length;
1416 in_crypt.enctype = subkey->enctype;
1417 output.data = (char *) *parms->scopedPdu;
1418 output.length = *parms->scopedPduLen;
1421 krb5_c_decrypt(kcontext, subkey, KSM_KEY_USAGE_ENCRYPTION,
1422 &ivector, &in_crypt, &output);
1423 #else /* MIT_NEW_CRYPTO */
1425 retcode = krb5_decrypt(kcontext, (krb5_pointer) current,
1426 *parms->scopedPdu, length, &eblock,
1429 krb5_finish_key(kcontext, &eblock);
1431 #endif /* MIT_NEW_CRYPTO */
1434 DEBUGMSGTL(("ksm", "Decryption failed: %s\n",
1435 error_message(retcode)));
1436 snmp_set_detail(error_message(retcode));
1437 retval = SNMPERR_KRB5;
1441 *parms->scopedPduLen = length;
1448 *parms->scopedPdu = current;
1449 *parms->scopedPduLen =
1450 parms->wholeMsgLen - (current - parms->wholeMsg);
1457 *parms->maxSizeResponse = parms->maxMsgSize - 200;
1459 DEBUGMSGTL(("ksm", "KSM processing complete\n"));
1462 * Set the secName to the right value (a hack for now). But that's
1463 * only used for when we're processing a request, not a response.
1468 retcode = krb5_unparse_name(kcontext, ticket->enc_part2->client,
1472 DEBUGMSGTL(("ksm", "KSM krb5_unparse_name failed: %s\n",
1473 error_message(retcode)));
1474 snmp_set_detail(error_message(retcode));
1475 retval = SNMPERR_KRB5;
1479 if (strlen(cname) > *parms->secNameLen + 1) {
1481 "KSM: Principal length (%d) is too long (%d)\n",
1482 strlen(cname), parms->secNameLen));
1483 retval = SNMPERR_TOO_LONG;
1488 strcpy(parms->secName, cname);
1489 *parms->secNameLen = strlen(cname);
1494 * Also, if we're not a response, keep around our auth_context so we
1495 * can encode the reply message correctly
1498 ksm_state = SNMP_MALLOC_STRUCT(ksm_secStateRef);
1501 DEBUGMSGTL(("ksm", "KSM unable to malloc memory for "
1502 "ksm_secStateRef\n"));
1503 retval = SNMPERR_MALLOC;
1507 ksm_state->auth_context = auth_context;
1508 auth_context = NULL;
1509 ksm_state->cksumtype = cksumtype;
1511 *parms->secStateRef = ksm_state;
1515 * We _still_ have to set the secName in process_in_msg(). Do
1516 * that now with what we were passed in before (we cached it,
1520 memcpy(parms->secName, entry->secName, entry->secNameLen);
1521 *parms->secNameLen = entry->secNameLen;
1528 parms->secEngineID = (u_char *) "";
1529 *parms->secEngineIDLen = 0;
1531 auth_context = NULL; /* So we don't try to free it on success */
1534 if (retval == SNMPERR_ASN_PARSE_ERR &&
1535 snmp_increment_statistic(STAT_SNMPINASNPARSEERRS) == 0)
1536 DEBUGMSGTL(("ksm", "Failed to increment statistics.\n"));
1539 krb5_free_keyblock(kcontext, subkey);
1541 if (checksum.contents)
1542 free(checksum.contents);
1548 krb5_free_ticket(kcontext, ticket);
1550 if (!response && auth_context)
1551 krb5_auth_con_free(kcontext, auth_context);