1
0
Эх сурвалжийг харах

Bug 1873: Support for OpenSSH certificates for user authentication

https://winscp.net/tracker/1873

Source commit: fc13a226065092a102456a27242ac69151cb1d15
Martin Prikryl 3 жил өмнө
parent
commit
48106ee1b6

+ 12 - 5
source/core/PuttyIntf.cpp

@@ -259,8 +259,12 @@ static void connection_fatal(Seat * seat, const char * message)
 SeatPromptResult confirm_ssh_host_key(Seat * seat, const char * host, int port, const char * keytype,
   char * keystr, SeatDialogText *, HelpCtx,
   void (*DebugUsedArg(callback))(void *ctx, SeatPromptResult result), void * DebugUsedArg(ctx),
-  char **key_fingerprints)
+  char **key_fingerprints, bool is_certificate)
 {
+  if (DebugAlwaysFalse(is_certificate))
+  {
+    NotImplemented();
+  }
   UnicodeString FingerprintSHA256, FingerprintMD5;
   if (key_fingerprints[SSH_FPTYPE_SHA256] != NULL)
   {
@@ -863,7 +867,8 @@ void FreeKey(TPrivateKey * PrivateKey)
   sfree(Ssh2Key);
 }
 //---------------------------------------------------------------------------
-RawByteString LoadPublicKey(const UnicodeString & FileName, UnicodeString & Algorithm, UnicodeString & Comment)
+RawByteString LoadPublicKey(
+  const UnicodeString & FileName, UnicodeString & Algorithm, UnicodeString & Comment, bool & HasCertificate)
 {
   RawByteString Result;
   UTF8String UtfFileName = UTF8String(FileName);
@@ -880,6 +885,8 @@ RawByteString LoadPublicKey(const UnicodeString & FileName, UnicodeString & Algo
       throw Exception(Error);
     }
     Algorithm = UnicodeString(AnsiString(AlgorithmStr));
+    const ssh_keyalg * KeyAlg = find_pubkey_alg(AlgorithmStr);
+    HasCertificate = (KeyAlg != NULL) && KeyAlg->is_certificate;
     sfree(AlgorithmStr);
     Comment = UnicodeString(AnsiString(CommentStr));
     sfree(CommentStr);
@@ -893,10 +900,10 @@ RawByteString LoadPublicKey(const UnicodeString & FileName, UnicodeString & Algo
   return Result;
 }
 //---------------------------------------------------------------------------
-UnicodeString GetPublicKeyLine(const UnicodeString & FileName, UnicodeString & Comment)
+UnicodeString GetPublicKeyLine(const UnicodeString & FileName, UnicodeString & Comment, bool & HasCertificate)
 {
   UnicodeString Algorithm;
-  RawByteString PublicKey = LoadPublicKey(FileName, Algorithm, Comment);
+  RawByteString PublicKey = LoadPublicKey(FileName, Algorithm, Comment, HasCertificate);
   UnicodeString PublicKeyBase64 = EncodeBase64(PublicKey.c_str(), PublicKey.Length());
   PublicKeyBase64 = ReplaceStr(PublicKeyBase64, L"\r", L"");
   PublicKeyBase64 = ReplaceStr(PublicKeyBase64, L"\n", L"");
@@ -1043,7 +1050,7 @@ UnicodeString __fastcall ParseOpenSshPubLine(const UnicodeString & Line, const s
   {
     try
     {
-      Algorithm = find_pubkey_alg(AlgorithmName);
+      Algorithm = find_pubkey_alg_winscp_host(AlgorithmName);
       if (Algorithm == NULL)
       {
         throw Exception(FORMAT(L"Unknown public key algorithm \"%s\".", (AlgorithmName)));

+ 1 - 1
source/core/PuttyTools.h

@@ -20,7 +20,7 @@ void ChangeKeyComment(TPrivateKey * PrivateKey, const UnicodeString & Comment);
 void SaveKey(TKeyType KeyType, const UnicodeString & FileName,
   const UnicodeString & Passphrase, TPrivateKey * PrivateKey);
 void FreeKey(TPrivateKey * PrivateKey);
-UnicodeString GetPublicKeyLine(const UnicodeString & FileName, UnicodeString & Comment);
+UnicodeString GetPublicKeyLine(const UnicodeString & FileName, UnicodeString & Comment, bool & HasCertificate);
 extern const UnicodeString PuttyKeyExt;
 //---------------------------------------------------------------------------
 bool __fastcall HasGSSAPI(UnicodeString CustomPath);

+ 7 - 3
source/core/SecureShell.cpp

@@ -242,9 +242,13 @@ Conf * __fastcall TSecureShell::StoreToConfig(TSessionData * Data, bool Simple)
   conf_set_filename(conf, CONF_ssh_gss_custom, GssLibCustomFileName);
   filename_free(GssLibCustomFileName);
 
-  Filename * KeyFileFileName = filename_from_str(UTF8String(Data->ResolvePublicKeyFile()).c_str());
-  conf_set_filename(conf, CONF_keyfile, KeyFileFileName);
-  filename_free(KeyFileFileName);
+  Filename * AFileName = filename_from_str(UTF8String(Data->ResolvePublicKeyFile()).c_str());
+  conf_set_filename(conf, CONF_keyfile, AFileName);
+  filename_free(AFileName);
+
+  AFileName = filename_from_str(UTF8String(ExpandEnvironmentVariables(Data->DetachedCertificate)).c_str());
+  conf_set_filename(conf, CONF_detached_cert, AFileName);
+  filename_free(AFileName);
 
   conf_set_bool(conf, CONF_ssh2_des_cbc, Data->Ssh2DES);
   conf_set_bool(conf, CONF_ssh_no_userauth, Data->SshNoUserAuth);

+ 17 - 4
source/core/SessionData.cpp

@@ -191,7 +191,8 @@ void __fastcall TSessionData::DefaultSettings()
     GssLib[Index] = DefaultGssLibList[Index];
   }
   GssLibCustom = L"";
-  PublicKeyFile = L"";
+  PublicKeyFile = EmptyStr;
+  DetachedCertificate = EmptyStr;
   Passphrase = L"";
   FPuttyProtocol = L"";
   TcpNoDelay = false;
@@ -356,6 +357,7 @@ void __fastcall TSessionData::NonPersistant()
   PROPERTY(UserName); \
   PROPERTY_HANDLER(Password, F); \
   PROPERTY(PublicKeyFile); \
+  PROPERTY(DetachedCertificate); \
   PROPERTY_HANDLER(Passphrase, F); \
   PROPERTY(FSProtocol); \
   PROPERTY(Ftps); \
@@ -727,6 +729,7 @@ void __fastcall TSessionData::DoLoad(THierarchicalStorage * Storage, bool PuttyI
   }
   GssLibCustom = Storage->ReadString(L"GSSCustom", GssLibCustom);
   PublicKeyFile = Storage->ReadString(L"PublicKeyFile", PublicKeyFile);
+  DetachedCertificate = Storage->ReadString(L"DetachedCertificate", DetachedCertificate);
   AddressFamily = static_cast<TAddressFamily>
     (Storage->ReadInteger(L"AddressFamily", AddressFamily));
   RekeyData = Storage->ReadString(L"RekeyBytes", RekeyData);
@@ -1046,11 +1049,13 @@ void __fastcall TSessionData::DoSave(THierarchicalStorage * Storage,
     // PuTTY is started in its binary directory to allow relative paths when opening PuTTY's own stored session.
     // To allow relative paths in our sessions, we have to expand them for PuTTY.
     WRITE_DATA_EX(StringRaw, L"PublicKeyFile", PublicKeyFile, ExpandFileName);
+    WRITE_DATA_EX(StringRaw, L"DetachedCertificate", DetachedCertificate, ExpandFileName);
   }
   else
   {
     WRITE_DATA(String, UserName);
     WRITE_DATA(String, PublicKeyFile);
+    WRITE_DATA(String, DetachedCertificate);
     WRITE_DATA(Integer, FSProtocol);
     WRITE_DATA(String, LocalDirectory);
     WRITE_DATA(String, OtherLocalDirectory);
@@ -2553,6 +2558,7 @@ TSessionData * TSessionData::CreateTunnelData(int TunnelLocalPortNumber)
   TunnelData->UserName = TunnelUserName;
   TunnelData->Password = TunnelPassword;
   TunnelData->PublicKeyFile = TunnelPublicKeyFile;
+  TunnelData->DetachedCertificate = EmptyStr;
   TunnelData->Passphrase = TunnelPassphrase;
   UnicodeString AHostName = HostNameExpanded;
   if (IsIPv6Literal(AHostName))
@@ -2599,6 +2605,7 @@ void __fastcall TSessionData::ExpandEnvironmentVariables()
   HostName = HostNameExpanded;
   UserName = UserNameExpanded;
   PublicKeyFile = ::ExpandEnvironmentVariables(PublicKeyFile);
+  DetachedCertificate = ::ExpandEnvironmentVariables(DetachedCertificate);
 }
 //---------------------------------------------------------------------
 void __fastcall TSessionData::ValidatePath(const UnicodeString Path)
@@ -3148,6 +3155,11 @@ void __fastcall TSessionData::SetPublicKeyFile(UnicodeString value)
   }
 }
 //---------------------------------------------------------------------
+void __fastcall TSessionData::SetDetachedCertificate(UnicodeString value)
+{
+  SET_SESSION_PROPERTY(DetachedCertificate);
+}
+//---------------------------------------------------------------------
 UnicodeString TSessionData::ResolvePublicKeyFile()
 {
   UnicodeString Result = PublicKeyFile;
@@ -4599,9 +4611,10 @@ void __fastcall TSessionData::DisableAuthentationsExceptPassword()
   AuthKIPassword = false;
   AuthGSSAPI = false;
   AuthGSSAPIKEX = false;
-  PublicKeyFile = L"";
-  TlsCertificateFile = L"";
-  Passphrase = L"";
+  PublicKeyFile = EmptyStr;
+  DetachedCertificate = EmptyStr;
+  TlsCertificateFile = EmptyStr;
+  Passphrase = EmptyStr;
   TryAgent = false;
 }
 //---------------------------------------------------------------------

+ 3 - 0
source/core/SessionData.h

@@ -134,6 +134,7 @@ private:
   bool FVMSAllRevisions;
   UnicodeString FPublicKeyFile;
   UnicodeString FPassphrase;
+  UnicodeString FDetachedCertificate;
   UnicodeString FPuttyProtocol;
   TFSProtocol FFSProtocol;
   bool FModified;
@@ -285,6 +286,7 @@ private:
   void __fastcall SetPublicKeyFile(UnicodeString value);
   UnicodeString __fastcall GetPassphrase() const;
   void __fastcall SetPassphrase(UnicodeString value);
+  void __fastcall SetDetachedCertificate(UnicodeString value);
 
   void __fastcall SetPuttyProtocol(UnicodeString value);
   bool __fastcall GetCanLogin();
@@ -580,6 +582,7 @@ public:
   __property UnicodeString GssLibCustom = { read=FGssLibCustom, write=SetGssLibCustom };
   __property UnicodeString PublicKeyFile  = { read=FPublicKeyFile, write=SetPublicKeyFile };
   __property UnicodeString Passphrase  = { read=GetPassphrase, write=SetPassphrase };
+  __property UnicodeString DetachedCertificate  = { read=FDetachedCertificate, write=SetDetachedCertificate };
   __property UnicodeString PuttyProtocol  = { read=FPuttyProtocol, write=SetPuttyProtocol };
   __property TFSProtocol FSProtocol  = { read=FFSProtocol, write=SetFSProtocol  };
   __property UnicodeString FSProtocolStr  = { read=GetFSProtocolStr };

+ 2 - 1
source/core/Terminal.cpp

@@ -8423,7 +8423,8 @@ UnicodeString TTerminal::UploadPublicKey(const UnicodeString & FileName)
     Log->AddSeparator();
 
     UnicodeString Comment;
-    UnicodeString Line = GetPublicKeyLine(FileName, Comment);
+    bool UnusedHasCertificate;
+    UnicodeString Line = GetPublicKeyLine(FileName, Comment, UnusedHasCertificate);
 
     LogEvent(FORMAT(L"Adding public key line to \"%s\" file:\n%s", (AuthorizedKeysFilePath, Line)));
 

+ 30 - 2
source/forms/SiteAdvanced.cpp

@@ -51,6 +51,7 @@ __fastcall TSiteAdvancedDialog::TSiteAdvancedDialog(TComponent * AOwner) :
   TForm(AOwner)
 {
   NoUpdate = 0;
+  FKeyHasCertificate = false;
 
   // we need to make sure that window procedure is set asap
   // (so that CM_SHOWINGCHANGED handling is applied)
@@ -248,6 +249,7 @@ void __fastcall TSiteAdvancedDialog::LoadSession()
     GSSAPIFwdTGTCheck->Checked = FSessionData->GSSAPIFwdTGT;
     AgentFwdCheck->Checked = FSessionData->AgentFwd;
     PrivateKeyEdit3->Text = FSessionData->PublicKeyFile;
+    DetachedCertificateEdit->Text = FSessionData->DetachedCertificate;
 
     // SSH page
     Ssh2LegacyDESCheck->Checked = FSessionData->Ssh2DES;
@@ -474,6 +476,9 @@ void __fastcall TSiteAdvancedDialog::SaveSession(TSessionData * SessionData)
   SessionData->GSSAPIFwdTGT = GSSAPIFwdTGTCheck->Checked;
   SessionData->AgentFwd = AgentFwdCheck->Checked;
   SessionData->PublicKeyFile = PrivateKeyEdit3->Text;
+  // When user selectes key with certificate, s/he cannot clear the certificate box anymore as it is disabled,
+  // so let's not save it, in case it causes troubles in PuTTY code.
+  SessionData->DetachedCertificate = !FKeyHasCertificate ? DetachedCertificateEdit->Text : EmptyStr;
 
   // Connection page
   SessionData->FtpPasvMode = FtpPasvModeCheck->Checked;
@@ -855,7 +860,25 @@ void __fastcall TSiteAdvancedDialog::UpdateControls()
       AuthenticationGroup->Enabled && (AuthKICheck->Enabled && AuthKICheck->Checked));
     EnableControl(AuthenticationParamsGroup, AuthenticationGroup->Enabled);
     EnableControl(AgentFwdCheck, AuthenticationParamsGroup->Enabled && TryAgentCheck->Checked);
+    if (PrivateKeyEdit3->Text != FLastPrivateKey)
+    {
+      FLastPrivateKey = PrivateKeyEdit3->Text;
+      FKeyHasCertificate = false;
+      if (PrivateKeyEdit3->Enabled && !FLastPrivateKey.IsEmpty())
+      {
+        try
+        {
+          UnicodeString UnusedComment;
+          GetPublicKeyLine(FLastPrivateKey, UnusedComment, FKeyHasCertificate);
+        }
+        catch (...)
+        {
+        }
+      }
+    }
     EnableControl(PrivateKeyViewButton, PrivateKeyEdit3->Enabled && !PrivateKeyEdit3->Text.IsEmpty());
+    EnableControl(DetachedCertificateEdit, PrivateKeyViewButton->Enabled && !FKeyHasCertificate);
+    EnableControl(DetachedCertificateLabel, DetachedCertificateEdit->Enabled);
     EnableControl(AuthGSSAPICheck3, AuthenticationGroup->Enabled);
     EnableControl(GSSAPIFwdTGTCheck,
       AuthGSSAPICheck3->Enabled && AuthGSSAPICheck3->Checked);
@@ -1336,6 +1359,10 @@ void __fastcall TSiteAdvancedDialog::FormCloseQuery(TObject * /*Sender*/,
 void __fastcall TSiteAdvancedDialog::PathEditBeforeDialog(TObject * /*Sender*/,
   UnicodeString & Name, bool & /*Action*/)
 {
+  // Reset key stats, even if new key is not select, this way the clicking browse button gives the user chance to
+  // reload the "certificate" state in case the key file is recreated with certificate
+  FLastPrivateKey = EmptyStr;
+
   FBeforeDialogPath = Name;
   Name = ExpandEnvironmentVariables(Name);
 }
@@ -1609,7 +1636,8 @@ void __fastcall TSiteAdvancedDialog::PrivateKeyViewButtonClick(TObject * /*Sende
   VerifyAndConvertKey(FileName, false);
   PrivateKeyEdit3->Text = FileName;
   UnicodeString CommentDummy;
-  UnicodeString Line = GetPublicKeyLine(FileName, CommentDummy);
+  bool HasCertificate;
+  UnicodeString Line = GetPublicKeyLine(FileName, CommentDummy, HasCertificate);
   std::unique_ptr<TStrings> Messages(TextToStringList(Line));
 
   TClipboardHandler ClipboardHandler;
@@ -1623,7 +1651,7 @@ void __fastcall TSiteAdvancedDialog::PrivateKeyViewButtonClick(TObject * /*Sende
   Params.Aliases = Aliases;
   Params.AliasesCount = LENOF(Aliases);
 
-  UnicodeString Message = LoadStr(LOGIN_AUTHORIZED_KEYS);
+  UnicodeString Message = LoadStr(HasCertificate ? LOGIN_KEY_WITH_CERTIFICATE : LOGIN_AUTHORIZED_KEYS);
   int Answers = qaOK | qaRetry;
   MoreMessageDialog(Message, Messages.get(), qtInformation, Answers, HELP_LOGIN_AUTHORIZED_KEYS, &Params);
 }

+ 28 - 3
source/forms/SiteAdvanced.dfm

@@ -2288,13 +2288,13 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
           Left = 0
           Top = 132
           Width = 393
-          Height = 120
+          Height = 164
           Anchors = [akLeft, akTop, akRight]
           Caption = 'Authentication parameters'
           TabOrder = 2
           DesignSize = (
             393
-            120)
+            164)
           object PrivateKeyLabel: TLabel
             Left = 12
             Top = 42
@@ -2303,6 +2303,14 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
             Caption = 'Private &key file:'
             FocusControl = PrivateKeyEdit3
           end
+          object DetachedCertificateLabel: TLabel
+            Left = 12
+            Top = 117
+            Width = 186
+            Height = 13
+            Caption = 'Certificate to use with the private key:'
+            FocusControl = DetachedCertificateEdit
+          end
           object AgentFwdCheck: TCheckBox
             Left = 12
             Top = 19
@@ -2350,10 +2358,27 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
             TabOrder = 2
             OnClick = PrivateKeyViewButtonClick
           end
+          object DetachedCertificateEdit: TFilenameEdit
+            Left = 12
+            Top = 133
+            Width = 372
+            Height = 21
+            AcceptFiles = True
+            OnBeforeDialog = PathEditBeforeDialog
+            OnAfterDialog = PrivateKeyEdit3AfterDialog
+            Filter = 'Public key files (*.pub)|*.pub|All Files (*.*)|*.*'
+            DialogOptions = [ofReadOnly, ofPathMustExist, ofFileMustExist]
+            DialogTitle = 'Select certificate file'
+            ClickKey = 16397
+            Anchors = [akLeft, akTop, akRight]
+            TabOrder = 4
+            Text = 'DetachedCertificateEdit'
+            OnChange = DataChange
+          end
         end
         object GSSAPIGroup: TGroupBox
           Left = 0
-          Top = 258
+          Top = 302
           Width = 393
           Height = 71
           Anchors = [akLeft, akTop, akRight]

+ 4 - 0
source/forms/SiteAdvanced.h

@@ -281,6 +281,8 @@ __published:
   TCheckBox *VMSAllRevisionsCheck;
   TLabel *Label5;
   TComboBox *SFTPRealPathCombo;
+  TLabel *DetachedCertificateLabel;
+  TFilenameEdit *DetachedCertificateEdit;
   void __fastcall DataChange(TObject *Sender);
   void __fastcall FormShow(TObject *Sender);
   void __fastcall PageControlChange(TObject *Sender);
@@ -346,6 +348,8 @@ private:
   TFSProtocol FFSProtocol;
   TSessionData * FSessionData;
   TColor FColor;
+  UnicodeString FLastPrivateKey;
+  bool FKeyHasCertificate;
   std::unique_ptr<TPopupMenu> FColorPopupMenu;
   std::unique_ptr<TObjectList> FPrivateKeyMonitors;
   std::unique_ptr<TStrings> FPuttyRegSettings;

+ 1 - 2
source/putty/crypto/openssh-certs.c

@@ -1,4 +1,3 @@
-#ifndef WINSCP
 /*
  * Public key type for OpenSSH certificates.
  */
@@ -725,6 +724,7 @@ static key_components *opensshcert_components(ssh_key *key)
 static SeatDialogText *opensshcert_cert_info(ssh_key *key)
 {
 #ifdef WINSCP
+    assert(false);
     return NULL;
 #else
     opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
@@ -1223,4 +1223,3 @@ static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags,
     opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
     ssh_key_sign(ck->basekey, data, flags, bs);
 }
-#endif

+ 7 - 4
source/putty/putty.h

@@ -1286,7 +1286,7 @@ struct SeatVtable {
         Seat *seat, const char *host, int port, const char *keytype,
         char *keystr, SeatDialogText *text, HelpCtx helpctx,
         void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
-        char **fingerprints); // WINSCP
+        char **fingerprints, bool is_certificate); // WINSCP
 
     /*
      * Check with the seat whether it's OK to use a cryptographic
@@ -1443,10 +1443,10 @@ static inline SeatPromptResult seat_confirm_ssh_host_key(
     InteractionReadySeat iseat, const char *h, int p, const char *ktyp,
     char *kstr, SeatDialogText *text, HelpCtx helpctx,
     void (*cb)(void *ctx, SeatPromptResult result), void *ctx,
-    char **fingerprints) // WINSCP
+    char **fingerprints, bool is_certificate) // WINSCP
 { return iseat.seat->vt->confirm_ssh_host_key(
         iseat.seat, h, p, ktyp, kstr, text, helpctx, cb, ctx,
-        fingerprints); }
+        fingerprints, is_certificate); } // WINSCP
 static inline SeatPromptResult seat_confirm_weak_crypto_primitive(
     InteractionReadySeat iseat, const char *atyp, const char *aname,
     void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
@@ -1539,7 +1539,8 @@ void nullseat_set_busy_status(Seat *seat, BusyStatus status);
 SeatPromptResult nullseat_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
     char *keystr, SeatDialogText *text, HelpCtx helpctx,
-    void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+    void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
+    char **fingerprints, bool is_certificate); // WINSCP
 SeatPromptResult nullseat_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
@@ -1571,6 +1572,7 @@ bool nullseat_get_cursor_position(Seat *seat, int *x, int *y);
  * support module (console.c in each platform subdirectory).
  */
 
+#ifndef WINSCP
 void console_connection_fatal(Seat *seat, const char *message);
 SeatPromptResult console_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
@@ -1588,6 +1590,7 @@ void console_set_trust_status(Seat *seat, bool trusted);
 bool console_can_set_trust_status(Seat *seat);
 bool console_has_mixed_input_stream(Seat *seat);
 const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat);
+#endif
 
 /*
  * Other centralised seat functions.

+ 1 - 0
source/putty/ssh.h

@@ -1507,6 +1507,7 @@ int rsa1_loadpub_f(const Filename *filename, BinarySink *bs,
 extern const ssh_keyalg *const all_keyalgs[];
 extern const size_t n_keyalgs;
 const ssh_keyalg *find_pubkey_alg(const char *name);
+const ssh_keyalg *find_pubkey_alg_winscp_host(const char *name);
 const ssh_keyalg *find_pubkey_alg_len(ptrlen name);
 
 ptrlen pubkey_blob_to_alg_name(ptrlen blob);

+ 1 - 1
source/putty/ssh/common_p.c

@@ -1111,7 +1111,7 @@ SeatPromptResult verify_ssh_host_key(
     { // WINSCP
     SeatPromptResult toret = seat_confirm_ssh_host_key(
         iseat, host, port, keytype, keystr, text, helpctx, callback, ctx,
-        fingerprints); // WINSCP
+        fingerprints, key && ssh_key_alg(key)->is_certificate); // WINSCP
     seat_dialog_text_free(text);
     return toret;
     } // WINSCP

+ 17 - 3
source/putty/sshpubk.c

@@ -618,7 +618,6 @@ const ssh_keyalg *const all_keyalgs[] = {
     &ssh_ecdsa_nistp521,
     &ssh_ecdsa_ed25519,
     &ssh_ecdsa_ed448,
-#ifndef WINSCP
     &opensshcert_ssh_dsa,
     &opensshcert_ssh_rsa,
     &opensshcert_ssh_rsa_sha256,
@@ -627,7 +626,6 @@ const ssh_keyalg *const all_keyalgs[] = {
     &opensshcert_ssh_ecdsa_nistp256,
     &opensshcert_ssh_ecdsa_nistp384,
     &opensshcert_ssh_ecdsa_nistp521,
-#endif
 };
 const size_t n_keyalgs = lenof(all_keyalgs);
 
@@ -641,11 +639,27 @@ const ssh_keyalg *find_pubkey_alg_len(ptrlen name)
     return NULL;
 }
 
+static const ssh_keyalg *find_pubkey_alg_len_winscp_host(ptrlen name)
+{
+    size_t i;
+    for (i = 0; i < n_keyalgs; i++)
+        if (!all_keyalgs[i]->is_certificate &&
+            ptrlen_eq_string(name, all_keyalgs[i]->ssh_id))
+            return all_keyalgs[i];
+
+    return NULL;
+}
+
 const ssh_keyalg *find_pubkey_alg(const char *name)
 {
     return find_pubkey_alg_len(ptrlen_from_asciz(name));
 }
 
+const ssh_keyalg *find_pubkey_alg_winscp_host(const char *name)
+{
+    return find_pubkey_alg_len_winscp_host(ptrlen_from_asciz(name));
+}
+
 ptrlen pubkey_blob_to_alg_name(ptrlen blob)
 {
     BinarySource src[1];
@@ -1853,7 +1867,7 @@ char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)
     { // WINSCP
     ptrlen algname = get_string(src);
     if (!get_err(src)) {
-        const ssh_keyalg *alg = find_pubkey_alg_len(algname);
+        const ssh_keyalg *alg = find_pubkey_alg_len_winscp_host(algname);
         if (alg) {
             int bits = ssh_key_public_bits(alg, blob);
             put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits);

+ 2 - 1
source/putty/stubs/null-seat.c

@@ -23,7 +23,8 @@ void nullseat_set_busy_status(Seat *seat, BusyStatus status) {}
 SeatPromptResult nullseat_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
     char *keystr, SeatDialogText *text, HelpCtx helpctx,
-    void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+    void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
+    char **fingerprints, bool is_certificate) // WINSCP
 { return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
 SeatPromptResult nullseat_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,

+ 1 - 1
source/putty/utils/tempseat.c

@@ -250,7 +250,7 @@ static SeatPromptResult tempseat_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
     char *keystr, SeatDialogText *text, HelpCtx helpctx,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
-    char **fingerprints) // WINSCP
+    char **fingerprints, bool is_certificate) // WINSCP
 {
     unreachable("confirm_ssh_host_key should never be called on TempSeat");
 }

+ 2 - 1
source/putty/windows/platform.h

@@ -251,7 +251,8 @@ int has_embedded_chm(void);            /* 1 = yes, 0 = no, -1 = N/A */
 SeatPromptResult win_seat_confirm_ssh_host_key(
     Seat *seat, const char *host, int port, const char *keytype,
     char *keystr, SeatDialogText *text, HelpCtx helpctx,
-    void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+    void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
+    char **fingerprints, bool is_certificate); // WINSCP
 SeatPromptResult win_seat_confirm_weak_crypto_primitive(
     Seat *seat, const char *algtype, const char *algname,
     void (*callback)(void *ctx, SeatPromptResult result), void *ctx);

+ 1 - 1
source/resource/TextsWin.h

@@ -671,7 +671,7 @@
 #define KEX_NAME_DHGEX          6077
 #define KEX_NAME_RSA            6078
 #define KEX_NAME_ECDH           6079
-// Keep few indexes unused for future algos
+#define LOGIN_KEY_WITH_CERTIFICATE 7090
 
 // 2xxx is reserved for TextsFileZilla.h
 

+ 1 - 0
source/resource/TextsWin1.rc

@@ -676,6 +676,7 @@ BEGIN
         KEX_NAME_DHGEX, "Diffie-Hellman group exchange"
         KEX_NAME_RSA, "RSA-based key exchange"
         KEX_NAME_ECDH, "ECDH key exchange"
+        LOGIN_KEY_WITH_CERTIFICATE, "**This key contains an OpenSSH certificate.**\nIt is not supposed to be added to OpenSSH authorized_keys file."
 
         WIN_VARIABLE_STRINGS, "WIN_VARIABLE"
         WINSCP_COPYRIGHT, "Copyright © 2000–2022 Martin Prikryl"

+ 2 - 1
source/windows/TerminalManager.cpp

@@ -1946,7 +1946,8 @@ bool __fastcall TTerminalManager::UploadPublicKey(
         if (FAuthenticateForm != NULL)
         {
           UnicodeString Comment;
-          GetPublicKeyLine(FileName, Comment);
+          bool UnusedHasCertificate;
+          GetPublicKeyLine(FileName, Comment, UnusedHasCertificate);
           FAuthenticateForm->Log(FMTLOAD(LOGIN_PUBLIC_KEY_UPLOAD, (Comment)));
         }