diff --git a/server/incoming_spa.c b/server/incoming_spa.c index 024efe33..2c33192a 100644 --- a/server/incoming_spa.c +++ b/server/incoming_spa.c @@ -110,6 +110,64 @@ preprocess_spa_data(fko_srv_options_t *opts, const char *src_ip) return(FKO_SUCCESS); } +/* For replay attack detection +*/ +static int +get_raw_digest(char **digest, char *pkt_data) +{ + fko_ctx_t ctx = NULL; + char *tmp_digest = NULL; + int res = FKO_SUCCESS; + + /* initialize an FKO context with no decryption key just so + * we can get the outer message digest + */ + res = fko_new_with_data(&ctx, (char *)pkt_data, NULL); + if(res != FKO_SUCCESS) + { + log_msg(LOG_WARNING, "Error initializing FKO context from SPA data: %s", + fko_errstr(res)); + fko_destroy(ctx); + return(SPA_MSG_FKO_CTX_ERROR); + } + + res = fko_set_raw_spa_digest_type(ctx, FKO_DEFAULT_DIGEST); + if(res != FKO_SUCCESS) + { + log_msg(LOG_WARNING, "Error setting digest type for SPA data: %s", + fko_errstr(res)); + fko_destroy(ctx); + return(SPA_MSG_DIGEST_ERROR); + } + + res = fko_set_raw_spa_digest(ctx); + if(res != FKO_SUCCESS) + { + log_msg(LOG_WARNING, "Error setting digest for SPA data: %s", + fko_errstr(res)); + fko_destroy(ctx); + return(SPA_MSG_DIGEST_ERROR); + } + + res = fko_get_raw_spa_digest(ctx, &tmp_digest); + if(res != FKO_SUCCESS) + { + log_msg(LOG_WARNING, "Error getting digest from SPA data: %s", + fko_errstr(res)); + fko_destroy(ctx); + return(SPA_MSG_DIGEST_ERROR); + } + + *digest = strdup(tmp_digest); + + if (digest == NULL) + return SPA_MSG_ERROR; + + fko_destroy(ctx); + return res; +} + + /* Popluate a spa_data struct from an initialized (and populated) FKO context. */ static int @@ -178,9 +236,10 @@ incoming_spa(fko_srv_options_t *opts) */ fko_ctx_t ctx = NULL; - char *spa_ip_demark, *gpg_id; + char *spa_ip_demark, *gpg_id, *raw_digest = NULL; time_t now_ts; - int res, status, ts_diff, enc_type, found_acc_sip=0, stanza_num=0; + int res, status, ts_diff, enc_type, stanza_num=0; + int added_replay_digest = 0; spa_pkt_info_t *spa_pkt = &(opts->spa_pkt); @@ -213,7 +272,17 @@ incoming_spa(fko_srv_options_t *opts) if(strncasecmp(opts->config[CONF_ENABLE_DIGEST_PERSISTENCE], "Y", 1) == 0) /* Check for a replay attack */ - if (is_replay(opts, spa_pkt->packet_data) != SPA_MSG_SUCCESS) + res = get_raw_digest(&raw_digest, (char *)spa_pkt->packet_data); + if(res != FKO_SUCCESS) + { + if (raw_digest != NULL) + free(raw_digest); + return; + } + if (raw_digest == NULL) + return; + + if (is_replay(opts, raw_digest) != SPA_MSG_SUCCESS) return; } else @@ -240,8 +309,6 @@ incoming_spa(fko_srv_options_t *opts) continue; } - found_acc_sip = 1; - log_msg(LOG_INFO, "(stanza #%d) SPA Packet from IP: %s received with access source match", stanza_num, spadat.pkt_source_ip); @@ -366,7 +433,7 @@ incoming_spa(fko_srv_options_t *opts) continue; } - /* Do we have a valid FKO context? + /* Do we have a valid FKO context? Did the SPA decrypt properly? */ if(res != FKO_SUCCESS) { @@ -383,6 +450,23 @@ incoming_spa(fko_srv_options_t *opts) continue; } + /* Add this SPA packet into the replay detection cache + */ + if (! added_replay_digest) + { + res = add_replay(opts, raw_digest); + if (res != SPA_MSG_SUCCESS) + { + log_msg(LOG_WARNING, "(stanza #%d) Could not add digest to replay cache", + stanza_num); + if(ctx != NULL) + fko_destroy(ctx); + acc = acc->next; + continue; + } + added_replay_digest = 1; + } + /* At this point, we assume the SPA data is valid. Now we need to see * if it meets our access criteria. */ @@ -656,12 +740,8 @@ incoming_spa(fko_srv_options_t *opts) break; } - if(! found_acc_sip) - { - log_msg(LOG_WARNING, - "No access data found for source IP: %s", spadat.pkt_source_ip - ); - } + if (raw_digest != NULL) + free(raw_digest); return; } diff --git a/server/replay_cache.c b/server/replay_cache.c index fdc99e53..2b164e94 100644 --- a/server/replay_cache.c +++ b/server/replay_cache.c @@ -77,61 +77,6 @@ #define DATE_LEN 18 #define MAX_DIGEST_SIZE 64 -static int -get_raw_digest(char **digest, char *pkt_data) -{ - fko_ctx_t ctx = NULL; - char *tmp_digest = NULL; - int res = FKO_SUCCESS; - - /* initialize an FKO context with no decryption key just so - * we can get the outer message digest - */ - res = fko_new_with_data(&ctx, (char *)pkt_data, NULL); - if(res != FKO_SUCCESS) - { - log_msg(LOG_WARNING, "Error initializing FKO context from SPA data: %s", - fko_errstr(res)); - fko_destroy(ctx); - return(SPA_MSG_FKO_CTX_ERROR); - } - - res = fko_set_raw_spa_digest_type(ctx, FKO_DEFAULT_DIGEST); - if(res != FKO_SUCCESS) - { - log_msg(LOG_WARNING, "Error setting digest type for SPA data: %s", - fko_errstr(res)); - fko_destroy(ctx); - return(SPA_MSG_DIGEST_ERROR); - } - - res = fko_set_raw_spa_digest(ctx); - if(res != FKO_SUCCESS) - { - log_msg(LOG_WARNING, "Error setting digest for SPA data: %s", - fko_errstr(res)); - fko_destroy(ctx); - return(SPA_MSG_DIGEST_ERROR); - } - - res = fko_get_raw_spa_digest(ctx, &tmp_digest); - if(res != FKO_SUCCESS) - { - log_msg(LOG_WARNING, "Error getting digest from SPA data: %s", - fko_errstr(res)); - fko_destroy(ctx); - return(SPA_MSG_DIGEST_ERROR); - } - - *digest = strdup(tmp_digest); - - if (digest == NULL) - return SPA_MSG_ERROR; - - fko_destroy(ctx); - return res; -} - /* Rotate the digest file by simply renaming it. */ static void @@ -475,47 +420,45 @@ replay_db_cache_init(fko_srv_options_t *opts) #endif /* USE_FILE_CACHE */ /* Take an fko context, pull the digest and use it as the key to check the - * replay db (digest cache). Returns 1 if there was a match (a replay), - * 0 for no match, and -1 on error. + * replay db (digest cache). */ int -is_replay(fko_srv_options_t *opts, unsigned char *pkt_data) +is_replay(fko_srv_options_t *opts, char *digest) { #ifdef NO_DIGEST_CACHE return(-1); #else #if USE_FILE_CACHE - return is_replay_file_cache(opts, pkt_data); + return is_replay_file_cache(opts, digest); #else - return is_replay_dbm_cache(opts, pkt_data); + return is_replay_dbm_cache(opts, digest); +#endif +#endif /* NO_DIGEST_CACHE */ +} + +int +add_replay(fko_srv_options_t *opts, char *digest) +{ +#ifdef NO_DIGEST_CACHE + return(-1); +#else + +#if USE_FILE_CACHE + return add_replay_file_cache(opts, digest); +#else + return add_replay_dbm_cache(opts, digest); #endif #endif /* NO_DIGEST_CACHE */ } #if USE_FILE_CACHE int -is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data) +is_replay_file_cache(fko_srv_options_t *opts, char *digest) { - char *digest = NULL; - char src_ip[INET_ADDRSTRLEN+1] = {0}; - char dst_ip[INET_ADDRSTRLEN+1] = {0}; - int res = 0, digest_len = 0; - FILE *digest_file_ptr = NULL; + int digest_len = 0; - struct digest_cache_list *digest_list_ptr = NULL, *digest_elm = NULL; - - res = get_raw_digest(&digest, (char *)pkt_data); - - if(res != FKO_SUCCESS) - { - if (digest != NULL) - free(digest); - return res; - } - - if (digest == NULL) - return SPA_MSG_ERROR; + struct digest_cache_list *digest_list_ptr = NULL; digest_len = strlen(digest); @@ -529,21 +472,29 @@ is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data) replay_warning(opts, &(digest_list_ptr->cache_info)); - free(digest); return(SPA_MSG_REPLAY); } } + return(SPA_MSG_SUCCESS); +} + +int +add_replay_file_cache(fko_srv_options_t *opts, char *digest) +{ + FILE *digest_file_ptr = NULL; + int digest_len = 0; + char src_ip[INET_ADDRSTRLEN+1] = {0}; + char dst_ip[INET_ADDRSTRLEN+1] = {0}; + + struct digest_cache_list *digest_elm = NULL; + + digest_len = strlen(digest); - /* If we make it here, then this is a new SPA packet that needs to be - * added to the cache. We've already decrypted the data, so we know that - * the contents are valid. - */ if ((digest_elm = calloc(1, sizeof(struct digest_cache_list))) == NULL) { log_msg(LOG_WARNING, "Error calloc() returned NULL for digest cache element", fko_errstr(SPA_MSG_ERROR)); - free(digest); return(SPA_MSG_ERROR); } if ((digest_elm->cache_info.digest = calloc(1, digest_len+1)) == NULL) @@ -551,7 +502,6 @@ is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data) log_msg(LOG_WARNING, "Error calloc() returned NULL for digest cache string", fko_errstr(SPA_MSG_ERROR)); free(digest_elm); - free(digest); return(SPA_MSG_ERROR); } @@ -574,7 +524,6 @@ is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data) { log_msg(LOG_WARNING, "Could not open digest cache: %s", opts->config[CONF_DIGEST_FILE]); - free(digest); return(SPA_MSG_DIGEST_CACHE_ERROR); } @@ -593,14 +542,13 @@ is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data) fclose(digest_file_ptr); - free(digest); return(SPA_MSG_SUCCESS); } #endif /* USE_FILE_CACHE */ #if !USE_FILE_CACHE int -is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data) +is_replay_dbm_cache(fko_srv_options_t *opts, char *digest) { #ifdef NO_DIGEST_CACHE return 0; @@ -614,22 +562,10 @@ is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data) datum db_key, db_ent; char *digest = NULL; - int digest_len, res; + int digest_len, res = SPA_MSG_SUCCESS; digest_cache_info_t dc_info; - res = get_raw_digest(&digest, (char *)pkt_data); - - if(res != FKO_SUCCESS) - { - if (digest != NULL) - free(digest); - return res; - } - - if (digest == NULL) - return SPA_MSG_ERROR; - digest_len = strlen(digest); db_key.dptr = digest; @@ -652,7 +588,6 @@ is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data) MY_DBM_STRERROR(errno) ); - free(digest); return(SPA_MSG_DIGEST_CACHE_ERROR); } @@ -676,9 +611,65 @@ is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data) #ifdef HAVE_LIBGDBM free(db_ent.dptr); #endif - res = SPA_MSG_REPLAY; - } else { + } + + MY_DBM_CLOSE(rpdb); + + return(res); +#endif /* NO_DIGEST_CACHE */ +} + +int +add_replay_dbm_cache(fko_srv_options_t *opts, char *digest) +{ +#ifdef NO_DIGEST_CACHE + return 0; +#else + +#ifdef HAVE_LIBGDBM + GDBM_FILE rpdb; +#elif HAVE_LIBNDBM + DBM *rpdb; +#endif + datum db_key, db_ent; + + char *digest = NULL; + int digest_len, res = SPA_MSG_SUCCESS; + + digest_cache_info_t dc_info; + + digest_len = strlen(digest); + + db_key.dptr = digest; + db_key.dsize = digest_len; + + /* Check the db for the key + */ +#ifdef HAVE_LIBGDBM + rpdb = gdbm_open( + opts->config[CONF_DIGEST_DB_FILE], 512, GDBM_WRCREAT, S_IRUSR|S_IWUSR, 0 + ); +#elif HAVE_LIBNDBM + rpdb = dbm_open(opts->config[CONF_DIGEST_DB_FILE], O_RDWR, 0); +#endif + + if(!rpdb) + { + log_msg(LOG_WARNING, "Error opening digest_cache: '%s': %s", + opts->config[CONF_DIGEST_DB_FILE], + MY_DBM_STRERROR(errno) + ); + + return(SPA_MSG_DIGEST_CACHE_ERROR); + } + + db_ent = MY_DBM_FETCH(rpdb, db_key); + + /* If the datum is null, we have a new entry. + */ + if(db_ent.dptr == NULL) + { /* This is a new SPA packet that needs to be added to the cache. */ dc_info.src_ip = opts->spa_pkt.packet_src_ip; @@ -703,13 +694,14 @@ is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data) res = SPA_MSG_SUCCESS; } + else + res = SPA_MSG_DIGEST_CACHE_ERROR; MY_DBM_CLOSE(rpdb); - free(digest); return(res); #endif /* NO_DIGEST_CACHE */ -} + #endif /* USE_FILE_CACHE */ #if USE_FILE_CACHE diff --git a/server/replay_cache.h b/server/replay_cache.h index 8d785b7a..d523fa10 100644 --- a/server/replay_cache.h +++ b/server/replay_cache.h @@ -59,14 +59,17 @@ struct digest_cache_list { /* Prototypes */ int replay_cache_init(fko_srv_options_t *opts); -int is_replay(fko_srv_options_t *opts, unsigned char *pkt_data); +int is_replay(fko_srv_options_t *opts, char *digest); +int add_replay(fko_srv_options_t *opts, char *digest); #ifdef USE_FILE_CACHE int replay_file_cache_init(fko_srv_options_t *opts); -int is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data); +int is_replay_file_cache(fko_srv_options_t *opts, char *digest); +int add_replay_file_cache(fko_srv_options_t *opts, char *digest); void free_replay_list(fko_srv_options_t *opts); #else int replay_db_cache_init(fko_srv_options_t *opts); -int is_replay_dbm_cache(fko_srv_options_t *opts, unsigned char *pkt_data); +int is_replay_dbm_cache(fko_srv_options_t *opts, char *digest); +int add_replay_dbm_cache(fko_srv_options_t *opts, char *digest); #endif #endif /* REPLAY_CACHE_H */