소스 검색

Bug 1589: Support SHA256 host key fingerprints + Fingerprint tests

https://winscp.net/tracker/1589

Source commit: e5115747f7ae8d1f1bcd15a5e039701ea2b2176b
Martin Prikryl 7 년 전
부모
커밋
8412053553

+ 1 - 1
dotnet/SessionOptions.cs

@@ -431,7 +431,7 @@ namespace WinSCP
         private Protocol _protocol;
 
         private const string _listPattern = @"{0}(;{0})*";
-        private const string _sshHostKeyPattern = @"((ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-sha2-nistp(256|384|521))( |-))?(\d+ )?([0-9a-f]{2}(:|-)){15}[0-9a-f]{2}";
+        private const string _sshHostKeyPattern = @"((ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-sha2-nistp(256|384|521))( |-))?(\d+ )?(([0-9a-f]{2}(:|-)){15}[0-9a-f]{2}|[0-9a-zA-Z+/]{43}=)";
         private static readonly Regex _sshHostKeyRegex =
             new Regex(string.Format(CultureInfo.InvariantCulture, _listPattern, _sshHostKeyPattern));
         private const string _tlsCertificatePattern = @"([0-9a-f]{2}:){19}[0-9a-f]{2}";

+ 36 - 0
source/core/PuttyIntf.cpp

@@ -727,6 +727,7 @@ static void __fastcall DoNormalizeFingerprint(UnicodeString & Fingerprint, Unico
       if (IsNumber(Fingerprint.SubString(LenStart + 1, Space - LenStart - 1)))
       {
         Fingerprint.Delete(LenStart + 1, Space - LenStart);
+        // noop for SHA256 fingerprints
         Fingerprint = ReplaceChar(Fingerprint, L':', NormalizedSeparator);
         KeyType = UnicodeString(SignKey->keytype);
         return;
@@ -822,4 +823,39 @@ UnicodeString __fastcall ParseOpenSshPubLine(const UnicodeString & Line, const s
   return Result;
 }
 //---------------------------------------------------------------------------
+UnicodeString __fastcall GetKeyTypeHuman(const UnicodeString & KeyType)
+{
+  UnicodeString Result;
+  if (KeyType == ssh_dss.keytype)
+  {
+    Result = L"DSA";
+  }
+  else if (KeyType == ssh_rsa.keytype)
+  {
+    Result = L"RSA";
+  }
+  else if (KeyType == ssh_ecdsa_ed25519.keytype)
+  {
+    Result = L"Ed25519";
+  }
+  else if (KeyType == ssh_ecdsa_nistp256.keytype)
+  {
+    Result = L"ECDSA/nistp256";
+  }
+  else if (KeyType == ssh_ecdsa_nistp384.keytype)
+  {
+    Result = L"ECDSA/nistp384";
+  }
+  else if (KeyType == ssh_ecdsa_nistp521.keytype)
+  {
+    Result = L"ECDSA/nistp521";
+  }
+  else
+  {
+    DebugFail();
+    Result = KeyType;
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 //---------------------------------------------------------------------------

+ 2 - 0
source/core/PuttyTools.h

@@ -34,4 +34,6 @@ void __fastcall DllHijackingProtection();
 //---------------------------------------------------------------------------
 UnicodeString __fastcall ParseOpenSshPubLine(const UnicodeString & Line, const struct ssh_signkey *& Algorithm);
 //---------------------------------------------------------------------------
+UnicodeString __fastcall GetKeyTypeHuman(const UnicodeString & KeyType);
+//---------------------------------------------------------------------------
 #endif

+ 34 - 17
source/core/SecureShell.cpp

@@ -2170,7 +2170,8 @@ UnicodeString __fastcall TSecureShell::RetrieveHostKey(UnicodeString Host, int P
 struct TPasteKeyHandler
 {
   UnicodeString KeyStr;
-  UnicodeString NormalizedFingerprint;
+  UnicodeString NormalizedFingerprintMD5;
+  UnicodeString NormalizedFingerprintSHA256;
   TSessionUI * UI;
 
   void __fastcall Paste(TObject * Sender, unsigned int & Answer);
@@ -2183,7 +2184,8 @@ void __fastcall TPasteKeyHandler::Paste(TObject * /*Sender*/, unsigned int & Ans
   {
     UnicodeString NormalizedClipboardFingerprint = NormalizeFingerprint(ClipboardText);
     // case insensitive comparison, contrary to VerifyHostKey (we should change to insesitive there too)
-    if (SameText(NormalizedClipboardFingerprint, NormalizedFingerprint) ||
+    if (SameText(NormalizedClipboardFingerprint, NormalizedFingerprintMD5) ||
+        SameText(NormalizedClipboardFingerprint, NormalizedFingerprintSHA256) ||
         SameText(ClipboardText, KeyStr))
     {
       Answer = qaYes;
@@ -2212,8 +2214,9 @@ void __fastcall TPasteKeyHandler::Paste(TObject * /*Sender*/, unsigned int & Ans
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
-  const UnicodeString KeyType, UnicodeString KeyStr, UnicodeString Fingerprint)
+void __fastcall TSecureShell::VerifyHostKey(
+  const UnicodeString & AHost, int Port, const UnicodeString & KeyType, const UnicodeString & KeyStr,
+  const UnicodeString & Fingerprint)
 {
   if (Configuration->ActualLogProtocol >= 1)
   {
@@ -2224,21 +2227,31 @@ void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
 
   DebugAssert(KeyStr.Pos(HostKeyDelimiter) == 0);
 
+  UnicodeString Host = AHost;
   GetRealHost(Host, Port);
 
-  FSessionInfo.HostKeyFingerprint = Fingerprint;
+  UnicodeString Buf = Fingerprint;
+  UnicodeString SignKeyAlg = CutToChar(Buf, L' ', false);
+  UnicodeString SignKeySize = CutToChar(Buf, L' ', false);
+  UnicodeString SignKeyType = SignKeyAlg + L' ' + SignKeySize;
+  UnicodeString MD5 = CutToChar(Buf, L' ', false);
+  UnicodeString FingerprintMD5 = SignKeyType + L' ' + MD5;
+  UnicodeString SHA256 = Buf;
+  UnicodeString FingerprintSHA256 = SignKeyType + L' ' + SHA256;
+  UnicodeString NormalizedFingerprintMD5 = NormalizeFingerprint(FingerprintMD5);
+  UnicodeString NormalizedFingerprintSHA256 = NormalizeFingerprint(FingerprintSHA256);
+
+  FSessionInfo.HostKeyFingerprint = FingerprintSHA256;
 
   if (FSessionData->FingerprintScan)
   {
     Abort();
   }
 
-  UnicodeString NormalizedFingerprint = NormalizeFingerprint(Fingerprint);
-
   bool Result = false;
 
   UnicodeString StoredKeys = RetrieveHostKey(Host, Port, KeyType);
-  UnicodeString Buf = StoredKeys;
+  Buf = StoredKeys;
   while (!Result && !Buf.IsEmpty())
   {
     UnicodeString StoredKey = CutToChar(Buf, HostKeyDelimiter, false);
@@ -2253,7 +2266,7 @@ void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
       NormalizedExpectedKey = NormalizeFingerprint(StoredKey);
     }
     if ((!Fingerprint && (StoredKey == KeyStr)) ||
-        (Fingerprint && (NormalizedExpectedKey == NormalizedFingerprint)))
+        (Fingerprint && ((NormalizedExpectedKey == NormalizedFingerprintMD5) || (NormalizedExpectedKey == NormalizedFingerprintSHA256))))
     {
       LogEvent(L"Host key matches cached key");
       Result = true;
@@ -2289,7 +2302,7 @@ void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
         FLog->Add(llException, Message);
         Result = true;
       }
-      else if (NormalizedExpectedKey == NormalizedFingerprint)
+      else if ((NormalizedExpectedKey == NormalizedFingerprintMD5) || (NormalizedExpectedKey == NormalizedFingerprintSHA256))
       {
         LogEvent(L"Host key matches configured key");
         Result = true;
@@ -2324,10 +2337,11 @@ void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
       // but as scripting mode is handled earlier and in GUI it hardly happens,
       // it's a small issue.
       TClipboardHandler ClipboardHandler;
-      ClipboardHandler.Text = Fingerprint;
+      ClipboardHandler.Text = FingerprintSHA256 + L"\n" + FingerprintMD5;
       TPasteKeyHandler PasteKeyHandler;
       PasteKeyHandler.KeyStr = KeyStr;
-      PasteKeyHandler.NormalizedFingerprint = NormalizedFingerprint;
+      PasteKeyHandler.NormalizedFingerprintMD5 = NormalizedFingerprintMD5;
+      PasteKeyHandler.NormalizedFingerprintSHA256 = NormalizedFingerprintSHA256;
       PasteKeyHandler.UI = FUI;
 
       bool Unknown = StoredKeys.IsEmpty();
@@ -2364,7 +2378,9 @@ void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
       Params.Aliases = Aliases;
       Params.AliasesCount = AliasesCount;
 
-      UnicodeString Message = FMTLOAD((Unknown ? UNKNOWN_KEY3 : DIFFERENT_KEY4), (KeyType, Fingerprint));
+      UnicodeString KeyTypeHuman = GetKeyTypeHuman(KeyType);
+      UnicodeString KeyDetails = FMTLOAD(KEY_DETAILS, (SignKeyType, SHA256, MD5));
+      UnicodeString Message = FMTLOAD((Unknown ? UNKNOWN_KEY4 : DIFFERENT_KEY5), (KeyTypeHuman, KeyDetails));
       if (Configuration->Scripting)
       {
         AddToList(Message, LoadStr(SCRIPTING_USE_HOSTKEY), L"\n");
@@ -2372,14 +2388,15 @@ void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
 
       unsigned int R =
         FUI->QueryUser(Message, NULL, Answers, &Params, qtWarning);
+      UnicodeString StoreKeyStr = KeyStr;
 
       switch (R) {
         case qaOK:
           DebugAssert(!Unknown);
-          KeyStr = (StoredKeys + HostKeyDelimiter + KeyStr);
+          StoreKeyStr = (StoredKeys + HostKeyDelimiter + StoreKeyStr);
           // fall thru
         case qaYes:
-          store_host_key(AnsiString(Host).c_str(), Port, AnsiString(KeyType).c_str(), AnsiString(KeyStr).c_str());
+          store_host_key(AnsiString(Host).c_str(), Port, AnsiString(KeyType).c_str(), AnsiString(StoreKeyStr).c_str());
           Verified = true;
           break;
 
@@ -2414,7 +2431,7 @@ void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
       Exception * E = new Exception(MainInstructions(Message));
       try
       {
-        FUI->FatalError(E, FMTLOAD(HOSTKEY, (Fingerprint)));
+        FUI->FatalError(E, FMTLOAD(HOSTKEY, (FingerprintSHA256)));
       }
       __finally
       {
@@ -2423,7 +2440,7 @@ void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
     }
   }
 
-  Configuration->RememberLastFingerprint(FSessionData->SiteKey, SshFingerprintType, Fingerprint);
+  Configuration->RememberLastFingerprint(FSessionData->SiteKey, SshFingerprintType, FingerprintSHA256);
 }
 //---------------------------------------------------------------------------
 bool __fastcall TSecureShell::HaveHostKey(UnicodeString Host, int Port, const UnicodeString KeyType)

+ 3 - 2
source/core/SecureShell.h

@@ -156,8 +156,9 @@ public:
   void __fastcall FromBackend(bool IsStdErr, const unsigned char * Data, int Length);
   void __fastcall CWrite(const char * Data, int Length);
   const UnicodeString & __fastcall GetStdError();
-  void __fastcall VerifyHostKey(UnicodeString Host, int Port,
-    const UnicodeString KeyType, UnicodeString KeyStr, UnicodeString Fingerprint);
+  void __fastcall VerifyHostKey(
+    const UnicodeString & Host, int Port, const UnicodeString & KeyType, const UnicodeString & KeyStr,
+    const UnicodeString & Fingerprint);
   bool __fastcall HaveHostKey(UnicodeString Host, int Port, const UnicodeString KeyType);
   void __fastcall AskAlg(UnicodeString AlgType, UnicodeString AlgName);
   void __fastcall DisplayBanner(const UnicodeString & Banner);

+ 25 - 7
source/putty/sshpubk.c

@@ -1327,6 +1327,20 @@ void base64_encode(FILE *fp, const unsigned char *data, int datalen, int cpl)
     fputc('\n', fp);
 }
 
+void base64_encode_buf(const unsigned char *data, int datalen, unsigned char *out)
+{
+    int n;
+
+    while (datalen > 0) {
+	n = (datalen < 3 ? datalen : 3);
+	base64_encode_atom(data, n, out);
+	data += n;
+	out += 4;
+	datalen -= n;
+    }
+    *out = 0;
+}
+
 int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key,
 		      char *passphrase)
 {
@@ -1601,8 +1615,9 @@ void ssh2_write_pubkey(FILE *fp, const char *comment,
  */
 char *ssh2_fingerprint_blob(const void *blob, int bloblen)
 {
-    unsigned char digest[16];
-    char fingerprint_str[16*3];
+    unsigned char digest[32];
+    char fingerprint_str_md5[16*3];
+    char fingerprint_str_sha256[45]; /* ceil(32/3)*4+1 */
     const char *algstr;
     int alglen;
     const struct ssh_signkey *alg;
@@ -1613,7 +1628,10 @@ char *ssh2_fingerprint_blob(const void *blob, int bloblen)
      */
     MD5Simple(blob, bloblen, digest);
     for (i = 0; i < 16; i++)
-        sprintf(fingerprint_str + i*3, "%02x%s", digest[i], i==15 ? "" : ":");
+        sprintf(fingerprint_str_md5 + i*3, "%02x%s", digest[i], i==15 ? "" : ":");
+
+    SHA256_Simple(blob, bloblen, digest);
+    base64_encode_buf(digest, 32, fingerprint_str_sha256);
 
     /*
      * Identify the key algorithm, if possible.
@@ -1629,17 +1647,17 @@ char *ssh2_fingerprint_blob(const void *blob, int bloblen)
         alg = find_pubkey_alg_len(alglen, algstr);
         if (alg) {
             int bits = alg->pubkey_bits(alg, blob, bloblen);
-            return dupprintf("%.*s %d %s", alglen, algstr,
-                             bits, fingerprint_str);
+            return dupprintf("%.*s %d %s %s", alglen, algstr,
+                             bits, fingerprint_str_md5, fingerprint_str_sha256);
         } else {
-            return dupprintf("%.*s %s", alglen, algstr, fingerprint_str);
+            return dupprintf("%.*s %s %s", alglen, algstr, fingerprint_str_md5, fingerprint_str_sha256);
         }
     } else {
         /*
          * No algorithm available (which means a seriously confused
          * key blob, but there we go). Return only the hash.
          */
-        return dupstr(fingerprint_str);
+        return dupprintf("%s %s", fingerprint_str_md5, fingerprint_str_sha256);
     }
 }
 

+ 3 - 2
source/resource/TextsCore.h

@@ -1,8 +1,8 @@
 #ifndef TextsCoreH
 #define TextsCoreH
 
-#define UNKNOWN_KEY3    1
-#define DIFFERENT_KEY4  2
+#define UNKNOWN_KEY4    1
+#define DIFFERENT_KEY5  2
 #define OLD_KEY         3
 
 #define SCRIPT_HELP_HELP        4
@@ -473,6 +473,7 @@
 #define PASTE_KEY_BUTTON        558
 #define SCRIPT_CP_DESC          559
 #define TIME_UNKNOWN            560
+#define KEY_DETAILS             561
 
 #define CORE_VARIABLE_STRINGS   600
 #define PUTTY_BASED_ON          601

+ 1 - 0
source/resource/TextsCore1.rc

@@ -442,6 +442,7 @@ BEGIN
   PASTE_KEY_BUTTON, "&Paste key"
   SCRIPT_CP_DESC, "Duplicates remote file"
   TIME_UNKNOWN, "Unknown"
+  KEY_DETAILS, "    Algorithm:\t%s\n    SHA-256:\t%s\n    MD5:\t%s"
 
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"

+ 6 - 4
source/resource/TextsCore2.rc

@@ -2,20 +2,21 @@
 
 STRINGTABLE
 BEGIN
-  UNKNOWN_KEY3,
+  UNKNOWN_KEY4,
     "**Continue connecting to an unknown server and "
     "add its host key to a cache?**\n"
     "\n"
     "The server's host key was not found in the cache. You have no guarantee "
     "that the server is the computer you think it is.\n"
     "\n"
-    "The server's %s key fingerprint is:\n"
+    "The server's %s key details are:\n"
+    "\n"
     "%s\n"
     "\n"
     "If you trust this host, press Yes. "
     "To connect without adding host key to the cache, press No. "
     "To abandon the connection press Cancel."
-  DIFFERENT_KEY4,
+  DIFFERENT_KEY5,
     "**WARNING - POTENTIAL SECURITY BREACH!**\n"
     "\n"
     "The server's host key does not match the one WinSCP has "
@@ -25,7 +26,8 @@ BEGIN
     "or you have actually connected to another computer pretending "
     "to be the server.\n"
     "\n"
-    "The new %s key fingerprint is:\n"
+    "The new %s key details are:\n"
+    "\n"
     "%s\n"
     "\n"
     "If you were expecting this change, trust the new key and "