Only cache replay digests for SPA packets that decrypt
authorMichael Rash <mbr@cipherdyne.org>
Sun, 8 Jul 2012 12:36:30 +0000 (08:36 -0400)
committerMichael Rash <mbr@cipherdyne.org>
Sun, 8 Jul 2012 12:36:30 +0000 (08:36 -0400)
This change ensures that we only cache replay digests for those SPA packets
that actually decrypt.  Not doing this would have allowed an attacker to
potentially fill up digest cache space with digests for garbage packets.

server/incoming_spa.c
server/replay_cache.c
server/replay_cache.h

index 024efe3..2c33192 100644 (file)
@@ -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;
 }
index fdc99e5..2b164e9 100644 (file)
 #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 */
 }
 
-#if USE_FILE_CACHE
 int
-is_replay_file_cache(fko_srv_options_t *opts, unsigned char *pkt_data)
+add_replay(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;
-
-    struct digest_cache_list *digest_list_ptr = NULL, *digest_elm = NULL;
+#ifdef NO_DIGEST_CACHE
+    return(-1);
+#else
 
-    res = get_raw_digest(&digest, (char *)pkt_data);
+#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(res != FKO_SUCCESS)
-    {
-        if (digest != NULL)
-            free(digest);
-        return res;
-    }
+#if USE_FILE_CACHE
+int
+is_replay_file_cache(fko_srv_options_t *opts, char *digest)
+{
+    int         digest_len = 0;
 
-    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
index 8d785b7..d523fa1 100644 (file)
@@ -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 */