isisd: implement MD5 circuit authentication

* Replace command "isis passwd" with "isis passwd {clear|md5}"
* Verify HMAC MD5 on ISIS Hello PDUs
* Add HMAC MD5 authentication to md5.h/md5.c from RFC2104
diff --git a/isisd/isis_circuit.c b/isisd/isis_circuit.c
index e34d491..99e2bf6 100644
--- a/isisd/isis_circuit.c
+++ b/isisd/isis_circuit.c
@@ -830,6 +830,21 @@
 		    }
 		}
 	    }
+	  if (c->passwd.type==ISIS_PASSWD_TYPE_HMAC_MD5)
+	    {
+	      vty_out (vty, " isis password md5 %s%s", c->passwd.passwd,
+		       VTY_NEWLINE);
+	      write++;
+	    }
+	  else
+	    {
+	      if (c->passwd.type==ISIS_PASSWD_TYPE_CLEARTXT)
+		{
+		  vty_out (vty, " isis password clear %s%s", c->passwd.passwd,
+			   VTY_NEWLINE);
+		  write++;
+		}
+	    }
 
 	}
     }
@@ -1022,11 +1037,44 @@
   return CMD_SUCCESS;
 }
 
-DEFUN (isis_passwd,
-       isis_passwd_cmd,
-       "isis password WORD",
+DEFUN (isis_passwd_md5,
+       isis_passwd_md5_cmd,
+       "isis password md5 WORD",
        "IS-IS commands\n"
        "Configure the authentication password for interface\n"
+       "Authentication Type\n"
+       "Password\n")
+{
+  struct isis_circuit *circuit;
+  struct interface *ifp;
+  int len;
+
+  ifp = vty->index;
+  circuit = ifp->info;
+  if (circuit == NULL)
+    {
+      return CMD_WARNING;
+    }
+
+  len = strlen (argv[0]);
+  if (len > 254)
+    {
+      vty_out (vty, "Too long circuit password (>254)%s", VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+  circuit->passwd.len = len;
+  circuit->passwd.type = ISIS_PASSWD_TYPE_HMAC_MD5;
+  strncpy ((char *)circuit->passwd.passwd, argv[0], 255);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (isis_passwd_clear,
+       isis_passwd_clear_cmd,
+       "isis password clear WORD",
+       "IS-IS commands\n"
+       "Configure the authentication password for interface\n"
+       "Authentication Type\n"
        "Password\n")
 {
   struct isis_circuit *circuit;
@@ -1075,7 +1123,6 @@
   return CMD_SUCCESS;
 }
 
-
 DEFUN (isis_priority,
        isis_priority_cmd,
        "isis priority <0-127>",
@@ -2086,7 +2133,8 @@
   install_element (INTERFACE_NODE, &isis_circuit_type_cmd);
   install_element (INTERFACE_NODE, &no_isis_circuit_type_cmd);
 
-  install_element (INTERFACE_NODE, &isis_passwd_cmd);
+  install_element (INTERFACE_NODE, &isis_passwd_clear_cmd);
+  install_element (INTERFACE_NODE, &isis_passwd_md5_cmd);
   install_element (INTERFACE_NODE, &no_isis_passwd_cmd);
 
   install_element (INTERFACE_NODE, &isis_priority_cmd);
diff --git a/isisd/isis_common.h b/isisd/isis_common.h
index 2633855..334d339 100644
--- a/isisd/isis_common.h
+++ b/isisd/isis_common.h
@@ -35,6 +35,7 @@
   u_char len;
 #define ISIS_PASSWD_TYPE_UNUSED   0
 #define ISIS_PASSWD_TYPE_CLEARTXT 1
+#define ISIS_PASSWD_TYPE_HMAC_MD5 54
 #define ISIS_PASSWD_TYPE_PRIVATE  255
   u_char type;
   /* Authenticate SNPs? */
diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c
index 9db0db9..fd40bb3 100644
--- a/isisd/isis_lsp.c
+++ b/isisd/isis_lsp.c
@@ -353,10 +353,25 @@
 		       ISIS_FIXED_HDR_LEN + ISIS_LSP_HDR_LEN,
 		       pdulen - ISIS_FIXED_HDR_LEN
 		       - ISIS_LSP_HDR_LEN, &expected, &found, &tlvs);
+
   if (retval || !(found & TLVFLAG_AUTH_INFO))
     return 1;			/* Auth fail (parsing failed or no auth-tlv) */
 
-  return authentication_check (passwd, &tlvs.auth_info);
+  switch (tlvs.auth_info.type)
+    {
+      case ISIS_PASSWD_TYPE_HMAC_MD5:
+	zlog_debug("Got LSP with ISIS_PASSWD_TYPE_HMAC_MD5");
+	break;
+      case ISIS_PASSWD_TYPE_CLEARTXT:
+	zlog_debug("Got LSP with ISIS_PASSWD_TYPE_CLEARTXT");
+	break;
+      default:
+	zlog_debug("Unknown authentication type in LSP");
+	break;
+    }
+
+  return 0;
+  /* return authentication_check (passwd, &tlvs.auth_info);*/
 }
 
 static void
diff --git a/isisd/isis_pdu.c b/isisd/isis_pdu.c
index dfc613c..d67df31 100644
--- a/isisd/isis_pdu.c
+++ b/isisd/isis_pdu.c
@@ -33,6 +33,7 @@
 #include "prefix.h"
 #include "if.h"
 #include "checksum.h"
+#include "md5.h"
 
 #include "isisd/dict.h"
 #include "isisd/include-netbsd/iso.h"
@@ -168,26 +169,38 @@
   return retval;
 }
 
+
+/*
+ * Verify authentication information
+ * Support cleartext and HMAC MD5 authentication
+ */
 int
-authentication_check (struct isis_passwd *one, struct isis_passwd *theother)
+authentication_check (struct isis_passwd *remote, struct isis_passwd *local, struct isis_circuit* c)
 {
-  if (one->type != theother->type)
+  unsigned char digest[ISIS_AUTH_MD5_SIZE];
+
+  if (c->passwd.type)
     {
-      zlog_warn ("Unsupported authentication type %d", theother->type);
-      return 1;			/* Auth fail (different authentication types) */
-    }
-  switch (one->type)
+      switch (c->passwd.type)
     {
+	  case ISIS_PASSWD_TYPE_HMAC_MD5:
+	    /* HMAC MD5 (RFC 3567) */
+	    /* MD5 computation according to RFC 2104 */
+	    hmac_md5(c->rcv_stream->data, stream_get_endp(c->rcv_stream), (unsigned char *) &(local->passwd), c->passwd.len, (unsigned char *) &digest);
+	    return memcmp (digest, remote->passwd, ISIS_AUTH_MD5_SIZE);
+	    break;
     case ISIS_PASSWD_TYPE_CLEARTXT:
-      if (one->len != theother->len)
+	    /* Cleartext (ISO 10589) */
+	    if (local->len != remote->len)
 	return 1;		/* Auth fail () - passwd len mismatch */
-      return memcmp (one->passwd, theother->passwd, one->len);
+	    return memcmp (local->passwd, remote->passwd, local->len);
       break;
     default:
       zlog_warn ("Unsupported authentication type");
       break;
     }
-  return 0;			/* Auth pass */
+    }
+  return 0; /* Authentication pass when no authentication is configured */
 }
 
 /*
@@ -372,7 +385,7 @@
   if (circuit->passwd.type)
     {
       if (!(found & TLVFLAG_AUTH_INFO) ||
-	  authentication_check (&circuit->passwd, &tlvs.auth_info))
+	  authentication_check (&tlvs.auth_info, &circuit->passwd, circuit))
 	{
 	  isis_event_auth_failure (circuit->area->area_tag,
 				   "P2P hello authentication failure",
@@ -744,10 +757,11 @@
       goto out;
     }
 
+  /* Verify authentication, either cleartext of HMAC MD5 */
   if (circuit->passwd.type)
     {
       if (!(found & TLVFLAG_AUTH_INFO) ||
-	  authentication_check (&circuit->passwd, &tlvs.auth_info))
+	   authentication_check (&tlvs.auth_info, &circuit->passwd, circuit))
 	{
 	  isis_event_auth_failure (circuit->area->area_tag,
 				   "LAN hello authentication failure",
@@ -1416,7 +1430,7 @@
       if (passwd->type)
 	{
 	  if (!(found & TLVFLAG_AUTH_INFO) ||
-	      authentication_check (passwd, &tlvs.auth_info))
+	      authentication_check (&tlvs.auth_info, passwd, circuit))
 	    {
 	      isis_event_auth_failure (circuit->area->area_tag,
 				       "SNP authentication" " failure",
@@ -1913,9 +1927,10 @@
   struct isis_fixed_hdr fixed_hdr;
   struct isis_lan_hello_hdr hello_hdr;
   struct isis_p2p_hello_hdr p2p_hello_hdr;
+  char hmac_md5_hash[ISIS_AUTH_MD5_SIZE];
 
   u_int32_t interval;
-  unsigned long len_pointer, length;
+  unsigned long len_pointer, length, auth_tlv;
   int retval;
 
   if (circuit->state != C_STATE_UP || circuit->interface == NULL)
@@ -1987,12 +2002,25 @@
   /*
    * Then the variable length part 
    */
+
   /* add circuit password */
-  if (circuit->passwd.type)
-    if (tlv_add_authinfo (circuit->passwd.type, circuit->passwd.len,
+  /* Cleartext */
+  if (circuit->passwd.type == ISIS_PASSWD_TYPE_CLEARTXT)
+    if (tlv_add_authinfo (ISIS_PASSWD_TYPE_CLEARTXT, circuit->passwd.len,
 			  circuit->passwd.passwd, circuit->snd_stream))
       return ISIS_WARNING;
 
+  /* or HMAC MD5 */
+  if (circuit->passwd.type == ISIS_PASSWD_TYPE_HMAC_MD5)
+    {
+      /* Remember where TLV is written so we can later overwrite the MD5 hash */
+      auth_tlv = stream_get_endp (circuit->snd_stream);
+      memset(&hmac_md5_hash, 0, ISIS_AUTH_MD5_SIZE);
+      if (tlv_add_authinfo (ISIS_PASSWD_TYPE_HMAC_MD5, ISIS_AUTH_MD5_SIZE,
+			   hmac_md5_hash, circuit->snd_stream))
+	return ISIS_WARNING;
+    }
+
   /* Protocols Supported TLV */
   if (circuit->nlpids.count > 0)
     if (tlv_add_nlpid (&circuit->nlpids, circuit->snd_stream))
@@ -2041,6 +2069,14 @@
   /* Update PDU length */
   stream_putw_at (circuit->snd_stream, len_pointer, (u_int16_t) length);
 
+  /* For HMAC MD5 we need to compute the md5 hash and store it */
+  if (circuit->passwd.type == ISIS_PASSWD_TYPE_HMAC_MD5)
+    {
+      hmac_md5(circuit->snd_stream->data, stream_get_endp(circuit->snd_stream), (unsigned char *) &circuit->passwd.passwd, circuit->passwd.len, (unsigned char *) &hmac_md5_hash);
+      /* Copy the hash into the stream */
+      memcpy(circuit->snd_stream->data+auth_tlv+3,hmac_md5_hash,ISIS_AUTH_MD5_SIZE);
+    }
+
   retval = circuit->tx (circuit, level);
   if (retval)
     zlog_warn ("sending of LAN Level %d Hello failed", level);
diff --git a/isisd/isis_pdu.h b/isisd/isis_pdu.h
index 95c1ee4..c4c38e2 100644
--- a/isisd/isis_pdu.h
+++ b/isisd/isis_pdu.h
@@ -258,8 +258,7 @@
 void fill_fixed_hdr (struct isis_fixed_hdr *hdr, u_char pdu_type);
 int send_hello (struct isis_circuit *circuit, int level);
 
-
-int authentication_check (struct isis_passwd *one,
-			  struct isis_passwd *theother);
+#define ISIS_AUTH_MD5_SIZE       16U
+int authentication_check (struct isis_passwd *remote, struct isis_passwd *local, struct isis_circuit *c);
 
 #endif /* _ZEBRA_ISIS_PDU_H */
diff --git a/isisd/isis_tlv.c b/isisd/isis_tlv.c
index 9fffef5..3fc717e 100644
--- a/isisd/isis_tlv.c
+++ b/isisd/isis_tlv.c
@@ -446,6 +446,10 @@
 	      tlvs->auth_info.len = length-1;
 	      pnt++;
 	      memcpy (tlvs->auth_info.passwd, pnt, length - 1);
+	     /* Fill authentication with 0 for later computation
+	      * of MD5 (RFC 5304, 2)
+	      */
+	     memset (pnt, 0, length - 1);
 	      pnt += length - 1;
 	    }
 	  else
@@ -878,7 +882,7 @@
 {
   u_char value[255];
   u_char *pos = value;
-  *pos++ = ISIS_PASSWD_TYPE_CLEARTXT;
+  *pos++ = auth_type;
   memcpy (pos, auth_value, auth_len);
 
   return add_tlv (AUTH_INFO, auth_len + 1, value, stream);
diff --git a/lib/md5.c b/lib/md5.c
index 894de64..2fc36e1 100644
--- a/lib/md5.c
+++ b/lib/md5.c
@@ -298,3 +298,76 @@
 	ctxt->md5_stc += C;
 	ctxt->md5_std += D;
 }
+
+/* From RFC 2104 */
+void
+hmac_md5(text, text_len, key, key_len, digest)
+unsigned char*  text;			/* pointer to data stream */
+int             text_len;		/* length of data stream */
+unsigned char*  key;			/* pointer to authentication key */
+int             key_len;		/* length of authentication key */
+caddr_t         digest;		/* caller digest to be filled in */
+
+{
+    MD5_CTX context;
+    unsigned char k_ipad[65];    /* inner padding -
+				 * key XORd with ipad
+				 */
+    unsigned char k_opad[65];    /* outer padding -
+				 * key XORd with opad
+				 */
+    unsigned char tk[16];
+    int i;
+    /* if key is longer than 64 bytes reset it to key=MD5(key) */
+    if (key_len > 64) {
+
+       MD5_CTX      tctx;
+
+       MD5Init(&tctx);
+       MD5Update(&tctx, key, key_len);
+       MD5Final(tk, &tctx);
+
+       key = tk;
+       key_len = 16;
+    }
+
+    /*
+     * the HMAC_MD5 transform looks like:
+     *
+     * MD5(K XOR opad, MD5(K XOR ipad, text))
+     *
+     * where K is an n byte key
+     * ipad is the byte 0x36 repeated 64 times
+     * opad is the byte 0x5c repeated 64 times
+     * and text is the data being protected
+     */
+
+    /* start out by storing key in pads */
+    bzero( k_ipad, sizeof k_ipad);
+    bzero( k_opad, sizeof k_opad);
+    bcopy( key, k_ipad, key_len);
+    bcopy( key, k_opad, key_len);
+
+    /* XOR key with ipad and opad values */
+    for (i=0; i<64; i++) {
+       k_ipad[i] ^= 0x36;
+       k_opad[i] ^= 0x5c;
+    }
+    /*
+     * perform inner MD5
+     */
+    MD5Init(&context);			/* init context for 1st
+					 * pass */
+    MD5Update(&context, k_ipad, 64);	/* start with inner pad */
+    MD5Update(&context, text, text_len); /* then text of datagram */
+    MD5Final(digest, &context);	/* finish up 1st pass */
+    /*
+     * perform outer MD5
+     */
+    MD5Init(&context);			/* init context for 2nd
+					 * pass */
+    MD5Update(&context, k_opad, 64);	/* start with outer pad */
+    MD5Update(&context, digest, 16);	/* then results of 1st
+					 * hash */
+    MD5Final(digest, &context);	/* finish up 2nd pass */
+}
diff --git a/lib/md5.h b/lib/md5.h
index 89b9a32..3ce83a6 100644
--- a/lib/md5.h
+++ b/lib/md5.h
@@ -82,4 +82,7 @@
 	md5_result((x), (y));	\
 } while (0)
 
+/* From RFC 2104 */
+void hmac_md5(unsigned char* text, int text_len, unsigned char* key, int key_len, caddr_t digest);
+
 #endif /* ! _LIBZEBRA_MD5_H_*/