浏览代码

Change: Session.ScanFingerprint has mandatory algorithm parameter. + Displaying both SHA-256 and MD5 fingerprint of SSH host key on Server and protocol information dialog

Source commit: 11b4510273b16007335d41eefede396e3ebf0d0f
Martin Prikryl 8 年之前
父节点
当前提交
9d0b0e3fc6

+ 33 - 4
dotnet/Session.cs

@@ -336,10 +336,16 @@ namespace WinSCP
             return s;
         }
 
-        public string ScanFingerprint(SessionOptions sessionOptions)
+        public string ScanFingerprint(SessionOptions sessionOptions, string algorithm)
         {
             using (Logger.CreateCallstackAndLock())
             {
+                string normalizeAlgorithm = NormalizeIdent(algorithm);
+                if (string.IsNullOrEmpty(normalizeAlgorithm))
+                {
+                    throw Logger.WriteException(new ArgumentException("Algorithm cannot be empty", "algorithm"));
+                }
+
                 string result;
 
                 CheckNotDisposed();
@@ -372,14 +378,32 @@ namespace WinSCP
                         CheckForTimeout();
                     }
 
-                    string output = string.Join(Environment.NewLine, new List<string>(Output).ToArray());
                     if (_process.ExitCode == 0)
                     {
-                        result = output;
+                        result = null;
+                        foreach (string s in Output)
+                        {
+                            int p = s.IndexOf(":", StringComparison.Ordinal);
+                            if (p < 0)
+                            {
+                                throw Logger.WriteException(new SessionLocalException(this, string.Format(CultureInfo.CurrentCulture, "Unexpected fingerprint scan result line '{0}'", s)));
+                            }
+                            string a = NormalizeIdent(s.Substring(0, p).Trim());
+                            if (normalizeAlgorithm.Equals(a, StringComparison.OrdinalIgnoreCase))
+                            {
+                                result = s.Substring(p + 1).Trim();
+                                break;
+                            }
+                        }
+
+                        if (result == null)
+                        {
+                            throw Logger.WriteException(new SessionLocalException(this, string.Format(CultureInfo.CurrentCulture, "Fingerprint for algorithm {0} not supported", algorithm)));
+                        }
                     }
                     else
                     {
-                        throw Logger.WriteException(new SessionRemoteException(this, output));
+                        throw Logger.WriteException(new SessionRemoteException(this, ListToString(Output)));
                     }
                 }
                 catch (Exception e)
@@ -396,6 +420,11 @@ namespace WinSCP
             }
         }
 
+        private static string NormalizeIdent(string algorithm)
+        {
+            return algorithm.Replace("-", string.Empty);
+        }
+
         public void Close()
         {
             using (Logger.CreateCallstackAndLock())

+ 1 - 1
source/core/FileMasks.cpp

@@ -1123,7 +1123,7 @@ __fastcall TCustomCommandData::TCustomCommandData()
 __fastcall TCustomCommandData::TCustomCommandData(TTerminal * Terminal)
 {
   Init(Terminal->SessionData, Terminal->UserName, Terminal->Password,
-    Terminal->GetSessionInfo().HostKeyFingerprint);
+    Terminal->GetSessionInfo().HostKeyFingerprintSHA256);
 }
 //---------------------------------------------------------------------------
 __fastcall TCustomCommandData::TCustomCommandData(

+ 5 - 3
source/core/SecureShell.cpp

@@ -125,9 +125,10 @@ const TSessionInfo & __fastcall TSecureShell::GetSessionInfo()
   return FSessionInfo;
 }
 //---------------------------------------------------------------------
-UnicodeString __fastcall TSecureShell::GetHostKeyFingerprint()
+void __fastcall TSecureShell::GetHostKeyFingerprint(UnicodeString & SHA256, UnicodeString & MD5)
 {
-  return FSessionInfo.HostKeyFingerprint;
+  SHA256 = FSessionInfo.HostKeyFingerprintSHA256;
+  MD5 = FSessionInfo.HostKeyFingerprintMD5;
 }
 //---------------------------------------------------------------------
 Conf * __fastcall TSecureShell::StoreToConfig(TSessionData * Data, bool Simple)
@@ -2241,7 +2242,8 @@ void __fastcall TSecureShell::VerifyHostKey(
   UnicodeString NormalizedFingerprintMD5 = NormalizeFingerprint(FingerprintMD5);
   UnicodeString NormalizedFingerprintSHA256 = NormalizeFingerprint(FingerprintSHA256);
 
-  FSessionInfo.HostKeyFingerprint = FingerprintSHA256;
+  FSessionInfo.HostKeyFingerprintSHA256 = FingerprintSHA256;
+  FSessionInfo.HostKeyFingerprintMD5 = FingerprintMD5;
 
   if (FSessionData->FingerprintScan)
   {

+ 1 - 1
source/core/SecureShell.h

@@ -133,7 +133,7 @@ public:
   void __fastcall SendNull();
 
   const TSessionInfo & __fastcall GetSessionInfo();
-  UnicodeString __fastcall GetHostKeyFingerprint();
+  void __fastcall GetHostKeyFingerprint(UnicodeString & SHA256, UnicodeString & MD5);
   bool __fastcall SshFallbackCmd() const;
   unsigned long __fastcall MaxPacketSize();
   void __fastcall ClearStdError();

+ 2 - 1
source/core/SessionInfo.h

@@ -23,7 +23,8 @@ struct TSessionInfo
 
   UnicodeString SshVersionString;
   UnicodeString SshImplementation;
-  UnicodeString HostKeyFingerprint;
+  UnicodeString HostKeyFingerprintSHA256;
+  UnicodeString HostKeyFingerprintMD5;
 
   UnicodeString CertificateFingerprint;
   UnicodeString Certificate;

+ 13 - 9
source/core/Terminal.cpp

@@ -1228,25 +1228,28 @@ void __fastcall TTerminal::ResetConnection()
   // as they can still be referenced in the GUI atm
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall TTerminal::FingerprintScan()
+void __fastcall TTerminal::FingerprintScan(UnicodeString & SHA256, UnicodeString & MD5)
 {
   SessionData->FingerprintScan = true;
   try
   {
     Open();
     // we should never get here
+    DebugFail();
     Abort();
   }
   catch (...)
   {
-    if (!FFingerprintScanned.IsEmpty())
+    if (!FFingerprintScannedSHA256.IsEmpty() || !FFingerprintScannedMD5.IsEmpty())
     {
-      return FFingerprintScanned;
+      SHA256 = FFingerprintScannedSHA256;
+      MD5 = FFingerprintScannedMD5;
+    }
+    else
+    {
+      throw;
     }
-    throw;
   }
-  DebugFail();
-  return UnicodeString();
 }
 //---------------------------------------------------------------------------
 void __fastcall TTerminal::Open()
@@ -1334,7 +1337,7 @@ void __fastcall TTerminal::Open()
                   DebugAssert(!FSecureShell->Active);
                   if (SessionData->FingerprintScan)
                   {
-                    FFingerprintScanned = FSecureShell->GetHostKeyFingerprint();
+                    FSecureShell->GetHostKeyFingerprint(FFingerprintScannedSHA256, FFingerprintScannedMD5);
                   }
                   if (!FSecureShell->Active && !FTunnelError.IsEmpty())
                   {
@@ -1420,7 +1423,8 @@ void __fastcall TTerminal::Open()
         if (SessionData->FingerprintScan && (FFileSystem != NULL) &&
             DebugAlwaysTrue(SessionData->Ftps != ftpsNone))
         {
-          FFingerprintScanned = FFileSystem->GetSessionInfo().CertificateFingerprint;
+          FFingerprintScannedSHA256 = UnicodeString();
+          FFingerprintScannedMD5 = FFileSystem->GetSessionInfo().CertificateFingerprint;
         }
         // Particularly to prevent reusing a wrong client certificate passphrase
         // in the next login attempt
@@ -4755,7 +4759,7 @@ TTerminal * __fastcall TTerminal::CreateSecondarySession(const UnicodeString & N
 void __fastcall TTerminal::FillSessionDataForCode(TSessionData * Data)
 {
   const TSessionInfo & SessionInfo = GetSessionInfo();
-  Data->HostKey = SessionInfo.HostKeyFingerprint;
+  Data->HostKey = SessionInfo.HostKeyFingerprintSHA256;
 }
 //---------------------------------------------------------------------------
 TTerminal * __fastcall TTerminal::GetCommandSession()

+ 3 - 2
source/core/Terminal.h

@@ -210,7 +210,8 @@ private:
   bool FRememberedPasswordTried;
   bool FRememberedTunnelPasswordTried;
   int FNesting;
-  UnicodeString FFingerprintScanned;
+  UnicodeString FFingerprintScannedSHA256;
+  UnicodeString FFingerprintScannedMD5;
   DWORD FLastProgressLogged;
   UnicodeString FDestFileName;
   bool FMultipleDestinationFiles;
@@ -456,7 +457,7 @@ public:
   __fastcall ~TTerminal();
   void __fastcall Open();
   void __fastcall Close();
-  UnicodeString __fastcall FingerprintScan();
+  void __fastcall FingerprintScan(UnicodeString & SHA256, UnicodeString & MD5);
   void __fastcall Reopen(int Params);
   virtual void __fastcall DirectoryModified(const UnicodeString Path, bool SubDirs);
   virtual void __fastcall DirectoryLoaded(TRemoteFileList * FileList);

+ 61 - 10
source/forms/FileSystemInfo.cpp

@@ -11,6 +11,7 @@
 #include "TextsWin.h"
 #include "GUITools.h"
 #include <BaseUtils.hpp>
+#include <StrUtils.hpp>
 //---------------------------------------------------------------------
 #pragma link "HistoryComboBox"
 #ifndef NO_RESOURCES
@@ -44,7 +45,9 @@ __fastcall TFileSystemInfoDialog::TFileSystemInfoDialog(TComponent * AOwner,
 
   CertificateGroup->Top = HostKeyGroup->Top;
 
-  ReadOnlyControl(HostKeyFingerprintEdit);
+  ReadOnlyControl(HostKeyAlgorithmEdit);
+  ReadOnlyControl(HostKeyFingerprintSHA256Edit);
+  ReadOnlyControl(HostKeyFingerprintMD5Edit);
   ReadOnlyControl(CertificateFingerprintEdit);
   ReadOnlyControl(InfoMemo);
 }
@@ -110,7 +113,8 @@ void __fastcall TFileSystemInfoDialog::Feed(TFeedFileSystemData AddItem)
   }
   AddItem(ServerView, FSINFO_COMPRESSION, Str);
 
-  AddItem(HostKeyFingerprintEdit, 0, FSessionInfo.HostKeyFingerprint);
+  AddItem(HostKeyFingerprintSHA256Edit, 0, FSessionInfo.HostKeyFingerprintSHA256);
+  AddItem(HostKeyFingerprintMD5Edit, 0, FSessionInfo.HostKeyFingerprintMD5);
   AddItem(CertificateFingerprintEdit, 0, FSessionInfo.CertificateFingerprint);
 
   AddItem(ProtocolView, FSINFO_MODE_CHANGING, CapabilityStr(fcModeChanging));
@@ -153,11 +157,14 @@ void __fastcall TFileSystemInfoDialog::ControlsAddItem(TControl * Control,
     FLastListItem = 0;
   }
 
-  if (Control == HostKeyFingerprintEdit)
+  if ((Control == HostKeyFingerprintSHA256Edit) || (Control == HostKeyFingerprintMD5Edit))
   {
     EnableControl(HostKeyGroup, !Value.IsEmpty());
     HostKeyGroup->Visible = !Value.IsEmpty();
-    HostKeyFingerprintEdit->Text = Value;
+    UnicodeString Alg1 = CutToChar(Value, L' ', true);
+    UnicodeString Alg2 = CutToChar(Value, L' ', true);
+    HostKeyAlgorithmEdit->Text = FORMAT(L"%s %s", (Alg1, Alg2));
+    DebugNotNull(dynamic_cast<TEdit *>(Control))->Text = Value;
   }
   else if (Control == CertificateFingerprintEdit)
   {
@@ -228,24 +235,43 @@ void __fastcall TFileSystemInfoDialog::ClipboardAddItem(TControl * Control,
 {
   if (Control->Enabled && !Value.IsEmpty())
   {
-    if (FLastFeededControl != Control)
+    TGroupBox * Group = dynamic_cast<TGroupBox *>(Control->Parent);
+    TControl * ControlGroup = (Group != NULL) ? Group : Control;
+    if (FLastFeededControl != ControlGroup)
     {
       if (FLastFeededControl != NULL)
       {
         FClipboard += UnicodeString::StringOfChar(L'-', 60) + L"\r\n";
       }
-      FLastFeededControl = Control;
+
+      if (Group != NULL)
+      {
+        FClipboard += FORMAT(L"%s\r\n", (Group->Caption));
+      }
+
+      FLastFeededControl = ControlGroup;
     }
 
     if (dynamic_cast<TListView *>(Control) == NULL)
     {
-      TGroupBox * Group = dynamic_cast<TGroupBox *>(Control->Parent);
-      DebugAssert(Group != NULL);
-      if ((Value.Length() >= 2) && (Value.SubString(Value.Length() - 1, 2) == L"\r\n"))
+      for (int Index = 0; Index < Control->Parent->ControlCount; Index++)
+      {
+        TLabel * Label = dynamic_cast<TLabel *>(Control->Parent->Controls[Index]);
+        if ((Label != NULL) && (Label->FocusControl == Control))
+        {
+          UnicodeString S = StripHotkey(Label->Caption);
+          if (EndsStr(L":", S))
+          {
+            S.SetLength(S.Length() - 1);
+          }
+          FClipboard += FORMAT(L"%s = ", (S));
+        }
+      }
+      if (EndsStr(L"\r\n", Value))
       {
         Value.SetLength(Value.Length() - 2);
       }
-      FClipboard += FORMAT(L"%s\r\n%s\r\n", (Group->Caption, Value));
+      FClipboard += FORMAT(L"%s\r\n", (Value));
     }
     else
     {
@@ -383,3 +409,28 @@ void __fastcall TFileSystemInfoDialog::SpaceAvailableViewCustomDrawItem(
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TFileSystemInfoDialog::EditCopyActionExecute(TObject * /*Sender*/)
+{
+  TEdit * Edit = HostKeyFingerprintSHA256Edit->Focused() ? HostKeyFingerprintSHA256Edit : HostKeyFingerprintMD5Edit;
+  if ((Edit->SelLength == 0) || (Edit->SelLength == Edit->Text.Length()))
+  {
+    CopyToClipboard(FORMAT(L"%s %s", (HostKeyAlgorithmEdit->Text, Edit->Text)));
+  }
+  else
+  {
+    Edit->CopyToClipboard();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TFileSystemInfoDialog::HostKeyFingerprintSHA256EditContextPopup(
+  TObject * Sender, TPoint & MousePos, bool & Handled)
+{
+  MenuPopup(Sender, MousePos, Handled);
+}
+//---------------------------------------------------------------------------
+void __fastcall TFileSystemInfoDialog::EditCopyActionUpdate(TObject * /*Sender*/)
+{
+  // When noting is selected, we copy whole key, including algorithm, see EditCopyActionExecute
+  EditCopyAction->Enabled = true;
+}
+//---------------------------------------------------------------------------

+ 89 - 9
source/forms/FileSystemInfo.dfm

@@ -57,19 +57,58 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
         324)
       object HostKeyGroup: TGroupBox
         Left = 6
-        Top = 202
+        Top = 201
         Width = 351
-        Height = 41
+        Height = 87
         Anchors = [akLeft, akRight, akBottom]
-        Caption = 'Server host key fingerprint'
+        Caption = 'Server host key fingerprints'
         TabOrder = 1
         DesignSize = (
           351
-          41)
-        object HostKeyFingerprintEdit: TEdit
-          Left = 10
+          87)
+        object Label2: TLabel
+          Left = 7
           Top = 18
-          Width = 334
+          Width = 49
+          Height = 13
+          Caption = 'Algorithm:'
+          FocusControl = HostKeyAlgorithmEdit
+        end
+        object Label3: TLabel
+          Left = 7
+          Top = 41
+          Width = 46
+          Height = 13
+          Caption = 'SHA-256:'
+          FocusControl = HostKeyFingerprintSHA256Edit
+        end
+        object Label4: TLabel
+          Left = 7
+          Top = 64
+          Width = 25
+          Height = 13
+          Caption = 'MD5:'
+          FocusControl = HostKeyFingerprintMD5Edit
+        end
+        object HostKeyFingerprintSHA256Edit: TEdit
+          Left = 62
+          Top = 41
+          Width = 282
+          Height = 17
+          TabStop = False
+          Anchors = [akLeft, akTop, akRight]
+          BorderStyle = bsNone
+          Color = clBtnFace
+          PopupMenu = FingerprintPopupMenu
+          ReadOnly = True
+          TabOrder = 1
+          Text = 'HostKeyFingerprintSHA256Edit'
+          OnContextPopup = HostKeyFingerprintSHA256EditContextPopup
+        end
+        object HostKeyAlgorithmEdit: TEdit
+          Left = 62
+          Top = 18
+          Width = 282
           Height = 17
           TabStop = False
           Anchors = [akLeft, akTop, akRight]
@@ -77,7 +116,22 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
           Color = clBtnFace
           ReadOnly = True
           TabOrder = 0
-          Text = 'HostKeyFingerprintEdit'
+          Text = 'HostKeyAlgorithmEdit'
+        end
+        object HostKeyFingerprintMD5Edit: TEdit
+          Left = 62
+          Top = 64
+          Width = 282
+          Height = 17
+          TabStop = False
+          Anchors = [akLeft, akTop, akRight]
+          BorderStyle = bsNone
+          Color = clBtnFace
+          PopupMenu = FingerprintPopupMenu
+          ReadOnly = True
+          TabOrder = 2
+          Text = 'HostKeyFingerprintMD5Edit'
+          OnContextPopup = HostKeyFingerprintSHA256EditContextPopup
         end
       end
       object ServerView: TListView
@@ -108,7 +162,7 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
       end
       object CertificateGroup: TGroupBox
         Left = 6
-        Top = 246
+        Top = 294
         Width = 351
         Height = 72
         Anchors = [akLeft, akRight, akBottom]
@@ -286,4 +340,30 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
       OnClick = CopyClick
     end
   end
+  object FingerprintPopupMenu: TPopupMenu
+    Left = 240
+    Top = 354
+    object Copy1: TMenuItem
+      Action = EditCopyAction
+    end
+    object TMenuItem
+      Action = EditSelectAllAction
+    end
+  end
+  object FingerprintActionList: TActionList
+    Left = 328
+    Top = 354
+    object EditCopyAction: TEditCopy
+      Category = 'Edit'
+      Caption = '&Copy'
+      ShortCut = 16451
+      OnExecute = EditCopyActionExecute
+      OnUpdate = EditCopyActionUpdate
+    end
+    object EditSelectAllAction: TEditSelectAll
+      Category = 'Edit'
+      Caption = 'Select &All'
+      ShortCut = 16449
+    end
+  end
 end

+ 17 - 1
source/forms/FileSystemInfo.h

@@ -7,6 +7,9 @@
 #include <Vcl.Controls.hpp>
 #include <Vcl.Menus.hpp>
 #include <Vcl.StdCtrls.hpp>
+#include <System.Actions.hpp>
+#include <Vcl.ActnList.hpp>
+#include <Vcl.StdActns.hpp>
 //----------------------------------------------------------------------------
 typedef void __fastcall (__closure *TFeedFileSystemData)
   (TControl * Control, int Label, UnicodeString Value);
@@ -19,7 +22,7 @@ __published:
   TPageControl *PageControl;
   TTabSheet *ProtocolSheet;
   TGroupBox *HostKeyGroup;
-  TEdit *HostKeyFingerprintEdit;
+  TEdit *HostKeyFingerprintSHA256Edit;
   TTabSheet *CapabilitiesSheet;
   TGroupBox *InfoGroup;
   TMemo *InfoMemo;
@@ -36,6 +39,16 @@ __published:
   TGroupBox *CertificateGroup;
   TEdit *CertificateFingerprintEdit;
   TButton *CertificateViewButton;
+  TLabel *Label2;
+  TEdit *HostKeyAlgorithmEdit;
+  TLabel *Label3;
+  TLabel *Label4;
+  TEdit *HostKeyFingerprintMD5Edit;
+  TPopupMenu *FingerprintPopupMenu;
+  TActionList *FingerprintActionList;
+  TEditCopy *EditCopyAction;
+  TEditSelectAll *EditSelectAllAction;
+  TMenuItem *Copy1;
   void __fastcall HelpButtonClick(TObject *Sender);
   void __fastcall ClipboardButtonClick(TObject *Sender);
   void __fastcall CopyClick(TObject *Sender);
@@ -50,6 +63,9 @@ __published:
   void __fastcall CertificateViewButtonClick(TObject *Sender);
   void __fastcall SpaceAvailableViewCustomDrawItem(TCustomListView *Sender, TListItem *Item,
           TCustomDrawState State, bool &DefaultDraw);
+  void __fastcall EditCopyActionExecute(TObject *Sender);
+  void __fastcall HostKeyFingerprintSHA256EditContextPopup(TObject *Sender, TPoint &MousePos, bool &Handled);
+  void __fastcall EditCopyActionUpdate(TObject *Sender);
 public:
   virtual __fastcall TFileSystemInfoDialog(TComponent * AOwner,
     TGetSpaceAvailable OnGetSpaceAvailable);

+ 11 - 1
source/windows/ConsoleRunner.cpp

@@ -2506,7 +2506,17 @@ int __fastcall FingerprintScan(TConsole * Console, TProgramParams * Params)
     }
 
     std::unique_ptr<TTerminal> Terminal(new TTerminal(SessionData.get(), Configuration));
-    ConsolePrintLine(Console, Terminal->FingerprintScan());
+    UnicodeString SHA256;
+    UnicodeString MD5;
+    Terminal->FingerprintScan(SHA256, MD5);
+    if (!SHA256.IsEmpty())
+    {
+      ConsolePrintLine(Console, FORMAT(L"SHA-256: %s", (SHA256)));
+    }
+    if (!MD5.IsEmpty())
+    {
+      ConsolePrintLine(Console, FORMAT(L"MD5:     %s", (MD5)));
+    }
   }
   catch (Exception & E)
   {

+ 5 - 1
source/windows/VCLCommon.cpp

@@ -356,7 +356,11 @@ void __fastcall ReadOnlyControl(TControl * Control, bool ReadOnly)
       Edit->Color = clWindow;
       // not supported atm, we need to persist previous value of WantReturns
       DebugAssert(Memo == NULL);
-      delete Edit->PopupMenu;
+
+      if ((Edit->PopupMenu != NULL) && (Edit->PopupMenu->Owner == Edit))
+      {
+        delete Edit->PopupMenu;
+      }
     }
   }
   else if ((dynamic_cast<TCustomComboBox *>(Control) != NULL) ||