Browse Source

Bug 2044: Provide private key as string in .NET assembly

https://winscp.net/tracker/2044
(cherry picked from commit 1018b272eaf39ba27d3056a8426a5037253fbd46)

Source commit: 0c40d1ef172d932508fd1b89887ad2eefc5fbd26
Martin Prikryl 3 years ago
parent
commit
219bab96c7

+ 29 - 5
dotnet/Session.cs

@@ -15,6 +15,7 @@ using System.Security;
 #endif
 using System.Text.RegularExpressions;
 using System.Linq;
+using System.Text;
 
 namespace WinSCP
 {
@@ -2048,13 +2049,28 @@ namespace WinSCP
                     }
                 }
 
-                if (!string.IsNullOrEmpty(sessionOptions.SshPrivateKeyPath) && !scanFingerprint)
+                bool hasSshPrivateKeyPath = !string.IsNullOrEmpty(sessionOptions.SshPrivateKeyPath);
+                bool hasSshPrivateKey = !string.IsNullOrEmpty(sessionOptions.SshPrivateKey);
+                if ((hasSshPrivateKeyPath || hasSshPrivateKey) && !scanFingerprint)
                 {
                     if (!sessionOptions.IsSsh)
                     {
-                        throw Logger.WriteException(new ArgumentException("SessionOptions.SshPrivateKeyPath is set, but SessionOptions.Protocol is neither Protocol.Sftp nor Protocol.Scp."));
+                        throw Logger.WriteException(new ArgumentException("SessionOptions.SshPrivateKeyPath or SessionOptions.SshPrivateKey is set, but SessionOptions.Protocol is neither Protocol.Sftp nor Protocol.Scp."));
                     }
-                    switches.Add(FormatSwitch("privatekey", sessionOptions.SshPrivateKeyPath));
+                    if (hasSshPrivateKeyPath && hasSshPrivateKey)
+                    {
+                        throw Logger.WriteException(new ArgumentException("Set only one of SessionOptions.SshPrivateKeyPath or SessionOptions.SshPrivateKey."));
+                    }
+                    string privateKey;
+                    if (hasSshPrivateKeyPath)
+                    {
+                        privateKey = sessionOptions.SshPrivateKeyPath;
+                    }
+                    else
+                    {
+                        privateKey = "@" + GenerateInMemoryPrivateKey(sessionOptions);
+                    }
+                    switches.Add(FormatSwitch("privatekey", privateKey));
                 }
 
                 if (!string.IsNullOrEmpty(sessionOptions.TlsClientCertificatePath) && !scanFingerprint)
@@ -2116,9 +2132,11 @@ namespace WinSCP
 
                 if ((sessionOptions.SecurePrivateKeyPassphrase != null) && !scanFingerprint)
                 {
-                    if (string.IsNullOrEmpty(sessionOptions.SshPrivateKeyPath) && string.IsNullOrEmpty(sessionOptions.TlsClientCertificatePath))
+                    if (string.IsNullOrEmpty(sessionOptions.SshPrivateKeyPath) &&
+                        string.IsNullOrEmpty(sessionOptions.SshPrivateKey) &&
+                        string.IsNullOrEmpty(sessionOptions.TlsClientCertificatePath))
                     {
-                        throw Logger.WriteException(new ArgumentException("SessionOptions.PrivateKeyPassphrase is set, but neither SessionOptions.SshPrivateKeyPath nor SessionOptions.TlsClientCertificatePath is set."));
+                        throw Logger.WriteException(new ArgumentException("SessionOptions.PrivateKeyPassphrase is set, but neither SessionOptions.SshPrivateKeyPath, SessionOptions.SshPrivateKey nor SessionOptions.TlsClientCertificatePath is set."));
                     }
                     switches.Add(FormatSwitch("passphrase", sessionOptions.PrivateKeyPassphrase));
                     logSwitches.Add(FormatSwitch("passphrase", "***"));
@@ -2139,6 +2157,12 @@ namespace WinSCP
             }
         }
 
+        internal static string GenerateInMemoryPrivateKey(SessionOptions sessionOptions)
+        {
+            byte[] bytes = Encoding.Default.GetBytes(sessionOptions.SshPrivateKey);
+            return BitConverter.ToString(bytes).Replace("-", "");
+        }
+
         private static string AddStarToList(string list)
         {
             return (string.IsNullOrEmpty(list) ? string.Empty : list + ";") + "*";

+ 1 - 0
dotnet/SessionOptions.cs

@@ -76,6 +76,7 @@ namespace WinSCP
         [Obsolete("Use SshHostKeyPolicy")]
         public bool GiveUpSecurityAndAcceptAnySshHostKey { get { return GetGiveUpSecurityAndAcceptAnySshHostKey(); } set { SetGiveUpSecurityAndAcceptAnySshHostKey(value); } }
         public string SshPrivateKeyPath { get; set; }
+        public string SshPrivateKey { get; set; }
         [Obsolete("Use PrivateKeyPassphrase")]
         public string SshPrivateKeyPassphrase { get { return PrivateKeyPassphrase; } set { PrivateKeyPassphrase = value; } }
 

+ 1 - 0
source/core/Interface.h

@@ -27,6 +27,7 @@
 #define RAWTRANSFERSETTINGS_SWITCH L"rawtransfersettings"
 #define USERNAME_SWITCH L"username"
 #define PASSWORD_SWITCH L"password"
+#define PRIVATEKEY_SWITCH L"privatekey"
 extern const wchar_t * TransferModeNames[];
 extern const int TransferModeNamesCount;
 extern const wchar_t * ToggleNames[];

+ 2 - 1
source/core/Option.cpp

@@ -313,7 +313,7 @@ bool __fastcall TOptions::UnusedSwitch(UnicodeString & Switch)
   return Result;
 }
 //---------------------------------------------------------------------------
-bool __fastcall TOptions::WasSwitchAdded(UnicodeString & Switch, wchar_t & SwitchMark)
+bool __fastcall TOptions::WasSwitchAdded(UnicodeString & Switch, UnicodeString & Value, wchar_t & SwitchMark)
 {
   bool Result =
     DebugAlwaysTrue(FOptions.size() > 0) &&
@@ -322,6 +322,7 @@ bool __fastcall TOptions::WasSwitchAdded(UnicodeString & Switch, wchar_t & Switc
   {
     TOption & Option = FOptions.back();
     Switch = Option.Name;
+    Value = Option.Value;
     SwitchMark = Option.SwitchMark;
   }
   return Result;

+ 1 - 1
source/core/Option.h

@@ -31,7 +31,7 @@ public:
   bool __fastcall SwitchValue(const UnicodeString Switch, bool Default);
   bool __fastcall SwitchValue(const UnicodeString Switch, bool Default, bool DefaultOnNonExistence);
   bool __fastcall UnusedSwitch(UnicodeString & Switch);
-  bool __fastcall WasSwitchAdded(UnicodeString & Switch, wchar_t & SwitchMark);
+  bool __fastcall WasSwitchAdded(UnicodeString & Switch, UnicodeString & Value, wchar_t & SwitchMark);
 
   void __fastcall LogOptions(TLogOptionEvent OnEnumOption);
 

+ 3 - 3
source/core/Script.cpp

@@ -2559,12 +2559,12 @@ void __fastcall TManagementScript::MaskPasswordInCommandLine(UnicodeString & Com
     {
       UnicodeString & MaskedParams = Url.IsEmpty() ? MaskedParamsPre : MaskedParamsPost;
 
-      UnicodeString Switch;
+      UnicodeString Switch, Value;
       wchar_t SwitchMark;
-      if (Options.WasSwitchAdded(Switch, SwitchMark))
+      if (Options.WasSwitchAdded(Switch, Value, SwitchMark))
       {
         OptionWithParameters = L"";
-        if (TSessionData::IsSensitiveOption(Switch))
+        if (TSessionData::IsSensitiveOption(Switch, Value))
         {
           // We should use something like TProgramParams::FormatSwitch here
           RawParam = FORMAT(L"%s%s=%s", (SwitchMark, Switch, PasswordMask));

+ 21 - 7
source/core/SessionData.cpp

@@ -1956,12 +1956,26 @@ bool __fastcall TSessionData::IsProtocolUrl(
     DoIsProtocolUrl(Url, WinSCPProtocolPrefix + Protocol, ProtocolLen);
 }
 //---------------------------------------------------------------------
-bool __fastcall TSessionData::IsSensitiveOption(const UnicodeString & Option)
+bool __fastcall TSessionData::IsSensitiveOption(const UnicodeString & Option, const UnicodeString & Value)
 {
-  return
-    SameText(Option, PassphraseOption) ||
-    SameText(Option, PASSWORD_SWITCH) ||
-    SameText(Option, NEWPASSWORD_SWITCH);
+  bool Result;
+  if (SameText(Option, PassphraseOption) ||
+      SameText(Option, PASSWORD_SWITCH) ||
+      SameText(Option, NEWPASSWORD_SWITCH))
+  {
+    Result = true;
+  }
+  else if (SameText(Option, PRIVATEKEY_SWITCH))
+  {
+    Filename * AFilename = filename_from_str(UTF8String(Value).c_str());
+    Result = (in_memory_key_data(AFilename) != NULL);
+    filename_free(AFilename);
+  }
+  else
+  {
+    Result = false;
+  }
+  return Result;
 }
 //---------------------------------------------------------------------
 bool __fastcall TSessionData::IsOptionWithParameters(const UnicodeString & Option)
@@ -2414,7 +2428,7 @@ bool __fastcall TSessionData::ParseUrl(UnicodeString Url, TOptions * Options,
       ChangePassword = true;
       NewPassword = Value;
     }
-    if (Options->FindSwitch(L"privatekey", Value))
+    if (Options->FindSwitch(PRIVATEKEY_SWITCH, Value))
     {
       PublicKeyFile = Value;
     }
@@ -3617,7 +3631,7 @@ UnicodeString __fastcall TSessionData::GenerateOpenCommandArgs(bool Rtf)
   }
   if (SessionData->PublicKeyFile != FactoryDefaults->PublicKeyFile)
   {
-    AddSwitch(Result, L"privatekey", SessionData->PublicKeyFile, Rtf);
+    AddSwitch(Result, PRIVATEKEY_SWITCH, SessionData->PublicKeyFile, Rtf);
     SessionData->PublicKeyFile = FactoryDefaults->PublicKeyFile;
   }
   if (SessionData->TlsCertificateFile != FactoryDefaults->TlsCertificateFile)

+ 1 - 1
source/core/SessionData.h

@@ -535,7 +535,7 @@ public:
   static UnicodeString __fastcall ExtractLocalName(const UnicodeString & Name);
   static UnicodeString __fastcall ExtractFolderName(const UnicodeString & Name);
   static UnicodeString __fastcall ComposePath(const UnicodeString & Path, const UnicodeString & Name);
-  static bool __fastcall IsSensitiveOption(const UnicodeString & Option);
+  static bool __fastcall IsSensitiveOption(const UnicodeString & Option, const UnicodeString & Value);
   static bool __fastcall IsOptionWithParameters(const UnicodeString & Option);
   static bool __fastcall MaskPasswordInOptionParameter(const UnicodeString & Option, UnicodeString & Param);
   static UnicodeString __fastcall FormatSiteKey(const UnicodeString & HostName, int PortNumber);

+ 1 - 0
source/putty/putty.h

@@ -2240,6 +2240,7 @@ enum {
  */
 Filename *filename_from_str(const char *string);
 const char *filename_to_str(const Filename *fn);
+const char* in_memory_key_data(const Filename *fn); // WINSCP
 bool filename_equal(const Filename *f1, const Filename *f2);
 bool filename_is_null(const Filename *fn);
 Filename *filename_copy(const Filename *fn);

+ 30 - 0
source/putty/sshpubk.c

@@ -93,6 +93,32 @@ LoadFileStatus lf_load_fp(LoadedFile *lf, FILE *fp)
 
 LoadFileStatus lf_load(LoadedFile *lf, const Filename *filename)
 {
+    #ifdef WINSCP
+    const char * data = in_memory_key_data(filename);
+    if (data != NULL)
+    {
+        LoadFileStatus status = LF_OK;
+        int len = strlen(data);
+        char buf[3] = { '\0' };
+        int i;
+        for (i = 0; i < len; i += 2)
+        {
+            if (lf->len == lf->max_size)
+            {
+                status = LF_TOO_BIG;
+                break;
+            }
+            buf[0] = data[i];
+            buf[1] = data[i + 1];
+            lf->data[lf->len] = strtol(buf, NULL, 16);
+            lf->len++;
+        }
+
+        BinarySource_INIT(lf, lf->data, lf->len);
+        return status;
+    }
+    #endif
+    { // WINSCP
     FILE *fp = f_open(filename, "rb", false);
     if (!fp)
         return LF_ERROR;
@@ -102,6 +128,7 @@ LoadFileStatus lf_load(LoadedFile *lf, const Filename *filename)
     fclose(fp);
     return status;
     } // WINSCP
+    } // WINSCP
 }
 
 static inline bool lf_load_keyfile_helper(LoadFileStatus status,
@@ -135,6 +162,8 @@ LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr)
     return lf;
 }
 
+#ifndef WINSCP
+/* This API does not support in-memory keys like lf_load, so make sure it's not in use */
 LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr)
 {
     LoadedFile *lf = lf_new(MAX_KEY_FILE_SIZE);
@@ -144,6 +173,7 @@ LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr)
     }
     return lf;
 }
+#endif
 
 static bool expect_signature(BinarySource *src, ptrlen realsig)
 {

+ 37 - 0
source/putty/windows/winmisc.c

@@ -33,8 +33,45 @@ Filename *filename_copy(const Filename *fn)
     return filename_from_str(fn->path);
 }
 
+#ifdef WINSCP
+const char* in_memory_key_data(const Filename *fn)
+{
+    const char* result = fn->path;
+    if (result[0] != '@')
+    {
+        result = NULL;
+    }
+    else
+    {
+        int len;
+        result++;
+        len = strlen(result);
+        if (((len % 2) != 0) ||
+            ((len / 2) < MAX_PATH))
+        {
+            result = NULL;
+        }
+        else
+        {
+            int i;
+            for (i = 0; (result != NULL) && (i < len); i++)
+            {
+                 if (!isxdigit(result[i]))
+                 {
+                     result = NULL;
+                 }
+            }
+        }
+    }
+    return result;
+}
+#endif
+
 const char *filename_to_str(const Filename *fn)
 {
+    #ifdef WINSCP
+    if (in_memory_key_data(fn) != NULL) return "in-memory";
+    #endif
     return fn->path;
 }
 

+ 2 - 2
source/windows/ConsoleRunner.cpp

@@ -2292,7 +2292,7 @@ void __fastcall Usage(TConsole * Console)
     PrintUsageSyntax(Console, FORMAT(L"[mysession] /%s [local_dir] [remote_dir] [/%s]", (LowerCase(SYNCHRONIZE_SWITCH), LowerCase(DEFAULTS_SWITCH))));
     PrintUsageSyntax(Console, FORMAT(L"[mysession] /%s [local_dir] [remote_dir] [/%s]", (LowerCase(KEEP_UP_TO_DATE_SWITCH), LowerCase(DEFAULTS_SWITCH))));
     PrintUsageSyntax(Console, FORMAT(L"[mysession] /%s [path]", (LowerCase(REFRESH_SWITCH))));
-    PrintUsageSyntax(Console, FORMAT(L"[mysession] [/privatekey=<file> [/%s=<passphrase>]]", (PassphraseOption)));
+    PrintUsageSyntax(Console, FORMAT(L"[mysession] [/%s=<file> [/%s=<passphrase>]]", (LowerCase(PRIVATEKEY_SWITCH), PassphraseOption)));
     PrintUsageSyntax(Console, L"[mysession] [/hostkey=<fingerprint>]");
     PrintUsageSyntax(Console, FORMAT(L"[mysession] [/%s=<user> [/%s=<password>]]", (LowerCase(USERNAME_SWITCH), LowerCase(PASSWORD_SWITCH))));
     PrintUsageSyntax(Console, FORMAT(L"[mysession] [/clientcert=<file> [/%s=<passphrase>]]", (PassphraseOption)));
@@ -2348,7 +2348,7 @@ void __fastcall Usage(TConsole * Console)
     RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(KEEP_UP_TO_DATE_SWITCH), USAGE_KEEPUPTODATE);
     RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(REFRESH_SWITCH), USAGE_REFRESH);
     RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(DEFAULTS_SWITCH), USAGE_DEFAULTS);
-    RegisterSwitch(SwitchesUsage, L"/privatekey=", USAGE_PRIVATEKEY);
+    RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(PRIVATEKEY_SWITCH) + L"=", USAGE_PRIVATEKEY);
     RegisterSwitch(SwitchesUsage, L"/hostkey=", USAGE_HOSTKEY);
     RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(USERNAME_SWITCH), USAGE_USERNAME);
     RegisterSwitch(SwitchesUsage, TProgramParams::FormatSwitch(PASSWORD_SWITCH), USAGE_PASSWORD);

+ 1 - 1
source/windows/WinMain.cpp

@@ -785,7 +785,7 @@ int __fastcall Execute()
   UpdateStaticUsage();
 
   UnicodeString KeyFile;
-  if (Params->FindSwitch(L"PrivateKey", KeyFile))
+  if (Params->FindSwitch(PRIVATEKEY_SWITCH, KeyFile))
   {
     WinConfiguration->DefaultKeyFile = KeyFile;
   }