Added support for server public key verification.
authorPekka Riikonen <priikone@silcnet.org>
Fri, 7 Jul 2000 06:53:45 +0000 (06:53 +0000)
committerPekka Riikonen <priikone@silcnet.org>
Fri, 7 Jul 2000 06:53:45 +0000 (06:53 +0000)
apps/silc/clientutil.c
apps/silc/clientutil.h
apps/silc/protocol.c

index 66ba97170aa002c8d90ab9d2356d6d31c34de304..8fd39a0d9fb0e6164860ec443e5dab448839001a 100644 (file)
@@ -20,6 +20,9 @@
 /*
  * $Id$
  * $Log$
+ * Revision 1.3  2000/07/07 06:53:45  priikone
+ *     Added support for server public key verification.
+ *
  * Revision 1.2  2000/07/05 06:11:00  priikone
  *     Added ~./silc directory checking, autoloading of keys and
  *     tweaked the key pair generation function.
@@ -259,6 +262,42 @@ char *silc_client_ask_passphrase(SilcClient client)
   return ret;
 }
 
+/* Asks yes/no from user on the input line. Returns TRUE on "yes" and
+   FALSE on "no". */
+
+int silc_client_ask_yes_no(SilcClient client, char *prompt)
+{
+  char answer[4];
+  int ret;
+
+ again:
+  silc_screen_input_reset(client->screen);
+
+  /* Print prompt */
+  wattroff(client->screen->input_win, A_INVIS);
+  silc_screen_input_print_prompt(client->screen, prompt);
+
+  /* Get string */
+  memset(answer, 0, sizeof(answer));
+  echo();
+  wgetnstr(client->screen->input_win, answer, sizeof(answer));
+  if (!strncasecmp(answer, "yes", strlen(answer)) ||
+      !strncasecmp(answer, "y", strlen(answer))) {
+    ret = TRUE;
+  } else if (!strncasecmp(answer, "no", strlen(answer)) ||
+            !strncasecmp(answer, "n", strlen(answer))) {
+    ret = FALSE;
+  } else {
+    silc_say(client, "Type yes or no");
+    goto again;
+  }
+  noecho();
+
+  silc_screen_input_reset(client->screen);
+
+  return ret;
+}
+
 /* Lists supported (builtin) ciphers */
 
 void silc_client_list_ciphers()
@@ -516,6 +555,11 @@ New pair of keys will be created.  Please, answer to following questions.\n\
   if (ret_prv_key)
     *ret_prv_key = prv_key;
 
+  printf("Public key has been save into `%s'.\n", pkfile);
+  printf("Private key has been saved into `%s'.\n", prvfile);
+  printf("Press <Enter> to continue...\n");
+  getchar();
+
   memset(key, 0, sizeof(key_len));
   silc_free(key);
 
@@ -536,6 +580,7 @@ New pair of keys will be created.  Please, answer to following questions.\n\
 int silc_client_check_silc_dir()
 {
   char filename[256], file_public_key[256], file_private_key[256];
+  char servfilename[256];
   char *identifier;
   struct stat st;
   struct passwd *pw;
@@ -558,6 +603,8 @@ int silc_client_check_silc_dir()
 
   /* We'll take home path from /etc/passwd file to be sure. */
   snprintf(filename, sizeof(filename) - 1, "%s/.silc/", pw->pw_dir);
+  snprintf(servfilename, sizeof(servfilename) - 1, "%s/.silc/serverkeys", 
+          pw->pw_dir);
 
   /*
    * Check ~/.silc directory
@@ -599,6 +646,28 @@ int silc_client_check_silc_dir()
       return FALSE;
     }
   }
+
+  /*
+   * Check ~./silc/serverkeys directory
+   */
+  if ((stat(servfilename, &st)) == -1) {
+    /* If dir doesn't exist */
+    if (errno == ENOENT) {
+      if (pw->pw_uid == geteuid()) {
+       if ((mkdir(servfilename, 0755)) == -1) {
+         fprintf(stderr, "Couldn't create `%s' directory\n", servfilename);
+         return FALSE;
+       }
+      } else {
+       fprintf(stderr, "Couldn't create `%s' directory due to a wrong uid!\n",
+               servfilename);
+       return FALSE;
+      }
+    } else {
+      fprintf(stderr, "%s\n", strerror(errno));
+      return FALSE;
+    }
+  }
   
   /*
    * Check Public and Private keys
@@ -610,7 +679,7 @@ int silc_client_check_silc_dir()
   
   /* If running SILC first time */
   if (firstime) {
-    fprintf(stdout, "Running SILC for the first time.\n");
+    fprintf(stdout, "Running SILC for the first time\n");
     silc_client_create_key_pair(SILC_CLIENT_DEF_PKCS, 
                                SILC_CLIENT_DEF_PKCS_LEN,
                                file_public_key, file_private_key, 
@@ -726,3 +795,121 @@ int silc_client_load_keys(SilcClient client)
 
   return TRUE;
 }
+
+/* Verifies received public key. If user decides to trust the key it is
+   saved as trusted server key for later use. If user does not trust the
+   key this returns FALSE. */
+
+int silc_client_verify_server_key(SilcClient client, 
+                                 SilcSocketConnection sock,
+                                 unsigned char *pk, unsigned int pk_len,
+                                 SilcSKEPKType pk_type)
+{
+  char filename[256];
+  char file[256];
+  char *hostname;
+  struct passwd *pw;
+  struct stat st;
+
+  hostname = sock->hostname ? sock->hostname : sock->ip;
+
+  if (pk_type != SILC_SKE_PK_TYPE_SILC) {
+    silc_say(client, "We don't support server %s key type", hostname);
+    return FALSE;
+  }
+
+  pw = getpwuid(getuid());
+  if (!pw)
+    return FALSE;
+
+  memset(filename, 0, sizeof(filename));
+  memset(file, 0, sizeof(file));
+  snprintf(file, sizeof(file) - 1, "serverkey_%s_%d.pub", hostname,
+          sock->port);
+  snprintf(filename, sizeof(filename) - 1, "%s/.silc/serverkeys/%s", 
+          pw->pw_dir, file);
+
+  /* Check wheter this key already exists */
+  if (stat(filename, &st) < 0) {
+
+    silc_say(client, "Received server %s public key", hostname);
+    /* XXX print fingerprint of the key */
+
+    /* Ask user to verify the key and save it */
+    if (silc_client_ask_yes_no(client, 
+       "Would you like to accept the server key (y/n)? "))
+      {
+       /* Save the key for future checking */
+       silc_pkcs_save_public_key_data(filename, pk, pk_len);
+       return TRUE;
+      }
+  } else {
+    /* The key already exists, verify it. */
+    SilcPublicKey public_key;
+    unsigned char *encpk;
+    unsigned int encpk_len;
+
+    /* Load the key file */
+    if (!silc_pkcs_load_public_key(filename, &public_key)) {
+      silc_say(client, "Received server %s public key", hostname);
+      silc_say(client, "Could not load your local copy of the server %s key",
+              hostname);
+      if (silc_client_ask_yes_no(client, 
+         "Would you like to accept the server key anyway (y/n)? "))
+       {
+         /* Save the key for future checking */
+         unlink(filename);
+         silc_pkcs_save_public_key_data(filename, pk, pk_len);
+         return TRUE;
+       }
+
+      return FALSE;
+    }
+
+    /* Encode the key data */
+    encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
+    if (!encpk) {
+      silc_say(client, "Received server %s public key", hostname);
+      silc_say(client, "Your local copy of the server %s key is malformed",
+              hostname);
+      if (silc_client_ask_yes_no(client, 
+         "Would you like to accept the server key anyway (y/n)? "))
+       {
+         /* Save the key for future checking */
+         unlink(filename);
+         silc_pkcs_save_public_key_data(filename, pk, pk_len);
+         return TRUE;
+       }
+
+      return FALSE;
+    }
+
+    if (memcmp(encpk, pk, encpk_len)) {
+      silc_say(client, "Received server %s public key", hostname);
+      silc_say(client, "Server %s key does not match with your local copy",
+              hostname);
+      silc_say(client, "It is possible that the key has expired or changed");
+      silc_say(client, "It is also possible that some one is performing "
+                      "man-in-the-middle attack");
+      
+      /* Ask user to verify the key and save it */
+      if (silc_client_ask_yes_no(client, 
+         "Would you like to accept the server key anyway (y/n)? "))
+       {
+         /* Save the key for future checking */
+         unlink(filename);
+         silc_pkcs_save_public_key_data(filename, pk, pk_len);
+         return TRUE;
+       }
+
+      silc_say(client, "Will not accept server %s key", hostname);
+      return FALSE;
+    }
+
+    /* Local copy matched */
+    return TRUE;
+  }
+
+  silc_say(client, "Will not accept server %s key", hostname);
+  return FALSE;
+}
index 298c3bdae5b506dd96bfc044a5aa1aebe001479c..1ad7286a1fe62624f854e63673791582baef6478 100644 (file)
@@ -30,6 +30,7 @@ char *silc_get_username();
 char *silc_get_real_name();
 int silc_client_time_til_next_min();
 char *silc_client_ask_passphrase(SilcClient client);
+int silc_client_ask_yes_no(SilcClient client, char *prompt);
 char *silc_client_get_input(const char *prompt);
 char *silc_client_get_passphrase(const char *prompt);
 void silc_client_list_ciphers();
@@ -43,5 +44,9 @@ int silc_client_create_key_pair(char *pkcs_name, int bits,
                                SilcPrivateKey *ret_prv_key);
 int silc_client_check_silc_dir();
 int silc_client_load_keys(SilcClient client);
+int silc_client_verify_server_key(SilcClient client, 
+                                 SilcSocketConnection sock,
+                                 unsigned char *pk, unsigned int pk_len,
+                                 SilcSKEPKType pk_type);
 
 #endif
index 5cc3c48a4ea3e52485a8cc986b7a44f47f2bbbd8..541d26dbf05b150c3c5abed1131f1ce68e1f43fc 100644 (file)
@@ -23,6 +23,9 @@
 /*
  * $Id$
  * $Log$
+ * Revision 1.4  2000/07/07 06:53:45  priikone
+ *     Added support for server public key verification.
+ *
  * Revision 1.3  2000/07/06 07:14:16  priikone
  *     Deprecated old `channel_auth' protocol.
  *
@@ -55,6 +58,8 @@ const SilcProtocolObject silc_protocol_list[] =
  * Key Exhange protocol functions
  */
 
+/* Function that is called when SKE protocol sends packets to network. */
+
 static void silc_client_protocol_ke_send_packet(SilcSKE ske,
                                                SilcBuffer packet,
                                                SilcPacketType type,
@@ -71,20 +76,15 @@ static void silc_client_protocol_ke_send_packet(SilcSKE ske,
 
 }
 
-static void silc_client_protocol_ke_phase1_cb(SilcSKE ske,
-                                             void *context)
-{
-  SilcProtocol protocol = (SilcProtocol)context;
-  SilcClientKEInternalContext *ctx = 
-    (SilcClientKEInternalContext *)protocol->context;
-  SilcClient client = (SilcClient)ctx->client;
-
-  SILC_LOG_DEBUG(("Start"));
+/* Callback that is called when we have received KE2 payload from
+   responder. We try to verify the public key now. */
 
-}
-
-static void silc_client_protocol_ke_finish_cb(SilcSKE ske,
-                                             void *context)
+static SilcSKEStatus 
+silc_client_protocol_ke_verify_key(SilcSKE ske,
+                                  unsigned char *pk_data,
+                                  unsigned int pk_len,
+                                  SilcSKEPKType pk_type,
+                                  void *context)
 {
   SilcProtocol protocol = (SilcProtocol)context;
   SilcClientKEInternalContext *ctx = 
@@ -93,6 +93,11 @@ static void silc_client_protocol_ke_finish_cb(SilcSKE ske,
 
   SILC_LOG_DEBUG(("Start"));
 
+  if (!silc_client_verify_server_key(client, ctx->sock, 
+                                    pk_data, pk_len, pk_type))
+    return SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY;
+
+  return SILC_SKE_STATUS_OK;
 }
 
 /* Sets the negotiated key material into use for particular connection. */
@@ -226,11 +231,7 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
           paylaod reply we just got from the responder. The callback
           function will receive the processed payload where we will
           save it. */
-       status = 
-         silc_ske_initiator_phase_1(ctx->ske,
-                                    ctx->packet,
-                                    silc_client_protocol_ke_phase1_cb,
-                                    context);
+       status = silc_ske_initiator_phase_1(ctx->ske, ctx->packet, NULL, NULL);
       }
 
       switch(status) {
@@ -281,6 +282,7 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
        * Finish protocol
        */
       if (ctx->responder == TRUE) {
+       status = 0;
 #if 0
        status = 
          silc_ske_responder_phase_2(ctx->ske, 
@@ -291,16 +293,15 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
       } else {
        /* Finish the protocol. This verifies the Key Exchange 2 payload
           sent by responder. */
-       status = 
-         silc_ske_initiator_finish(ctx->ske,
-                                   ctx->packet,
-                                   silc_client_protocol_ke_finish_cb,
-                                   context);
+       status = silc_ske_initiator_finish(ctx->ske, ctx->packet,
+                                          silc_client_protocol_ke_verify_key,
+                                          context, NULL, NULL);
       }
 
-      switch(status) {
-      default:
-       break;
+      if (status != SILC_SKE_STATUS_OK) {
+       protocol->state = SILC_PROTOCOL_STATE_ERROR;
+       protocol->execute(client->timeout_queue, 0, protocol, fd, 0, 0);
+       return;
       }
       
       /* Send Ok to the other end. We will end the protocol as server
@@ -338,7 +339,10 @@ SILC_TASK_CALLBACK(silc_client_protocol_key_exchange)
   case SILC_PROTOCOL_STATE_ERROR:
     
     /* On error the final callback is always called. */
-    /*    protocol->final_callback(pptr, context);*/
+    if (protocol->final_callback)
+      protocol->execute_final(client->timeout_queue, 0, protocol, fd);
+    else
+      silc_protocol_free(protocol);
     break;
   case SILC_PROTOCOL_STATE_UNKNOWN:
     break;