Browse Source

Bug 1842: Support SHA-256 fingerprints of TLS/SSL certificates

https://winscp.net/tracker/1842

Source commit: 66b808783a0b67b0277fff65af905cb132573175
Martin Prikryl 5 years ago
parent
commit
1fe119535a

+ 1 - 1
dotnet/SessionOptions.cs

@@ -504,7 +504,7 @@ namespace WinSCP
         private const string _sshHostKeyPattern = @"((ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-sha2-nistp(256|384|521))( |-))?(\d+ )?(([0-9a-fA-F]{2}(:|-)){15}[0-9a-fA-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-fA-F]{2}[:\-]){19}[0-9a-fA-F]{2}";
+        private const string _tlsCertificatePattern = @"((([0-9a-fA-F]{2}[:\-]){31})|(([0-9a-fA-F]{2}[:\-]){19}))[0-9a-fA-F]{2}";
         private static readonly Regex _tlsCertificateRegex =
             new Regex(string.Format(CultureInfo.InvariantCulture, _listPattern, _tlsCertificatePattern));
     }

+ 5 - 3
libs/neon/src/ne_openssl.c

@@ -1150,18 +1150,20 @@ char *ne_ssl_cert_export(const ne_ssl_certificate *cert)
 # error SHA digest length is not 20 bytes
 #endif
 
-int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest)
+int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest, int sha256) // WINSCP
 {
     unsigned char sha1[EVP_MAX_MD_SIZE];
     unsigned int len, j;
     char *p;
+    const EVP_MD *type = sha256 ? EVP_sha256() : EVP_sha1(); // WINSCP
+    unsigned int type_len = sha256 ? 32 : 20;
 
-    if (!X509_digest(cert->subject, EVP_sha1(), sha1, &len) || len != 20) {
+    if (!X509_digest(cert->subject, type, sha1, &len) || len != type_len) { // WINSCP
         ERR_clear_error();
         return -1;
     }
     
-    for (j = 0, p = digest; j < 20; j++) {
+    for (j = 0, p = digest; j < type_len; j++) {
         *p++ = NE_HEX2ASC((sha1[j] >> 4) & 0x0f);
         *p++ = NE_HEX2ASC(sha1[j] & 0x0f);
         *p++ = ':';

+ 2 - 2
libs/neon/src/ne_ssl.h

@@ -84,14 +84,14 @@ const ne_ssl_dname *ne_ssl_cert_issuer(const ne_ssl_certificate *cert);
 /* Returns the distinguished name of the certificate subject. */
 const ne_ssl_dname *ne_ssl_cert_subject(const ne_ssl_certificate *cert);
 
-#define NE_SSL_DIGESTLEN (60)
+#define NE_SSL_DIGESTLEN (96) // WINSCP for SHA-256
 
 /* Calculate the certificate digest ("fingerprint") and format it as a
  * NUL-terminated hex string in 'digest', of the form "aa:bb:...:ff".
  * Returns zero on success or non-zero if there was an internal error
  * whilst calculating the digest.  'digest' must be at least 
  * NE_SSL_DIGESTLEN bytes in length. */
-int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest);
+int ne_ssl_cert_digest(const ne_ssl_certificate *cert, char *digest, int sha256); // WINSCP
 
 /* Copy the validity times for the certificate 'cert' into 'from' and
  * 'until' (either may be NULL).  If the time cannot be represented by

+ 10 - 6
source/core/FtpFileSystem.cpp

@@ -3874,8 +3874,10 @@ bool __fastcall TFTPFileSystem::HandleAsynchRequestVerifyCertificate(
   }
   else
   {
-    FSessionInfo.CertificateFingerprint =
-      BytesToHex(RawByteString((const char*)Data.Hash, Data.HashLen), false, L':');
+    FSessionInfo.CertificateFingerprintSHA1 =
+      BytesToHex(RawByteString((const char*)Data.HashSha1, Data.HashSha1Len), false, L':');
+    FSessionInfo.CertificateFingerprintSHA256 =
+      BytesToHex(RawByteString((const char*)Data.HashSha256, Data.HashSha256Len), false, L':');
 
     if (FTerminal->SessionData->FingerprintScan)
     {
@@ -3884,7 +3886,7 @@ bool __fastcall TFTPFileSystem::HandleAsynchRequestVerifyCertificate(
     else
     {
       UnicodeString CertificateSubject = Data.Subject.Organization;
-      FTerminal->LogEvent(FORMAT(L"Verifying certificate for \"%s\" with fingerprint %s and %d failures", (CertificateSubject, FSessionInfo.CertificateFingerprint, Data.VerificationResult)));
+      FTerminal->LogEvent(FORMAT(L"Verifying certificate for \"%s\" with fingerprint %s and %d failures", (CertificateSubject, FSessionInfo.CertificateFingerprintSHA256, Data.VerificationResult)));
 
       bool Trusted = false;
       bool TryWindowsSystemCertificateStore = false;
@@ -3978,7 +3980,8 @@ bool __fastcall TFTPFileSystem::HandleAsynchRequestVerifyCertificate(
       if (!VerificationResult)
       {
         if (FTerminal->VerifyCertificate(FtpsCertificateStorageKey, FTerminal->SessionData->SiteKey,
-              FSessionInfo.CertificateFingerprint, CertificateSubject, Data.VerificationResult))
+              FSessionInfo.CertificateFingerprintSHA1, FSessionInfo.CertificateFingerprintSHA256,
+              CertificateSubject, Data.VerificationResult))
         {
           // certificate is trusted, but for not purposes of info dialog
           VerificationResult = true;
@@ -4030,12 +4033,13 @@ bool __fastcall TFTPFileSystem::HandleAsynchRequestVerifyCertificate(
       }
 
       FSessionInfo.Certificate =
-        FMTLOAD(CERT_TEXT, (
+        FMTLOAD(CERT_TEXT2, (
           FormatContact(Data.Issuer),
           FormatContact(Data.Subject),
           FormatValidityTime(Data.ValidFrom),
           FormatValidityTime(Data.ValidUntil),
-          FSessionInfo.CertificateFingerprint,
+          FSessionInfo.CertificateFingerprintSHA256,
+          FSessionInfo.CertificateFingerprintSHA1,
           Summary));
 
       RequestResult = VerificationResult ? 1 : 0;

+ 16 - 6
source/core/NeonIntf.cpp

@@ -434,12 +434,21 @@ void __fastcall UnregisterFromNeonDebug(TTerminal * Terminal)
 void __fastcall RetrieveNeonCertificateData(
   int Failures, const ne_ssl_certificate * Certificate, TNeonCertificateData & Data)
 {
-  char Fingerprint[NE_SSL_DIGESTLEN] = {0};
-  if (ne_ssl_cert_digest(Certificate, Fingerprint) != 0)
+  char Fingerprint[NE_SSL_DIGESTLEN];
+  Fingerprint[0] = '\0';
+  if (ne_ssl_cert_digest(Certificate, Fingerprint, 0) != 0)
   {
     strcpy(Fingerprint, "<unknown>");
   }
-  Data.Fingerprint = StrFromNeon(Fingerprint);
+  Data.FingerprintSHA1 = StrFromNeon(Fingerprint);
+
+  Fingerprint[0] = '\0';
+  if (ne_ssl_cert_digest(Certificate, Fingerprint, 1) != 0)
+  {
+    strcpy(Fingerprint, "<unknown>");
+  }
+  Data.FingerprintSHA256 = StrFromNeon(Fingerprint);
+
   Data.AsciiCert = NeonExportCertificate(Certificate);
 
   char * Subject = ne_ssl_readable_dname(ne_ssl_cert_subject(Certificate));
@@ -462,7 +471,7 @@ UnicodeString __fastcall CertificateVerificationMessage(const TNeonCertificateDa
 {
   return
     FORMAT(L"Verifying certificate for \"%s\" with fingerprint %s and %2.2X failures",
-           (Data.Subject, Data.Fingerprint, Data.Failures));
+           (Data.Subject, Data.FingerprintSHA256, Data.Failures));
 }
 //---------------------------------------------------------------------------
 UnicodeString __fastcall CertificateSummary(const TNeonCertificateData & Data, const UnicodeString & HostName)
@@ -479,12 +488,13 @@ UnicodeString __fastcall CertificateSummary(const TNeonCertificateData & Data, c
 
   UnicodeString ValidityTimeFormat = L"ddddd tt";
   return
-    FMTLOAD(CERT_TEXT, (
+    FMTLOAD(CERT_TEXT2, (
       Data.Issuer + L"\n",
       Data.Subject + L"\n",
       FormatDateTime(ValidityTimeFormat, Data.ValidFrom),
       FormatDateTime(ValidityTimeFormat, Data.ValidUntil),
-      Data.Fingerprint,
+      Data.FingerprintSHA256,
+      Data.FingerprintSHA1,
       Summary));
 }
 //---------------------------------------------------------------------------

+ 2 - 1
source/core/NeonIntf.h

@@ -18,7 +18,8 @@ struct TNeonCertificateData
   TDateTime ValidFrom;
   TDateTime ValidUntil;
 
-  UnicodeString Fingerprint;
+  UnicodeString FingerprintSHA1;
+  UnicodeString FingerprintSHA256;
   AnsiString AsciiCert;
 
   int Failures;

+ 4 - 2
source/core/S3FileSystem.cpp

@@ -202,7 +202,8 @@ int TS3FileSystem::LibS3SslCallback(int Failures, const ne_ssl_certificate_s * C
 // Similar to TWebDAVFileSystem::VerifyCertificate
 bool TS3FileSystem::VerifyCertificate(TNeonCertificateData Data)
 {
-  FSessionInfo.CertificateFingerprint = Data.Fingerprint;
+  FSessionInfo.CertificateFingerprintSHA1 = Data.FingerprintSHA1;
+  FSessionInfo.CertificateFingerprintSHA256 = Data.FingerprintSHA256;
 
   bool Result;
   if (FTerminal->SessionData->FingerprintScan)
@@ -215,7 +216,8 @@ bool TS3FileSystem::VerifyCertificate(TNeonCertificateData Data)
 
     UnicodeString SiteKey = TSessionData::FormatSiteKey(FTerminal->SessionData->HostNameExpanded, FTerminal->SessionData->PortNumber);
     Result =
-      FTerminal->VerifyCertificate(HttpsCertificateStorageKey, SiteKey, Data.Fingerprint, Data.Subject, Data.Failures);
+      FTerminal->VerifyCertificate(
+        HttpsCertificateStorageKey, SiteKey, Data.FingerprintSHA1, Data.FingerprintSHA256, Data.Subject, Data.Failures);
 
     if (Result)
     {

+ 2 - 1
source/core/SessionInfo.h

@@ -26,7 +26,8 @@ struct TSessionInfo
   UnicodeString HostKeyFingerprintSHA256;
   UnicodeString HostKeyFingerprintMD5;
 
-  UnicodeString CertificateFingerprint;
+  UnicodeString CertificateFingerprintSHA1;
+  UnicodeString CertificateFingerprintSHA256;
   UnicodeString Certificate;
   bool CertificateVerifiedManually;
 };

+ 22 - 15
source/core/Terminal.cpp

@@ -1329,8 +1329,9 @@ void __fastcall TTerminal::Open()
         if (SessionData->FingerprintScan && (FFileSystem != NULL) &&
             DebugAlwaysTrue(SessionData->Ftps != ftpsNone))
         {
-          FFingerprintScannedSHA256 = UnicodeString();
-          FFingerprintScannedSHA1 = FFileSystem->GetSessionInfo().CertificateFingerprint;
+          const TSessionInfo & SessionInfo = FFileSystem->GetSessionInfo();
+          FFingerprintScannedSHA256 = SessionInfo.CertificateFingerprintSHA256;
+          FFingerprintScannedSHA1 = SessionInfo.CertificateFingerprintSHA1;
           FFingerprintScannedMD5 = UnicodeString();
         }
         // Particularly to prevent reusing a wrong client certificate passphrase
@@ -4835,9 +4836,9 @@ void __fastcall TTerminal::FillSessionDataForCode(TSessionData * Data)
   {
     Data->HostKey = SessionInfo.HostKeyFingerprintSHA256;
   }
-  else if (SessionInfo.CertificateVerifiedManually && DebugAlwaysTrue(!SessionInfo.CertificateFingerprint.IsEmpty()))
+  else if (SessionInfo.CertificateVerifiedManually && DebugAlwaysTrue(!SessionInfo.CertificateFingerprintSHA256.IsEmpty()))
   {
-    Data->HostKey = SessionInfo.CertificateFingerprint;
+    Data->HostKey = SessionInfo.CertificateFingerprintSHA256;
   }
 }
 //---------------------------------------------------------------------------
@@ -7671,12 +7672,13 @@ static UnicodeString __fastcall FormatCertificateData(const UnicodeString & Fing
 //---------------------------------------------------------------------------
 bool  __fastcall TTerminal::VerifyCertificate(
   const UnicodeString & CertificateStorageKey, const UnicodeString & SiteKey,
-  const UnicodeString & Fingerprint,
+  const UnicodeString & FingerprintSHA1, const UnicodeString & FingerprintSHA256,
   const UnicodeString & CertificateSubject, int Failures)
 {
   bool Result = false;
 
-  UnicodeString CertificateData = FormatCertificateData(Fingerprint, Failures);
+  UnicodeString CertificateDataSHA1 = FormatCertificateData(FingerprintSHA1, Failures);
+  UnicodeString CertificateDataSHA256 = FormatCertificateData(FingerprintSHA256, Failures);
 
   std::unique_ptr<THierarchicalStorage> Storage(Configuration->CreateConfigStorage());
   Storage->AccessMode = smRead;
@@ -7686,13 +7688,14 @@ bool  __fastcall TTerminal::VerifyCertificate(
     if (Storage->ValueExists(SiteKey))
     {
       UnicodeString CachedCertificateData = Storage->ReadString(SiteKey, L"");
-      if (SameChecksum(CertificateData, CachedCertificateData, false))
+      if (SameChecksum(CertificateDataSHA1, CachedCertificateData, false) ||
+          SameChecksum(CertificateDataSHA256, CachedCertificateData, false))
       {
         LogEvent(FORMAT(L"Certificate for \"%s\" matches cached fingerprint and failures", (CertificateSubject)));
         Result = true;
       }
     }
-    else if (Storage->ValueExists(Fingerprint))
+    else if (Storage->ValueExists(FingerprintSHA1) || Storage->ValueExists(FingerprintSHA256))
     {
       LogEvent(FORMAT(L"Certificate for \"%s\" matches legacy cached fingerprint", (CertificateSubject)));
       Result = true;
@@ -7712,7 +7715,8 @@ bool  __fastcall TTerminal::VerifyCertificate(
         Log->Add(llException, Message);
         Result = true;
       }
-      else if (SameChecksum(ExpectedKey, Fingerprint, false))
+      else if (SameChecksum(ExpectedKey, FingerprintSHA1, false) ||
+               SameChecksum(ExpectedKey, FingerprintSHA256, false))
       {
         LogEvent(FORMAT(L"Certificate for \"%s\" matches configured fingerprint", (CertificateSubject)));
         Result = true;
@@ -7727,7 +7731,8 @@ bool __fastcall TTerminal::ConfirmCertificate(
   TSessionInfo & SessionInfo, int Failures, const UnicodeString & CertificateStorageKey, bool CanRemember)
 {
   TClipboardHandler ClipboardHandler;
-  ClipboardHandler.Text = SessionInfo.CertificateFingerprint;
+  ClipboardHandler.Text =
+    FORMAT(L"SHA-256: %s\nSHA-1: %s", (SessionInfo.CertificateFingerprintSHA256, SessionInfo.CertificateFingerprintSHA1));
 
   TQueryButtonAlias Aliases[1];
   Aliases[0].Button = qaRetry;
@@ -7750,7 +7755,8 @@ bool __fastcall TTerminal::ConfirmCertificate(
   {
     case qaYes:
       CacheCertificate(
-        CertificateStorageKey, SessionData->SiteKey, SessionInfo.CertificateFingerprint, Failures);
+        CertificateStorageKey, SessionData->SiteKey,
+        SessionInfo.CertificateFingerprintSHA1, SessionInfo.CertificateFingerprintSHA256, Failures);
       Result = true;
       break;
 
@@ -7773,15 +7779,16 @@ bool __fastcall TTerminal::ConfirmCertificate(
   if (Result && CanRemember)
   {
     Configuration->RememberLastFingerprint(
-      SessionData->SiteKey, TlsFingerprintType, SessionInfo.CertificateFingerprint);
+      SessionData->SiteKey, TlsFingerprintType, SessionInfo.CertificateFingerprintSHA256);
   }
   return Result;
 }
 //---------------------------------------------------------------------------
-void __fastcall TTerminal::CacheCertificate(const UnicodeString & CertificateStorageKey,
-  const UnicodeString & SiteKey, const UnicodeString & Fingerprint, int Failures)
+void __fastcall TTerminal::CacheCertificate(
+  const UnicodeString & CertificateStorageKey, const UnicodeString & SiteKey,
+  const UnicodeString & DebugUsedArg(FingerprintSHA1), const UnicodeString & FingerprintSHA256, int Failures)
 {
-  UnicodeString CertificateData = FormatCertificateData(Fingerprint, Failures);
+  UnicodeString CertificateData = FormatCertificateData(FingerprintSHA256, Failures);
 
   std::unique_ptr<THierarchicalStorage> Storage(Configuration->CreateConfigStorage());
   Storage->AccessMode = smReadWrite;

+ 3 - 2
source/core/Terminal.h

@@ -433,10 +433,11 @@ protected:
   void __fastcall DoEndTransaction(bool Inform);
   bool  __fastcall VerifyCertificate(
     const UnicodeString & CertificateStorageKey, const UnicodeString & SiteKey,
-    const UnicodeString & Fingerprint,
+    const UnicodeString & FingerprintSHA1, const UnicodeString & FingerprintSHA256,
     const UnicodeString & CertificateSubject, int Failures);
   void __fastcall CacheCertificate(const UnicodeString & CertificateStorageKey,
-    const UnicodeString & SiteKey, const UnicodeString & Fingerprint, int Failures);
+    const UnicodeString & SiteKey, const UnicodeString & FingerprintSHA1, const UnicodeString & FingerprintSHA256,
+    int Failures);
   bool __fastcall ConfirmCertificate(
     TSessionInfo & SessionInfo, int Failures, const UnicodeString & CertificateStorageKey, bool CanRemember);
   void __fastcall CollectTlsUsage(const UnicodeString & TlsVersionStr);

+ 4 - 2
source/core/WebDAVFileSystem.cpp

@@ -1723,7 +1723,8 @@ void __fastcall TWebDAVFileSystem::Sink(
 // Similar to TS3FileSystem::VerifyCertificate
 bool TWebDAVFileSystem::VerifyCertificate(TNeonCertificateData Data, bool Aux)
 {
-  FSessionInfo.CertificateFingerprint = Data.Fingerprint;
+  FSessionInfo.CertificateFingerprintSHA1 = Data.FingerprintSHA1;
+  FSessionInfo.CertificateFingerprintSHA256 = Data.FingerprintSHA256;
 
   bool Result;
   if (FTerminal->SessionData->FingerprintScan)
@@ -1736,7 +1737,8 @@ bool TWebDAVFileSystem::VerifyCertificate(TNeonCertificateData Data, bool Aux)
 
     UnicodeString SiteKey = TSessionData::FormatSiteKey(FHostName, FPortNumber);
     Result =
-      FTerminal->VerifyCertificate(HttpsCertificateStorageKey, SiteKey, Data.Fingerprint, Data.Subject, Data.Failures);
+      FTerminal->VerifyCertificate(
+        HttpsCertificateStorageKey, SiteKey, Data.FingerprintSHA1, Data.FingerprintSHA256, Data.Subject, Data.Failures);
 
     if (Result)
     {

+ 7 - 2
source/filezilla/AsyncSslSocketLayer.cpp

@@ -1566,8 +1566,13 @@ BOOL CAsyncSslSocketLayer::GetPeerCertificateData(t_SslCertData &SslCertData, LP
     BIO_vfree(subjectAltNameBio);
   }
 
-  unsigned int length = 20;
-  X509_digest(pX509, EVP_sha1(), SslCertData.hash, &length);
+  unsigned int length;
+  length = sizeof(SslCertData.hashSha1);
+  X509_digest(pX509, EVP_sha1(), SslCertData.hashSha1, &length);
+  DebugAssert(length == sizeof(SslCertData.hashSha1));
+  length = sizeof(SslCertData.hashSha256);
+  X509_digest(pX509, EVP_sha256(), SslCertData.hashSha256, &length);
+  DebugAssert(length == sizeof(SslCertData.hashSha256));
 
   // Inspired by ne_ssl_cert_export()
   // Find the length of the DER encoding.

+ 2 - 1
source/filezilla/AsyncSslSocketLayer.h

@@ -104,7 +104,8 @@ struct t_SslCertData
 
   TCHAR subjectAltName[10240];
 
-  unsigned char hash[20];
+  unsigned char hashSha1[20];
+  unsigned char hashSha256[32];
 
   unsigned char * certificate;
   size_t certificateLen;

+ 4 - 1
source/filezilla/FileZillaIntf.cpp

@@ -371,7 +371,10 @@ bool __fastcall TFileZillaIntf::HandleMessage(WPARAM wParam, LPARAM lParam)
           CopyValidityTime(Data.ValidFrom, AData->pCertData->validFrom);
           CopyValidityTime(Data.ValidUntil, AData->pCertData->validUntil);
           Data.SubjectAltName = AData->pCertData->subjectAltName;
-          Data.Hash = AData->pCertData->hash;
+          Data.HashSha1 = AData->pCertData->hashSha1;
+          DebugAssert(Data.HashSha1Len == sizeof(AData->pCertData->hashSha1));
+          Data.HashSha256 = AData->pCertData->hashSha256;
+          DebugAssert(Data.HashSha256Len == sizeof(AData->pCertData->hashSha256));
           Data.Certificate = AData->pCertData->certificate;
           Data.CertificateLen = AData->pCertData->certificateLen;
           Data.VerificationResult = AData->pCertData->verificationResult;

+ 4 - 2
source/filezilla/FileZillaIntf.h

@@ -72,8 +72,10 @@ struct TFtpsCertificateData
 
   const wchar_t * SubjectAltName;
 
-  const unsigned char * Hash;
-  static const size_t HashLen = 20;
+  const unsigned char * HashSha1;
+  static const size_t HashSha1Len = 20;
+  const unsigned char * HashSha256;
+  static const size_t HashSha256Len = 32;
 
   const unsigned char * Certificate;
   size_t CertificateLen;

+ 6 - 4
source/forms/FileSystemInfo.cpp

@@ -46,7 +46,8 @@ __fastcall TFileSystemInfoDialog::TFileSystemInfoDialog(TComponent * AOwner,
   ReadOnlyControl(HostKeyAlgorithmEdit);
   ReadOnlyControl(HostKeyFingerprintSHA256Edit);
   ReadOnlyControl(HostKeyFingerprintMD5Edit);
-  ReadOnlyControl(CertificateFingerprintEdit);
+  ReadOnlyControl(CertificateFingerprintSha256Edit);
+  ReadOnlyControl(CertificateFingerprintSha1Edit);
   ReadOnlyControl(InfoMemo);
 }
 //---------------------------------------------------------------------
@@ -113,7 +114,8 @@ void __fastcall TFileSystemInfoDialog::Feed(TFeedFileSystemData AddItem)
 
   AddItem(HostKeyFingerprintSHA256Edit, 0, FSessionInfo.HostKeyFingerprintSHA256);
   AddItem(HostKeyFingerprintMD5Edit, 0, FSessionInfo.HostKeyFingerprintMD5);
-  AddItem(CertificateFingerprintEdit, 0, FSessionInfo.CertificateFingerprint);
+  AddItem(CertificateFingerprintSha256Edit, 0, FSessionInfo.CertificateFingerprintSHA256);
+  AddItem(CertificateFingerprintSha1Edit, 0, FSessionInfo.CertificateFingerprintSHA1);
 
   AddItem(ProtocolView, FSINFO_MODE_CHANGING, CapabilityStr(fcModeChanging));
   AddItem(ProtocolView, FSINFO_OWNER_GROUP_CHANGING, CapabilityStr(fcGroupChanging));
@@ -164,11 +166,11 @@ void __fastcall TFileSystemInfoDialog::ControlsAddItem(TControl * Control,
     HostKeyAlgorithmEdit->Text = FORMAT(L"%s %s", (Alg1, Alg2));
     DebugNotNull(dynamic_cast<TEdit *>(Control))->Text = Value;
   }
-  else if (Control == CertificateFingerprintEdit)
+  else if ((Control == CertificateFingerprintSha256Edit) || (Control == CertificateFingerprintSha1Edit))
   {
     EnableControl(CertificateGroup, !Value.IsEmpty());
     CertificateGroup->Visible = !Value.IsEmpty();
-    CertificateFingerprintEdit->Text = Value;
+    DebugNotNull(dynamic_cast<TEdit *>(Control))->Text = Value;
   }
   else if (Control == InfoMemo)
   {

+ 48 - 17
source/forms/FileSystemInfo.dfm

@@ -67,7 +67,7 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
           351
           87)
         object Label2: TLabel
-          Left = 7
+          Left = 10
           Top = 18
           Width = 49
           Height = 13
@@ -75,7 +75,7 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
           FocusControl = HostKeyAlgorithmEdit
         end
         object Label3: TLabel
-          Left = 7
+          Left = 10
           Top = 41
           Width = 46
           Height = 13
@@ -83,7 +83,7 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
           FocusControl = HostKeyFingerprintSHA256Edit
         end
         object Label4: TLabel
-          Left = 7
+          Left = 10
           Top = 64
           Width = 25
           Height = 13
@@ -91,9 +91,9 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
           FocusControl = HostKeyFingerprintMD5Edit
         end
         object HostKeyFingerprintSHA256Edit: TEdit
-          Left = 62
+          Left = 65
           Top = 41
-          Width = 282
+          Width = 279
           Height = 17
           TabStop = False
           Anchors = [akLeft, akTop, akRight]
@@ -106,9 +106,9 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
           OnContextPopup = HostKeyFingerprintSHA256EditContextPopup
         end
         object HostKeyAlgorithmEdit: TEdit
-          Left = 62
+          Left = 65
           Top = 18
-          Width = 282
+          Width = 279
           Height = 17
           TabStop = False
           Anchors = [akLeft, akTop, akRight]
@@ -119,9 +119,9 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
           Text = 'HostKeyAlgorithmEdit'
         end
         object HostKeyFingerprintMD5Edit: TEdit
-          Left = 62
+          Left = 65
           Top = 64
-          Width = 282
+          Width = 279
           Height = 17
           TabStop = False
           Anchors = [akLeft, akTop, akRight]
@@ -164,17 +164,33 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
         Left = 6
         Top = 294
         Width = 351
-        Height = 72
+        Height = 104
         Anchors = [akLeft, akRight, akBottom]
         Caption = 'Certificate fingerprint'
         TabOrder = 2
         DesignSize = (
           351
-          72)
-        object CertificateFingerprintEdit: TEdit
+          104)
+        object Label5: TLabel
           Left = 10
-          Top = 18
-          Width = 334
+          Top = 20
+          Width = 46
+          Height = 13
+          Caption = 'SHA-256:'
+          FocusControl = CertificateFingerprintSha256Edit
+        end
+        object Label6: TLabel
+          Left = 10
+          Top = 43
+          Width = 34
+          Height = 13
+          Caption = 'SHA-1:'
+          FocusControl = CertificateFingerprintSha1Edit
+        end
+        object CertificateFingerprintSha256Edit: TEdit
+          Left = 65
+          Top = 20
+          Width = 279
           Height = 17
           TabStop = False
           Anchors = [akLeft, akTop, akRight]
@@ -182,17 +198,32 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
           Color = clBtnFace
           ReadOnly = True
           TabOrder = 0
-          Text = 'CertificateFingerprintEdit'
+          Text = 'CertificateFingerprintSha256Edit'
         end
         object CertificateViewButton: TButton
           Left = 10
-          Top = 37
+          Top = 66
           Width = 121
           Height = 25
           Caption = '&Full certificate'
-          TabOrder = 1
+          TabOrder = 2
           OnClick = CertificateViewButtonClick
         end
+        object CertificateFingerprintSha1Edit: TEdit
+          Left = 65
+          Top = 43
+          Width = 279
+          Height = 17
+          TabStop = False
+          Anchors = [akLeft, akTop, akRight]
+          BorderStyle = bsNone
+          Color = clBtnFace
+          PopupMenu = FingerprintPopupMenu
+          ReadOnly = True
+          TabOrder = 1
+          Text = 'CertificateFingerprintSha1Edit'
+          OnContextPopup = HostKeyFingerprintSHA256EditContextPopup
+        end
       end
     end
     object CapabilitiesSheet: TTabSheet

+ 4 - 1
source/forms/FileSystemInfo.h

@@ -39,7 +39,7 @@ __published:
   TEdit *SpaceAvailablePathEdit;
   TButton *SpaceAvailableButton;
   TGroupBox *CertificateGroup;
-  TEdit *CertificateFingerprintEdit;
+  TEdit *CertificateFingerprintSha256Edit;
   TButton *CertificateViewButton;
   TLabel *Label2;
   TEdit *HostKeyAlgorithmEdit;
@@ -51,6 +51,9 @@ __published:
   TEditCopy *EditCopyAction;
   TEditSelectAll *EditSelectAllAction;
   TMenuItem *Copy1;
+  TLabel *Label5;
+  TEdit *CertificateFingerprintSha1Edit;
+  TLabel *Label6;
   void __fastcall HelpButtonClick(TObject *Sender);
   void __fastcall ClipboardButtonClick(TObject *Sender);
   void __fastcall CopyClick(TObject *Sender);

+ 7 - 0
source/forms/MessageDlg.cpp

@@ -17,6 +17,7 @@
 #include <PasTools.hpp>
 #include <Math.hpp>
 #include <WebBrowserEx.hpp>
+#include <RegularExpressions.hpp>
 #include <Setup.h>
 #include <WinApi.h>
 #include "MessageDlg.h"
@@ -997,6 +998,12 @@ TForm * __fastcall TMessageForm::Create(const UnicodeString & Msg,
   }
 
   int MaxTextWidth = ScaleByTextHeightRunTime(Result, mcMaxDialogWidth);
+  // If the message contains SHA-256 hex fingerprint (CERT_TEXT2 on TLS/SSL certificate verification dialog),
+  // allow wider box to fit it
+  if (TRegEx::IsMatch(Msg, L"([0-9a-fA-F]{2}[:\-]){31}[0-9a-fA-F]{2}"))
+  {
+    MaxTextWidth = MaxTextWidth * 3 / 2;
+  }
   // if the dialog would be wide anyway (overwrite confirmation on Windows XP),
   // to fit the buttons, do not restrict the text
   if (MaxTextWidth < ButtonGroupWidth - IconWidth)

+ 1 - 1
source/resource/TextsCore.h

@@ -314,7 +314,7 @@
 #define VERIFY_CERT_PROMPT3     346
 #define VERIFY_CERT_CONTACT     347
 #define VERIFY_CERT_CONTACT_LIST 348
-#define CERT_TEXT               349
+#define CERT_TEXT2              349
 #define CERTIFICATE_PASSPHRASE_PROMPT 350
 #define CERTIFICATE_PASSPHRASE_TITLE 351
 #define KEY_TYPE_CONVERT3       352

+ 1 - 1
source/resource/TextsCore1.rc

@@ -285,7 +285,7 @@ BEGIN
   VERIFY_CERT_PROMPT3, "**The server's certificate is not known. You have no guarantee that the server is the computer you think it is.**\n\nServer's certificate details follow:\n\n%s\n\nIf you trust this certificate, press Yes. To connect without storing certificate, press No. To abandon the connection press Cancel.\n\nContinue connecting and store the certificate?"
   VERIFY_CERT_CONTACT, "- Organization: %s\n|- Location: %s\n|- Other: %s\n"
   VERIFY_CERT_CONTACT_LIST, "%s, %s"
-  CERT_TEXT, "Issuer:\n%s\nSubject:\n%s\nValid: %s - %s\n\nFingerprint (SHA-1): %s\n\nSummary: %s"
+  CERT_TEXT2, "Issuer:\n%s\nSubject:\n%s\nValid: %s - %s\n\nFingerprint:\n- SHA-256: %s\n- SHA-1: %s\n\nSummary: %s"
   CERTIFICATE_PASSPHRASE_PROMPT, "&Passphrase for client certificate:"
   CERTIFICATE_PASSPHRASE_TITLE, "Client certificate passphrase"
   KEY_TYPE_CONVERT3, "**Do you want to convert this %s private key to PuTTY format?**\n\n%s"