Browse Source

Bug 1653: Files can be optionally encrypted when storing them on SFTP server

https://winscp.net/tracker/1653

Source commit: 31d8bb5e3aebe725081c5ad74aa1e10b4f739356
Martin Prikryl 7 years ago
parent
commit
0a08412ec1
45 changed files with 905 additions and 117 deletions
  1. 1 1
      source/WinSCP.cbproj
  2. 4 0
      source/components/UnixDirView.cpp
  3. 11 4
      source/components/UnixDriveView.cpp
  4. 5 0
      source/core/Common.cpp
  5. 1 0
      source/core/Common.h
  6. 21 1
      source/core/CopyParam.cpp
  7. 3 0
      source/core/CopyParam.h
  8. 223 0
      source/core/Cryptography.cpp
  9. 38 0
      source/core/Cryptography.h
  10. 1 1
      source/core/FileSystems.h
  11. 1 1
      source/core/FtpFileSystem.cpp
  12. 1 1
      source/core/FtpFileSystem.h
  13. 18 2
      source/core/RemoteFiles.cpp
  14. 6 2
      source/core/RemoteFiles.h
  15. 1 1
      source/core/S3FileSystem.cpp
  16. 1 1
      source/core/S3FileSystem.h
  17. 1 1
      source/core/ScpFileSystem.cpp
  18. 1 1
      source/core/ScpFileSystem.h
  19. 4 1
      source/core/Script.cpp
  20. 69 1
      source/core/SessionData.cpp
  21. 6 0
      source/core/SessionData.h
  22. 127 58
      source/core/SftpFileSystem.cpp
  23. 7 2
      source/core/SftpFileSystem.h
  24. 124 11
      source/core/Terminal.cpp
  25. 13 3
      source/core/Terminal.h
  26. 1 1
      source/core/WebDAVFileSystem.cpp
  27. 1 1
      source/core/WebDAVFileSystem.h
  28. 4 0
      source/forms/CopyParams.cpp
  29. 9 0
      source/forms/CopyParams.dfm
  30. 1 0
      source/forms/CopyParams.h
  31. 9 1
      source/forms/CustomScpExplorer.cpp
  32. 1 0
      source/forms/CustomScpExplorer.h
  33. 1 1
      source/forms/ScpCommander.cpp
  34. 63 0
      source/forms/SiteAdvanced.cpp
  35. 96 19
      source/forms/SiteAdvanced.dfm
  36. 12 0
      source/forms/SiteAdvanced.h
  37. 3 1
      source/packages/filemng/CustomDirView.pas
  38. BIN
      source/packages/filemng/DirImg.res
  39. 2 0
      source/putty/puttyexp.h
  40. 5 0
      source/putty/sshaes.c
  41. 1 0
      source/resource/HelpCore.h
  42. 3 0
      source/resource/TextsCore.h
  43. 3 0
      source/resource/TextsCore1.rc
  44. 1 0
      source/resource/TextsWin.h
  45. 1 0
      source/resource/TextsWin1.rc

+ 1 - 1
source/WinSCP.cbproj

@@ -97,7 +97,7 @@
 		<DCC_DebugInfoInExe>true</DCC_DebugInfoInExe>
 		<DCC_Define>DEBUG</DCC_Define>
 		<DCC_Optimize>false</DCC_Optimize>
-		<Debugger_DebugSourcePath>packages\my;packages\filemng;packages\jcl;..\libs\openssl\crypto\bio;..\libs\openssl\ssl;..\libs\openssl\crypto\stack;..\libs\openssl\crypto\x509;..\libs\openssl\crypto\evp;..\libs\openssl\crypto\pkcs12;..\libs\openssl\crypto\pem;..\libs\openssl\crypto\asn1;..\libs\openssl\crypto\err;..\libs\neon\src;..\libs\libs3\src;..\libs\expat\lib;$(Debugger_DebugSourcePath)</Debugger_DebugSourcePath>
+		<Debugger_DebugSourcePath>packages\my;packages\filemng;packages\jcl;..\libs\openssl\crypto\bio;..\libs\openssl\ssl;..\libs\openssl\crypto\stack;..\libs\openssl\crypto\x509;..\libs\openssl\crypto\evp;..\libs\openssl\crypto\pkcs12;..\libs\openssl\crypto\pem;..\libs\openssl\crypto\asn1;..\libs\openssl\crypto\err;..\libs\openssl\crypto\rand;..\libs\neon\src;..\libs\libs3\src;..\libs\expat\lib;$(Debugger_DebugSourcePath)</Debugger_DebugSourcePath>
 		<Defines>_DEBUG;$(Defines)</Defines>
 		<ILINK_FullDebugInfo>true</ILINK_FullDebugInfo>
 		<TASM_Debugging>Full</TASM_Debugging>

+ 4 - 0
source/components/UnixDirView.cpp

@@ -277,6 +277,10 @@ Word __fastcall TUnixDirView::ItemOverlayIndexes(TListItem * Item)
   {
     Result |= ITEMFILE->BrokenLink ? oiBrokenLink : oiLink;
   }
+  if (ITEMFILE->IsEncrypted)
+  {
+    Result |= oiEncrypted;
+  }
   return Result;
 #else
   DebugUsedParam(Item);

+ 11 - 4
source/components/UnixDriveView.cpp

@@ -705,11 +705,18 @@ Word __fastcall TCustomUnixDriveView::NodeOverlayIndexes(TTreeNode * Node)
   if (Node->Parent != NULL)
   {
     TRemoteFile * File = NodeFile(Node);
-    if ((File != NULL) && (File->IsSymLink))
+    if (File != NULL)
     {
-      // broken link cannot probably happen anyway
-      // as broken links are treated as files
-      Result |= File->BrokenLink ? oiBrokenLink : oiLink;
+      if (File->IsSymLink)
+      {
+        // broken link cannot probably happen anyway
+        // as broken links are treated as files
+        Result |= File->BrokenLink ? oiBrokenLink : oiLink;
+      }
+      if (File->IsEncrypted)
+      {
+        Result |= oiEncrypted;
+      }
     }
   }
   return Result;

+ 5 - 0
source/core/Common.cpp

@@ -101,6 +101,11 @@ void __fastcall Shred(AnsiString & Str)
   DoShred(Str);
 }
 //---------------------------------------------------------------------------
+void __fastcall Shred(RawByteString & Str)
+{
+  DoShred(Str);
+}
+//---------------------------------------------------------------------------
 UnicodeString AnsiToString(const RawByteString & S)
 {
   return UnicodeString(AnsiString(S));

+ 1 - 0
source/core/Common.h

@@ -40,6 +40,7 @@ void PackStr(AnsiString & Str);
 void __fastcall Shred(UnicodeString & Str);
 void __fastcall Shred(UTF8String & Str);
 void __fastcall Shred(AnsiString & Str);
+void __fastcall Shred(RawByteString & Str);
 UnicodeString AnsiToString(const RawByteString & S);
 UnicodeString AnsiToString(const char * S, size_t Len);
 UnicodeString MakeValidFileName(UnicodeString FileName);

+ 21 - 1
source/core/CopyParam.cpp

@@ -54,6 +54,7 @@ void __fastcall TCopyParamType::Default()
   RemoveBOM = false;
   CPSLimit = 0;
   NewerOnly = false;
+  EncryptNewFiles = true;
 }
 //---------------------------------------------------------------------------
 UnicodeString __fastcall TCopyParamType::GetInfoStr(
@@ -426,6 +427,20 @@ void __fastcall TCopyParamType::DoGetInfoStr(
     }
   }
 
+  if (EncryptNewFiles != Defaults.EncryptNewFiles)
+  {
+    if (!DebugAlwaysFalse(EncryptNewFiles))
+    {
+      const int Except = cpaIncludeMaskOnly | cpaNoEncryptNewFiles;
+      ADD(StripHotkey(LoadStr(COPY_INFO_DONT_ENCRYPT_NEW_FILES)), Except);
+      if (FLAGCLEAR(Options, Except))
+      {
+        NoScriptArgs = true;
+        NoCodeProperties = true;
+      }
+    }
+  }
+
   bool ResumeThresholdDiffers = ((ResumeSupport == rsSmart) && (ResumeThreshold != Defaults.ResumeThreshold));
   if (((ResumeSupport != Defaults.ResumeSupport) || ResumeThresholdDiffers) &&
       (TransferMode != tmAscii) && FLAGCLEAR(Options, cpaNoResumeSupport))
@@ -509,6 +524,7 @@ void __fastcall TCopyParamType::Assign(const TCopyParamType * Source)
   COPY(RemoveBOM);
   COPY(CPSLimit);
   COPY(NewerOnly);
+  COPY(EncryptNewFiles);
   #undef COPY
 }
 //---------------------------------------------------------------------------
@@ -679,7 +695,7 @@ UnicodeString __fastcall TCopyParamType::GetLogStr() const
        BooleanToEngStr(CalculateSize),
        FileMask)) +
     FORMAT(
-      L"  TM: %s; ClAr: %s; RemEOF: %s; RemBOM: %s; CPS: %u; NewerOnly: %s; InclM: %s; ResumeL: %d\n"
+      L"  TM: %s; ClAr: %s; RemEOF: %s; RemBOM: %s; CPS: %u; NewerOnly: %s; EncryptNewFiles: %s; InclM: %s; ResumeL: %d\n"
        "  AscM: %s\n",
       (ModeC[TransferMode],
        BooleanToEngStr(ClearArchive),
@@ -687,6 +703,7 @@ UnicodeString __fastcall TCopyParamType::GetLogStr() const
        BooleanToEngStr(RemoveBOM),
        int(CPSLimit),
        BooleanToEngStr(NewerOnly),
+       BooleanToEngStr(EncryptNewFiles),
        IncludeFileMask.Masks,
        ((FTransferSkipList.get() != NULL) ? FTransferSkipList->Count : 0) + (!FTransferResumeFile.IsEmpty() ? 1 : 0),
        AsciiFileMask.Masks));
@@ -820,6 +837,7 @@ void __fastcall TCopyParamType::Load(THierarchicalStorage * Storage)
   RemoveBOM = Storage->ReadBool(L"RemoveBOM", RemoveBOM);
   CPSLimit = Storage->ReadInteger(L"CPSLimit", CPSLimit);
   NewerOnly = Storage->ReadBool(L"NewerOnly", NewerOnly);
+  EncryptNewFiles = Storage->ReadBool(L"EncryptNewFiles", EncryptNewFiles);
 }
 //---------------------------------------------------------------------------
 void __fastcall TCopyParamType::Save(THierarchicalStorage * Storage, const TCopyParamType * Defaults) const
@@ -863,6 +881,7 @@ void __fastcall TCopyParamType::Save(THierarchicalStorage * Storage, const TCopy
   WRITE_DATA(Bool, RemoveBOM);
   WRITE_DATA(Integer, CPSLimit);
   WRITE_DATA(Bool, NewerOnly);
+  WRITE_DATA(Bool, EncryptNewFiles);
 }
 //---------------------------------------------------------------------------
 #define C(Property) (Property == rhp.Property)
@@ -894,6 +913,7 @@ bool __fastcall TCopyParamType::operator==(const TCopyParamType & rhp) const
     C(RemoveBOM) &&
     C(CPSLimit) &&
     C(NewerOnly) &&
+    C(EncryptNewFiles) &&
     true;
 }
 #undef C

+ 3 - 0
source/core/CopyParam.h

@@ -25,6 +25,7 @@ const int cpaNoRemoveCtrlZ      = 0x200;
 const int cpaNoRemoveBOM        = 0x400;
 const int cpaNoPreserveTimeDirs = 0x800;
 const int cpaNoResumeSupport    = 0x1000;
+const int cpaNoEncryptNewFiles  = 0x2000;
 //---------------------------------------------------------------------------
 struct TUsableCopyParamAttrs
 {
@@ -62,6 +63,7 @@ private:
   bool FRemoveBOM;
   unsigned long FCPSLimit;
   bool FNewerOnly;
+  bool FEncryptNewFiles;
   static const wchar_t TokenPrefix = L'%';
   static const wchar_t NoReplacement = wchar_t(false);
   static const wchar_t TokenReplacement = wchar_t(true);
@@ -135,6 +137,7 @@ public:
   __property bool RemoveBOM = { read = FRemoveBOM, write = FRemoveBOM };
   __property unsigned long CPSLimit = { read = FCPSLimit, write = FCPSLimit };
   __property bool NewerOnly = { read = FNewerOnly, write = FNewerOnly };
+  __property bool EncryptNewFiles = { read = FEncryptNewFiles, write = FEncryptNewFiles };
 };
 //---------------------------------------------------------------------------
 unsigned long __fastcall GetSpeedLimit(const UnicodeString & Text);

+ 223 - 0
source/core/Cryptography.cpp

@@ -5,8 +5,12 @@
 #include "Common.h"
 #include "PuttyIntf.h"
 #include "Cryptography.h"
+#include "FileBuffer.h"
+#include "TextsCore.h"
 #include <openssl\rand.h>
 #include <process.h>
+#include <Soap.EncdDecd.hpp>
+#include <System.StrUtils.hpp>
 
 /*
  ---------------------------------------------------------------------------
@@ -362,6 +366,23 @@ static void AES256Salt(RawByteString & Salt)
   RAND_pseudo_bytes(reinterpret_cast<unsigned char *>(Salt.c_str()), Salt.Length());
 }
 //---------------------------------------------------------------------------
+RawByteString GenerateEncryptKey()
+{
+  RawByteString Result;
+  Result.SetLength(KEY_LENGTH(PASSWORD_MANAGER_AES_MODE));
+  RAND_pseudo_bytes(reinterpret_cast<unsigned char *>(Result.c_str()), Result.Length());
+  return Result;
+}
+//---------------------------------------------------------------------------
+void ValidateEncryptKey(const RawByteString & Key)
+{
+  int Len = KEY_LENGTH(PASSWORD_MANAGER_AES_MODE);
+  if (Key.Length() != Len)
+  {
+    throw Exception(FMTLOAD(INVALID_ENCRYPT_KEY, (L"AES-256", Len, Len * 2)));
+  }
+}
+//---------------------------------------------------------------------------
 void __fastcall AES256EncyptWithMAC(RawByteString Input, UnicodeString Password,
   RawByteString & Salt, RawByteString & Output, RawByteString & Mac)
 {
@@ -618,3 +639,205 @@ int __fastcall IsValidPassword(UnicodeString Password)
     return (Password.Length() >= 6) && ((A + B + C + D) >= 2);
   }
 }
+//---------------------------------------------------------------------------
+//---------------------------------------------------------------------------
+TEncryption::TEncryption(const RawByteString & Key)
+{
+  FKey = Key;
+  FOutputtedHeader = false;
+  if (!FKey.IsEmpty())
+  {
+    DebugAssert(FKey.Length() == KEY_LENGTH(PASSWORD_MANAGER_AES_MODE));
+    FContext = call_aes_make_context();
+    aes_set_encrypt_key(reinterpret_cast<unsigned char *>(FKey.c_str()), FKey.Length(), FContext);
+  }
+}
+//---------------------------------------------------------------------------
+TEncryption::~TEncryption()
+{
+  Shred(FKey);
+  if ((FInputHeader.Length() > 0) && (FInputHeader.Length() < GetOverhead()))
+  {
+    throw Exception(LoadStr(UNKNOWN_FILE_ENCRYPTION));
+  }
+}
+//---------------------------------------------------------------------------
+void TEncryption::SetSalt()
+{
+  aes_iv(FContext, reinterpret_cast<unsigned char *>(FSalt.c_str()));
+}
+//---------------------------------------------------------------------------
+void TEncryption::NeedSalt()
+{
+  if (FSalt.IsEmpty())
+  {
+    AES256Salt(FSalt);
+    SetSalt();
+  }
+}
+//---------------------------------------------------------------------------
+const int AesBlock = 16;
+const int AesBlockMask = 0x0F;
+UnicodeString AesCtrExt(L".aesctr.enc");
+RawByteString AesCtrMagic("aesctr.........."); // 16 bytes fixed [to match AES block size], even for future algos
+//---------------------------------------------------------------------------
+int TEncryption::RoundToBlock(int Size)
+{
+  int M = (Size % BLOCK_SIZE);
+  if (M != 0)
+  {
+    Size += (BLOCK_SIZE - M);
+  }
+  return Size;
+}
+//---------------------------------------------------------------------------
+int TEncryption::RoundToBlockDown(int Size)
+{
+  return Size - (Size % BLOCK_SIZE);
+}
+//---------------------------------------------------------------------------
+void TEncryption::Aes(char * Buffer, int Size)
+{
+  DebugAssert(!FSalt.IsEmpty());
+  call_aes_sdctr(reinterpret_cast<unsigned char *>(Buffer), Size, FContext);
+}
+//---------------------------------------------------------------------------
+void TEncryption::Aes(TFileBuffer & Buffer, bool Last)
+{
+  if (!FOverflowBuffer.IsEmpty())
+  {
+    Buffer.Insert(0, FOverflowBuffer.c_str(), FOverflowBuffer.Length());
+    FOverflowBuffer.SetLength(0);
+  }
+
+  int Size;
+  if (Last)
+  {
+    Size = Buffer.Size;
+    Buffer.Size = RoundToBlock(Size);
+  }
+  else
+  {
+    int RoundedSize = RoundToBlockDown(Buffer.Size);
+    if (RoundedSize != Buffer.Size)
+    {
+      FOverflowBuffer += RawByteString(Buffer.Data + RoundedSize, Buffer.Size - RoundedSize);
+      Buffer.Size = RoundedSize;
+    }
+  }
+
+  Aes(Buffer.Data, Buffer.Size);
+
+  if (Last)
+  {
+    Buffer.Size = Size;
+  }
+}
+//---------------------------------------------------------------------------
+void TEncryption::Encrypt(TFileBuffer & Buffer, bool Last)
+{
+  NeedSalt();
+  Aes(Buffer, Last);
+  if (!FOutputtedHeader)
+  {
+    DebugAssert(AesCtrMagic.Length() == BLOCK_SIZE);
+    RawByteString Header = AesCtrMagic + FSalt;
+    DebugAssert(Header.Length() == GetOverhead());
+    Buffer.Insert(0, Header.c_str(), Header.Length());
+    FOutputtedHeader = true;
+  }
+}
+//---------------------------------------------------------------------------
+void TEncryption::Decrypt(TFileBuffer & Buffer)
+{
+  if (FInputHeader < GetOverhead())
+  {
+    int HeaderSize = std::min(GetOverhead() - FInputHeader.Length(), Buffer.Size);
+    FInputHeader += RawByteString(Buffer.Data, HeaderSize);
+    Buffer.Delete(0, HeaderSize);
+
+    if (FInputHeader >= GetOverhead())
+    {
+      if (FInputHeader.SubString(1, AesCtrMagic.Length()) != AesCtrMagic)
+      {
+        throw Exception(LoadStr(UNKNOWN_FILE_ENCRYPTION));
+      }
+
+      FSalt = FInputHeader.SubString(AesCtrMagic.Length() + 1, SALT_LENGTH(PASSWORD_MANAGER_AES_MODE));
+      SetSalt();
+    }
+  }
+
+  if (Buffer.Size > 0)
+  {
+    Aes(Buffer, false);
+  }
+}
+//---------------------------------------------------------------------------
+bool TEncryption::DecryptEnd(TFileBuffer & Buffer)
+{
+  bool Result = !FOverflowBuffer.IsEmpty();
+  if (Result)
+  {
+    Aes(Buffer, true);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+void TEncryption::Aes(RawByteString & Buffer)
+{
+  int Size = Buffer.Length();
+  Buffer.SetLength(RoundToBlock(Buffer.Length()));
+  Aes(Buffer.c_str(), Buffer.Length());
+  Buffer.SetLength(Size);
+}
+//---------------------------------------------------------------------------
+UnicodeString TEncryption::EncryptFileName(const UnicodeString & FileName)
+{
+  NeedSalt();
+  UTF8String FileNameUtf(FileName);
+  RawByteString Buffer(FileNameUtf);
+  Aes(Buffer);
+  Buffer = FSalt + Buffer;
+  UnicodeString Base64 = ReplaceChar(UnicodeString(EncodeBase64(Buffer.c_str(), Buffer.Length())), L'/', L'_');
+  while (DebugAlwaysTrue(!Base64.IsEmpty()) && (Base64.SubString(Base64.Length(), 1) == L'='))
+  {
+    Base64.SetLength(Base64.Length() - 1);
+  }
+  UnicodeString Result = Base64 + AesCtrExt;
+  return Result;
+}
+//---------------------------------------------------------------------------
+UnicodeString TEncryption::DecryptFileName(const UnicodeString & FileName)
+{
+  if (!IsEncryptedFileName(FileName))
+  {
+    throw Exception(L"Not an encrypted file name");
+  }
+  UnicodeString Base64 = ReplaceChar(LeftStr(FileName, FileName.Length() - AesCtrExt.Length()), L'_', L'/');
+  int Padding = 4 - (Base64.Length() % 4);
+  if ((Padding > 0) && (Padding < 4))
+  {
+    Base64 += UnicodeString::StringOfChar(L'=', Padding);
+  }
+  DynamicArray<Byte> BufferBytes = DecodeBase64(Base64);
+  RawByteString Buffer(reinterpret_cast<const char *>(&BufferBytes[0]), BufferBytes.Length);
+  FSalt = Buffer.SubString(1, SALT_LENGTH(PASSWORD_MANAGER_AES_MODE));
+  SetSalt();
+  Buffer.Delete(1, FSalt.Length());
+  Aes(Buffer);
+  UTF8String FileNameUtf(Buffer);
+  UnicodeString Result(FileNameUtf);
+  return Result;
+}
+//---------------------------------------------------------------------------
+bool TEncryption::IsEncryptedFileName(const UnicodeString & FileName)
+{
+  return EndsStr(AesCtrExt, FileName);
+}
+//---------------------------------------------------------------------------
+int TEncryption::GetOverhead()
+{
+  return AesCtrMagic.Length() + SALT_LENGTH(PASSWORD_MANAGER_AES_MODE);
+}
+//---------------------------------------------------------------------------

+ 38 - 0
source/core/Cryptography.h

@@ -14,5 +14,43 @@ void __fastcall AES256CreateVerifier(UnicodeString Input, RawByteString & Verifi
 bool __fastcall AES256Verify(UnicodeString Input, RawByteString Verifier);
 int __fastcall IsValidPassword(UnicodeString Password);
 int __fastcall PasswordMaxLength();
+RawByteString GenerateEncryptKey();
+void ValidateEncryptKey(const RawByteString & Key);
+//---------------------------------------------------------------------------
+class TFileBuffer;
+//---------------------------------------------------------------------------
+class TEncryption
+{
+public:
+  TEncryption(const RawByteString & Key);
+  ~TEncryption();
+
+  static bool IsEncryptedFileName(const UnicodeString & FileName);
+
+  void Encrypt(TFileBuffer & Buffer, bool Last);
+  void Decrypt(TFileBuffer & Buffer);
+  bool DecryptEnd(TFileBuffer & Buffer);
+  UnicodeString EncryptFileName(const UnicodeString & FileName);
+  UnicodeString DecryptFileName(const UnicodeString & FileName);
+
+  static int GetOverhead();
+  static int RoundToBlock(int Size);
+  static int RoundToBlockDown(int Size);
+
+private:
+  RawByteString FKey;
+  RawByteString FSalt;
+  RawByteString FInputHeader;
+  RawByteString FOverflowBuffer;
+  bool FOutputtedHeader;
+  void * FContext;
+
+  void Init(const RawByteString & Key, const RawByteString & Salt);
+  void Aes(char * Buffer, int Size);
+  void Aes(RawByteString & Buffer);
+  void Aes(TFileBuffer & Buffer, bool Last);
+  void NeedSalt();
+  void SetSalt();
+};
 //---------------------------------------------------------------------------
 #endif

+ 1 - 1
source/core/FileSystems.h

@@ -72,7 +72,7 @@ public:
     const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
     const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
     unsigned int Flags, TDownloadSessionAction & Action) = 0;
-  virtual void __fastcall CreateDirectory(const UnicodeString DirName) = 0;
+  virtual void __fastcall CreateDirectory(const UnicodeString & DirName, bool Encrypt) = 0;
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic) = 0;
   virtual void __fastcall DeleteFile(const UnicodeString FileName,
     const TRemoteFile * File, int Params,

+ 1 - 1
source/core/FtpFileSystem.cpp

@@ -1674,7 +1674,7 @@ void __fastcall TFTPFileSystem::Source(
   FLastDataSent = Now();
 }
 //---------------------------------------------------------------------------
-void __fastcall TFTPFileSystem::CreateDirectory(const UnicodeString ADirName)
+void __fastcall TFTPFileSystem::CreateDirectory(const UnicodeString & ADirName, bool /*Encrypt*/)
 {
   UnicodeString DirName = AbsolutePath(ADirName, false);
 

+ 1 - 1
source/core/FtpFileSystem.h

@@ -63,7 +63,7 @@ public:
     const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
     const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
     unsigned int Flags, TDownloadSessionAction & Action);
-  virtual void __fastcall CreateDirectory(const UnicodeString DirName);
+  virtual void __fastcall CreateDirectory(const UnicodeString & DirName, bool Encrypt);
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   virtual void __fastcall DeleteFile(const UnicodeString FileName,
     const TRemoteFile * File, int Params, TRmSessionAction & Action);

+ 18 - 2
source/core/RemoteFiles.cpp

@@ -14,6 +14,7 @@
 #include "Terminal.h"
 #include "TextsCore.h"
 #include "HelpCore.h"
+#include "Cryptography.h"
 /* TODO 1 : Path class instead of UnicodeString (handle relativity...) */
 //---------------------------------------------------------------------------
 bool __fastcall IsUnixStyleWindowsPath(const UnicodeString & Path)
@@ -809,6 +810,7 @@ __fastcall TRemoteFile::TRemoteFile(TRemoteFile * ALinkedByFile):
   FTerminal = NULL;
   FDirectory = NULL;
   FIsHidden = -1;
+  FIsEncrypted = false;
 }
 //---------------------------------------------------------------------------
 __fastcall TRemoteFile::~TRemoteFile()
@@ -847,6 +849,7 @@ TRemoteFile * __fastcall TRemoteFile::Duplicate(bool Standalone) const
     COPY_FP(Type);
     COPY_FP(CyclicLink);
     COPY_FP(HumanRights);
+    COPY_FP(IsEncrypted);
     #undef COPY_FP
     if (Standalone && (!FFullFileName.IsEmpty() || (Directory != NULL)))
     {
@@ -1338,6 +1341,15 @@ void __fastcall TRemoteFile::Complete()
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TRemoteFile::SetEncrypted()
+{
+  FIsEncrypted = true;
+  if (Size > TEncryption::GetOverhead())
+  {
+    Size -= TEncryption::GetOverhead();
+  }
+}
+//---------------------------------------------------------------------------
 void __fastcall TRemoteFile::FindLinkedFile()
 {
   DebugAssert(Terminal && IsSymLink);
@@ -2534,7 +2546,8 @@ __fastcall TRemoteProperties::TRemoteProperties(const TRemoteProperties & rhp) :
   Group(rhp.Group),
   Owner(rhp.Owner),
   Modification(rhp.Modification),
-  LastAccess(rhp.Modification)
+  LastAccess(rhp.Modification),
+  Encrypt(rhp.Encrypt)
 {
 }
 //---------------------------------------------------------------------------
@@ -2547,6 +2560,7 @@ void __fastcall TRemoteProperties::Default()
   Group.Clear();
   Owner.Clear();
   Recursive = false;
+  Encrypt = false;
 }
 //---------------------------------------------------------------------------
 bool __fastcall TRemoteProperties::operator ==(const TRemoteProperties & rhp) const
@@ -2560,7 +2574,8 @@ bool __fastcall TRemoteProperties::operator ==(const TRemoteProperties & rhp) co
         (Valid.Contains(vpOwner) && (Owner != rhp.Owner)) ||
         (Valid.Contains(vpGroup) && (Group != rhp.Group)) ||
         (Valid.Contains(vpModification) && (Modification != rhp.Modification)) ||
-        (Valid.Contains(vpLastAccess) && (LastAccess != rhp.LastAccess)))
+        (Valid.Contains(vpLastAccess) && (LastAccess != rhp.LastAccess)) ||
+        (Valid.Contains(vpEncrypt) && (Encrypt != rhp.Encrypt)))
     {
       Result = false;
     }
@@ -2622,6 +2637,7 @@ TRemoteProperties __fastcall TRemoteProperties::CommonProperties(TStrings * File
 TRemoteProperties __fastcall TRemoteProperties::ChangedProperties(
   const TRemoteProperties & OriginalProperties, TRemoteProperties NewProperties)
 {
+  DebugAssert(!OriginalProperties.Valid.Contains(vpEncrypt));
   // TODO: Modification and LastAccess
   if (!NewProperties.Recursive)
   {

+ 6 - 2
source/core/RemoteFiles.h

@@ -105,6 +105,7 @@ private:
   UnicodeString FFullFileName;
   int FIsHidden;
   UnicodeString FTypeName;
+  bool FIsEncrypted;
   int __fastcall GetAttr();
   bool __fastcall GetBrokenLink();
   bool __fastcall GetIsDirectory() const;
@@ -144,6 +145,7 @@ public:
   void __fastcall ShiftTimeInSeconds(__int64 Seconds);
   bool __fastcall IsTimeShiftingApplicable();
   void __fastcall Complete();
+  void __fastcall SetEncrypted();
 
   static bool __fastcall IsTimeShiftingApplicable(TModificationFmt ModificationFmt);
   static void __fastcall ShiftTimeInSeconds(TDateTime & DateTime, TModificationFmt ModificationFmt, __int64 Seconds);
@@ -181,6 +183,7 @@ public:
   __property bool IsThisDirectory = { read = GetIsThisDirectory };
   __property bool IsInaccesibleDirectory  = { read=GetIsInaccesibleDirectory };
   __property UnicodeString Extension  = { read=GetExtension };
+  __property bool IsEncrypted  = { read = FIsEncrypted };
 };
 //---------------------------------------------------------------------------
 class TRemoteDirectoryFile : public TRemoteFile
@@ -410,8 +413,8 @@ private:
   void __fastcall SetRightUndef(TRight Right, TState value);
 };
 //---------------------------------------------------------------------------
-enum TValidProperty { vpRights, vpGroup, vpOwner, vpModification, vpLastAccess };
-typedef Set<TValidProperty, vpRights, vpLastAccess> TValidProperties;
+enum TValidProperty { vpRights, vpGroup, vpOwner, vpModification, vpLastAccess, vpEncrypt };
+typedef Set<TValidProperty, vpRights, vpEncrypt> TValidProperties;
 class TRemoteProperties
 {
 public:
@@ -423,6 +426,7 @@ public:
   TRemoteToken Owner;
   __int64 Modification; // unix time
   __int64 LastAccess; // unix time
+  bool Encrypt;
 
   __fastcall TRemoteProperties();
   __fastcall TRemoteProperties(const TRemoteProperties & rhp);

+ 1 - 1
source/core/S3FileSystem.cpp

@@ -1010,7 +1010,7 @@ void __fastcall TS3FileSystem::CopyFile(const UnicodeString AFileName, const TRe
   CheckLibS3Error(Data);
 }
 //---------------------------------------------------------------------------
-void __fastcall TS3FileSystem::CreateDirectory(const UnicodeString ADirName)
+void __fastcall TS3FileSystem::CreateDirectory(const UnicodeString & ADirName, bool /*Encrypt*/)
 {
   TOperationVisualizer Visualizer(FTerminal->UseBusyCursor);
   UnicodeString DirName = UnixExcludeTrailingBackslash(AbsolutePath(ADirName, false));

+ 1 - 1
source/core/S3FileSystem.h

@@ -72,7 +72,7 @@ public:
     const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
     const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
     unsigned int Flags, TDownloadSessionAction & Action);
-  virtual void __fastcall CreateDirectory(const UnicodeString DirName);
+  virtual void __fastcall CreateDirectory(const UnicodeString & DirName, bool Encrypt);
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   virtual void __fastcall DeleteFile(const UnicodeString FileName,
     const TRemoteFile * File, int Params,

+ 1 - 1
source/core/ScpFileSystem.cpp

@@ -1188,7 +1188,7 @@ void __fastcall TSCPFileSystem::CopyFile(const UnicodeString FileName, const TRe
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TSCPFileSystem::CreateDirectory(const UnicodeString DirName)
+void __fastcall TSCPFileSystem::CreateDirectory(const UnicodeString & DirName, bool /*Encrypt*/)
 {
   ExecCommand(fsCreateDirectory, ARRAYOFCONST((DelimitStr(DirName))));
 }

+ 1 - 1
source/core/ScpFileSystem.h

@@ -51,7 +51,7 @@ public:
     const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
     const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
     unsigned int Flags, TDownloadSessionAction & Action);
-  virtual void __fastcall CreateDirectory(const UnicodeString DirName);
+  virtual void __fastcall CreateDirectory(const UnicodeString & DirName, bool Encrypt);
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   virtual void __fastcall DeleteFile(const UnicodeString FileName,
     const TRemoteFile * File, int Params, TRmSessionAction & Action);

+ 4 - 1
source/core/Script.cpp

@@ -1418,7 +1418,10 @@ void __fastcall TScript::MkDirProc(TScriptProcParams * Parameters)
 {
   CheckSession();
 
-  FTerminal->CreateDirectory(Parameters->Param[1]);
+  TRemoteProperties Properties;
+  Properties.Valid = TValidProperties() << vpEncrypt;
+  Properties.Encrypt = FCopyParam.EncryptNewFiles;
+  FTerminal->CreateDirectory(Parameters->Param[1], &Properties);
 }
 //---------------------------------------------------------------------------
 void __fastcall TScript::GetProc(TScriptProcParams * Parameters)

+ 69 - 1
source/core/SessionData.cpp

@@ -162,6 +162,8 @@ void __fastcall TSessionData::Default()
   WinTitle = L"";
   InternalEditorEncoding = -1;
 
+  EncryptKey = UnicodeString();
+
   ProxyMethod = ::pmNone;
   ProxyHost = L"proxy";
   ProxyPort = ProxyPortNumber;
@@ -420,6 +422,9 @@ void __fastcall TSessionData::NonPersistant()
   PROPERTY(MaxTlsVersion); \
   \
   PROPERTY(WinTitle); \
+  \
+  PROPERTY(EncryptKey); \
+  \
   PROPERTY(CustomParam1); \
   PROPERTY(CustomParam2);
 #define META_PROPERTIES \
@@ -769,6 +774,16 @@ void __fastcall TSessionData::DoLoad(THierarchicalStorage * Storage, bool PuttyI
   MinTlsVersion = static_cast<TTlsVersion>(Storage->ReadInteger(L"MinTlsVersion", MinTlsVersion));
   MaxTlsVersion = static_cast<TTlsVersion>(Storage->ReadInteger(L"MaxTlsVersion", MaxTlsVersion));
 
+  if (Storage->ValueExists(L"EncryptKeyPlain"))
+  {
+    EncryptKey = Storage->ReadString(L"EncryptKeyPlain", EncryptKey);
+    RewritePassword = true;
+  }
+  else
+  {
+    FEncryptKey = Storage->ReadStringAsBinaryData(L"EncryptKey", FEncryptKey);
+  }
+
   IsWorkspace = Storage->ReadBool(L"IsWorkspace", IsWorkspace);
   Link = Storage->ReadString(L"Link", Link);
 
@@ -864,6 +879,11 @@ void __fastcall TSessionData::Load(THierarchicalStorage * Storage, bool PuttyImp
         {
           Storage->WriteBinaryDataAsString(L"TunnelPassword", FTunnelPassword);
         }
+        Storage->DeleteValue(L"EncryptKeyPlain");
+        if (!EncryptKey.IsEmpty())
+        {
+          Storage->WriteBinaryDataAsString(L"EncryptKey", FEncryptKey);
+        }
         Storage->CloseSubKey();
       }
     }
@@ -1479,6 +1499,31 @@ void __fastcall TSessionData::SavePasswords(THierarchicalStorage * Storage, bool
         Storage->DeleteValue(L"TunnelPassword");
       }
     }
+
+    if (DoNotEncryptPasswords)
+    {
+      if (!FEncryptKey.IsEmpty())
+      {
+        Storage->WriteString(L"EncryptKeyPlain", EncryptKey);
+      }
+      else
+      {
+        Storage->DeleteValue(L"EncryptKeyPlain");
+      }
+      Storage->DeleteValue(L"EncryptKey");
+    }
+    else
+    {
+      if (!FEncryptKey.IsEmpty())
+      {
+        Storage->WriteBinaryDataAsString(L"EncryptKey", StronglyRecryptPassword(FEncryptKey, UserName+HostName));
+      }
+      else
+      {
+        Storage->DeleteValue(L"EncryptKey");
+      }
+      Storage->DeleteValue(L"EncryptKeyPlain");
+    }
   }
 }
 //---------------------------------------------------------------------
@@ -1489,6 +1534,7 @@ void __fastcall TSessionData::RecryptPasswords()
   ProxyPassword = ProxyPassword;
   TunnelPassword = TunnelPassword;
   Passphrase = Passphrase;
+  EncryptKey = EncryptKey;
 }
 //---------------------------------------------------------------------
 bool __fastcall TSessionData::HasPassword()
@@ -1656,7 +1702,8 @@ bool __fastcall TSessionData::MaskPasswordInOptionParameter(const UnicodeString
       if (SameText(Key, L"ProxyPassword") ||
           SameText(Key, L"ProxyPasswordEnc") ||
           SameText(Key, L"TunnelPassword") ||
-          SameText(Key, L"TunnelPasswordPlain"))
+          SameText(Key, L"TunnelPasswordPlain") ||
+          SameText(Key, L"EncryptKey"))
       {
         Param = Key + L"=" + PasswordMask;
         Result = true;
@@ -1684,6 +1731,10 @@ void __fastcall TSessionData::MaskPasswords()
   {
     TunnelPassword = PasswordMask;
   }
+  if (!EncryptKey.IsEmpty())
+  {
+    EncryptKey = PasswordMask;
+  }
   if (!Passphrase.IsEmpty())
   {
     Passphrase = PasswordMask;
@@ -2233,6 +2284,7 @@ void __fastcall TSessionData::SetHostName(UnicodeString value)
     // HostName is key for password encryption
     UnicodeString XPassword = Password;
     UnicodeString XNewPassword = Password;
+    UnicodeString XEncryptKey = EncryptKey;
 
     // This is now hardly used as hostname is parsed directly on login dialog.
     // But can be used when importing sites from PuTTY, as it allows same format too.
@@ -2247,8 +2299,10 @@ void __fastcall TSessionData::SetHostName(UnicodeString value)
 
     Password = XPassword;
     NewPassword = XNewPassword;
+    EncryptKey = XEncryptKey;
     Shred(XPassword);
     Shred(XNewPassword);
+    Shred(XEncryptKey);
   }
 }
 //---------------------------------------------------------------------
@@ -2300,11 +2354,14 @@ void __fastcall TSessionData::SetUserName(UnicodeString value)
     // UserName is key for password encryption
     UnicodeString XPassword = Password;
     UnicodeString XNewPassword = NewPassword;
+    UnicodeString XEncryptKey = EncryptKey;
     SET_SESSION_PROPERTY(UserName);
     Password = XPassword;
     NewPassword = XNewPassword;
+    EncryptKey = XEncryptKey;
     Shred(XPassword);
     Shred(XNewPassword);
+    Shred(XEncryptKey);
   }
 }
 //---------------------------------------------------------------------
@@ -3848,6 +3905,17 @@ void __fastcall TSessionData::SetWinTitle(UnicodeString value)
   SET_SESSION_PROPERTY(WinTitle);
 }
 //---------------------------------------------------------------------
+UnicodeString __fastcall TSessionData::GetEncryptKey() const
+{
+  return DecryptPassword(FEncryptKey, UserName+HostName);
+}
+//---------------------------------------------------------------------
+void __fastcall TSessionData::SetEncryptKey(UnicodeString avalue)
+{
+  RawByteString value = EncryptPassword(avalue, UserName+HostName);
+  SET_SESSION_PROPERTY(EncryptKey);
+}
+//---------------------------------------------------------------------
 UnicodeString __fastcall TSessionData::GetInfoTip()
 {
   if (UsesSsh)

+ 6 - 0
source/core/SessionData.h

@@ -224,6 +224,7 @@ private:
   bool FOverrideCachedHostKey;
   UnicodeString FNote;
   UnicodeString FWinTitle;
+  RawByteString FEncryptKey;
 
   UnicodeString FOrigHostName;
   int FOrigPortNumber;
@@ -394,6 +395,9 @@ private:
   void __fastcall SetHostKey(UnicodeString value);
   void __fastcall SetNote(UnicodeString value);
   void __fastcall SetWinTitle(UnicodeString value);
+  UnicodeString __fastcall GetEncryptKey() const;
+  void __fastcall SetEncryptKey(UnicodeString value);
+
   TDateTime __fastcall GetTimeoutDT();
   void __fastcall SavePasswords(THierarchicalStorage * Storage, bool PuttyExport, bool DoNotEncryptPasswords);
   UnicodeString __fastcall GetLocalName();
@@ -641,6 +645,8 @@ public:
   __property bool OverrideCachedHostKey = { read = FOverrideCachedHostKey };
   __property UnicodeString Note = { read = FNote, write = SetNote };
   __property UnicodeString WinTitle = { read = FWinTitle, write = SetWinTitle };
+  __property UnicodeString EncryptKey = { read = GetEncryptKey, write = SetEncryptKey };
+
   __property UnicodeString StorageKey = { read = GetStorageKey };
   __property UnicodeString SiteKey = { read = GetSiteKey };
   __property UnicodeString OrigHostName = { read = FOrigHostName };

+ 127 - 58
source/core/SftpFileSystem.cpp

@@ -12,6 +12,7 @@
 #include "TextsCore.h"
 #include "HelpCore.h"
 #include "SecureShell.h"
+#include "Cryptography.h"
 #include <WideStrUtils.hpp>
 #include <limits>
 
@@ -1492,8 +1493,9 @@ private:
 class TSFTPUploadQueue : public TSFTPAsynchronousQueue
 {
 public:
-  TSFTPUploadQueue(TSFTPFileSystem * AFileSystem) :
-    TSFTPAsynchronousQueue(AFileSystem)
+  TSFTPUploadQueue(TSFTPFileSystem * AFileSystem, TEncryption * Encryption) :
+    TSFTPAsynchronousQueue(AFileSystem),
+    FEncryption(Encryption)
   {
     FStream = NULL;
     OperationProgress = NULL;
@@ -1568,6 +1570,11 @@ protected:
             (int(FTransferred), int(BlockBuf.Size))));
         }
 
+        if (FEncryption != NULL)
+        {
+          FEncryption->Encrypt(BlockBuf, (FStream->Position >= FStream->Size));
+        }
+
         Request->ChangeType(SSH_FXP_WRITE);
         Request->AddString(FHandle);
         Request->AddInt64(FTransferred);
@@ -1642,6 +1649,7 @@ private:
   RawByteString FHandle;
   bool FConvertToken;
   int FConvertParams;
+  TEncryption * FEncryption;
 };
 //---------------------------------------------------------------------------
 class TSFTPLoadFilesPropertiesQueue : public TSFTPFixedLenQueue
@@ -1692,8 +1700,7 @@ protected:
       if (Result)
       {
         Request->ChangeType(SSH_FXP_LSTAT);
-        Request->AddPathString(FFileSystem->LocalCanonify(File->FileName),
-          FFileSystem->FUtfStrings);
+        FFileSystem->AddPathString(*Request, FFileSystem->LocalCanonify(File->FileName));
         if (FFileSystem->FVersion >= 4)
         {
           Request->AddCardinal(
@@ -1775,8 +1782,7 @@ protected:
 
         Request->ChangeType(SSH_FXP_EXTENDED);
         Request->AddString(SFTP_EXT_CHECK_FILE_NAME);
-        Request->AddPathString(FFileSystem->LocalCanonify(File->FullFileName),
-          FFileSystem->FUtfStrings);
+        FFileSystem->AddPathString(*Request, FFileSystem->LocalCanonify(File->FullFileName));
         Request->AddString(FAlg);
         Request->AddInt64(0); // offset
         Request->AddInt64(0); // length (0 = till end)
@@ -2016,7 +2022,7 @@ void __fastcall TSFTPFileSystem::Idle()
     {
       FTerminal->LogEvent(L"Sending dummy command to keep session alive.");
       TSFTPPacket Packet(SSH_FXP_REALPATH);
-      Packet.AddPathString(L"/", FUtfStrings);
+      AddPathString(Packet, L"/");
       SendPacketAndReceiveResponse(&Packet, &Packet);
     }
     else
@@ -2055,12 +2061,14 @@ bool __fastcall TSFTPFileSystem::IsCapable(int Capability) const
     case fcSecondaryShell:
     case fcRemoveCtrlZUpload:
     case fcRemoveBOMUpload:
-    case fcMoveToQueue:
     case fcPreservingTimestampDirs:
+      return true;
+
+    case fcMoveToQueue:
     case fcResumeSupport:
     case fsSkipTransfer:
     case fsParallelTransfers:
-      return true;
+      return !FTerminal->IsEncryptingFiles();
 
     case fcRename:
     case fcRemoteMove:
@@ -2068,7 +2076,7 @@ bool __fastcall TSFTPFileSystem::IsCapable(int Capability) const
 
     case fcSymbolicLink:
     case fcResolveSymlink:
-      return (FVersion >= 3);
+      return (FVersion >= 3) && !FTerminal->IsEncryptingFiles();
 
     case fcModeChanging:
     case fcModeChangingUpload:
@@ -2087,11 +2095,13 @@ bool __fastcall TSFTPFileSystem::IsCapable(int Capability) const
           FLAGSET(FSupport->AttributeMask, SSH_FILEXFER_ATTR_OWNERGROUP)));
 
     case fcNativeTextMode:
-      return (FVersion >= 4);
+      return !FTerminal->IsEncryptingFiles() && (FVersion >= 4);
 
     case fcTextMode:
-      return (FVersion >= 4) ||
-        strcmp(GetEOL(), EOLToStr(FTerminal->Configuration->LocalEOLType)) != 0;
+      return
+        !FTerminal->IsEncryptingFiles() &&
+        ((FVersion >= 4) ||
+         (strcmp(GetEOL(), EOLToStr(FTerminal->Configuration->LocalEOLType)) != 0));
 
     case fcUserGroupListing:
       return SupportsExtension(SFTP_EXT_OWNER_GROUP);
@@ -2124,13 +2134,14 @@ bool __fastcall TSFTPFileSystem::IsCapable(int Capability) const
 
     case fcCalculatingChecksum:
       return
-        // Specification says that "check-file" should be announced,
-        // yet Vandyke VShell (as of 4.0.3) announce "check-file-name"
-        // https://forums.vandyke.com/showthread.php?t=11597
-        SupportsExtension(SFTP_EXT_CHECK_FILE) ||
-        SupportsExtension(SFTP_EXT_CHECK_FILE_NAME) ||
-        // see above
-        (FSecureShell->SshImplementation == sshiBitvise);
+        !FTerminal->IsEncryptingFiles() &&
+        (// Specification says that "check-file" should be announced,
+         // yet Vandyke VShell (as of 4.0.3) announce "check-file-name"
+         // https://forums.vandyke.com/showthread.php?t=11597
+         SupportsExtension(SFTP_EXT_CHECK_FILE) ||
+         SupportsExtension(SFTP_EXT_CHECK_FILE_NAME) ||
+         // see above
+         (FSecureShell->SshImplementation == sshiBitvise));
 
     case fcRemoteCopy:
       return
@@ -2189,7 +2200,8 @@ unsigned long __fastcall TSFTPFileSystem::TransferBlockSize(unsigned long Overhe
   const unsigned long SFTPPacketOverhead = 4 + 4 + 1;
   unsigned long AMaxPacketSize = FSecureShell->MaxPacketSize();
   bool MaxPacketSizeValid = (AMaxPacketSize > 0);
-  unsigned long Result = OperationProgress->CPS();
+  unsigned long CPSRounded = TEncryption::RoundToBlock(OperationProgress->CPS());
+  unsigned long Result = CPSRounded;
 
   if ((MaxPacketSize > 0) &&
       ((MaxPacketSize < AMaxPacketSize) || !MaxPacketSizeValid))
@@ -2229,6 +2241,11 @@ unsigned long __fastcall TSFTPFileSystem::TransferBlockSize(unsigned long Overhe
       AMaxPacketSize -= Overhead;
       if (Result > AMaxPacketSize)
       {
+        unsigned int MaxPacketSizeRounded = TEncryption::RoundToBlockDown(AMaxPacketSize);
+        if (MaxPacketSizeRounded > 0)
+        {
+          AMaxPacketSize = MaxPacketSizeRounded;
+        }
         Result = AMaxPacketSize;
       }
     }
@@ -2711,7 +2728,7 @@ UnicodeString __fastcall TSFTPFileSystem::RealPath(const UnicodeString Path)
     }
 
     TSFTPPacket Packet(SSH_FXP_REALPATH);
-    Packet.AddPathString(Path, FUtfStrings);
+    AddPathString(Packet, Path);
 
     // In SFTP-6 new optional field control-byte is added that defaults to
     // SSH_FXP_REALPATH_NO_CHECK=0x01, meaning it won't fail, if the path does not exist.
@@ -2752,6 +2769,7 @@ UnicodeString __fastcall TSFTPFileSystem::RealPath(const UnicodeString Path)
     }
 
     UnicodeString RealDir = UnixExcludeTrailingBackslash(Packet.GetPathString(FUtfStrings));
+    RealDir = FTerminal->DecryptFileName(RealDir);
     // ignore rest of SSH_FXP_NAME packet
 
     if (FTerminal->Configuration->ActualLogProtocol >= 0)
@@ -2896,8 +2914,7 @@ UnicodeString __fastcall TSFTPFileSystem::GetHomeDirectory()
 void __fastcall TSFTPFileSystem::LoadFile(TRemoteFile * File, TSFTPPacket * Packet,
   bool Complete)
 {
-  Packet->GetFile(File, FVersion, FTerminal->SessionData->DSTMode,
-    FUtfStrings, FSignedTS, Complete);
+  Packet->GetFile(File, FVersion, FTerminal->SessionData->DSTMode, FUtfStrings, FSignedTS, Complete);
 }
 //---------------------------------------------------------------------------
 TRemoteFile * __fastcall TSFTPFileSystem::LoadFile(TSFTPPacket * Packet,
@@ -3333,7 +3350,7 @@ void __fastcall TSFTPFileSystem::TryOpenDirectory(const UnicodeString Directory)
     // traverse-only (chmod 110) directories.
     // This is workaround for http://www.ftpshell.com/
     TSFTPPacket Packet(SSH_FXP_OPENDIR);
-    Packet.AddPathString(UnixExcludeTrailingBackslash(Directory), FUtfStrings);
+    AddPathString(Packet, UnixExcludeTrailingBackslash(Directory));
     SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_HANDLE);
     RawByteString Handle = Packet.GetFileHandle();
     Packet.ChangeType(SSH_FXP_CLOSE);
@@ -3387,7 +3404,7 @@ void __fastcall TSFTPFileSystem::ReadDirectory(TRemoteFileList * FileList)
 
   try
   {
-    Packet.AddPathString(Directory, FUtfStrings);
+    AddPathString(Packet, Directory);
 
     SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_HANDLE);
 
@@ -3434,6 +3451,18 @@ void __fastcall TSFTPFileSystem::ReadDirectory(TRemoteFileList * FileList)
         for (unsigned long Index = 0; !isEOF && (Index < Count); Index++)
         {
           File = LoadFile(&ListingPacket, NULL, L"", FileList);
+          FileList->AddFile(File);
+          if (FTerminal->IsEncryptingFiles() && // optimization
+              !File->IsParentDirectory && !File->IsThisDirectory)
+          {
+            UnicodeString FullFileName = UnixExcludeTrailingBackslash(File->FullFileName);
+            UnicodeString FileName = UnixExtractFileName(FTerminal->DecryptFileName(FullFileName));
+            if (File->FileName != FileName)
+            {
+              File->SetEncrypted();
+            }
+            File->FileName = FileName;
+          }
           if (FTerminal->Configuration->ActualLogProtocol >= 1)
           {
             FTerminal->LogEvent(FORMAT(L"Read file '%s' from listing", (File->FileName)));
@@ -3446,7 +3475,6 @@ void __fastcall TSFTPFileSystem::ReadDirectory(TRemoteFileList * FileList)
           {
             HasParentDirectory = true;
           }
-          FileList->AddFile(File);
           Total++;
 
           if (Total % 10 == 0)
@@ -3575,14 +3603,14 @@ void __fastcall TSFTPFileSystem::ReadSymlink(TRemoteFile * SymlinkFile,
     SymlinkFile->Directory != NULL ? SymlinkFile->FullFileName : SymlinkFile->FileName);
 
   TSFTPPacket ReadLinkPacket(SSH_FXP_READLINK);
-  ReadLinkPacket.AddPathString(FileName, FUtfStrings);
+  AddPathString(ReadLinkPacket, FileName);
   SendPacket(&ReadLinkPacket);
   ReserveResponse(&ReadLinkPacket, &ReadLinkPacket);
 
   // send second request before reading response to first one
   // (performance benefit)
   TSFTPPacket AttrsPacket(SSH_FXP_STAT);
-  AttrsPacket.AddPathString(FileName, FUtfStrings);
+  AddPathString(AttrsPacket, FileName);
   if (FVersion >= 4)
   {
     AttrsPacket.AddCardinal(SSH_FILEXFER_ATTR_COMMON);
@@ -3595,7 +3623,7 @@ void __fastcall TSFTPFileSystem::ReadSymlink(TRemoteFile * SymlinkFile,
   {
     FTerminal->FatalError(NULL, LoadStr(SFTP_NON_ONE_FXP_NAME_PACKET));
   }
-  SymlinkFile->LinkTo = ReadLinkPacket.GetPathString(FUtfStrings);
+  SymlinkFile->LinkTo = FTerminal->DecryptFileName(ReadLinkPacket.GetPathString(FUtfStrings));
   FTerminal->LogEvent(FORMAT(L"Link resolved to \"%s\".", (SymlinkFile->LinkTo)));
 
   ReceiveResponse(&AttrsPacket, &AttrsPacket, SSH_FXP_ATTRS);
@@ -3661,13 +3689,17 @@ void __fastcall TSFTPFileSystem::CustomReadFile(const UnicodeString FileName,
     SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME |
     SSH_FILEXFER_ATTR_OWNERGROUP;
   TSFTPPacket Packet(Type);
-  Packet.AddPathString(LocalCanonify(FileName), FUtfStrings);
+  AddPathString(Packet, LocalCanonify(FileName));
   SendCustomReadFile(&Packet, &Packet, Flags);
   ReceiveResponse(&Packet, &Packet, SSH_FXP_ATTRS, AllowStatus);
 
   if (Packet.Type == SSH_FXP_ATTRS)
   {
     File = LoadFile(&Packet, ALinkedByFile, UnixExtractFileName(FileName));
+    if (FTerminal->IsFileEncrypted(FileName))
+    {
+      File->SetEncrypted();
+    }
   }
   else
   {
@@ -3680,7 +3712,7 @@ void __fastcall TSFTPFileSystem::DoDeleteFile(const UnicodeString FileName, unsi
 {
   TSFTPPacket Packet(Type);
   UnicodeString RealFileName = LocalCanonify(FileName);
-  Packet.AddPathString(RealFileName, FUtfStrings);
+  AddPathString(Packet, RealFileName);
   SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS);
 }
 //---------------------------------------------------------------------------
@@ -3705,7 +3737,8 @@ void __fastcall TSFTPFileSystem::RenameFile(const UnicodeString FileName, const
 {
   TSFTPPacket Packet(SSH_FXP_RENAME);
   UnicodeString RealName = LocalCanonify(FileName);
-  Packet.AddPathString(RealName, FUtfStrings);
+  bool Encrypted = FTerminal->IsFileEncrypted(RealName);
+  AddPathString(Packet, RealName);
   UnicodeString TargetName;
   if (UnixExtractFilePath(NewName).IsEmpty())
   {
@@ -3716,7 +3749,7 @@ void __fastcall TSFTPFileSystem::RenameFile(const UnicodeString FileName, const
   {
     TargetName = LocalCanonify(NewName);
   }
-  Packet.AddPathString(TargetName, FUtfStrings);
+  AddPathString(Packet, TargetName, Encrypted);
   if (FVersion >= 5)
   {
     Packet.AddCardinal(0);
@@ -3731,17 +3764,19 @@ void __fastcall TSFTPFileSystem::CopyFile(const UnicodeString FileName, const TR
   DebugAssert(SupportsExtension(SFTP_EXT_COPY_FILE) || (FSecureShell->SshImplementation == sshiBitvise));
   TSFTPPacket Packet(SSH_FXP_EXTENDED);
   Packet.AddString(SFTP_EXT_COPY_FILE);
-  Packet.AddPathString(Canonify(FileName), FUtfStrings);
-  Packet.AddPathString(Canonify(NewName), FUtfStrings);
+  UnicodeString RealName = Canonify(FileName);
+  bool Encrypted = FTerminal->IsFileEncrypted(RealName);
+  AddPathString(Packet, RealName);
+  AddPathString(Packet, Canonify(NewName), Encrypted);
   Packet.AddBool(false);
   SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS);
 }
 //---------------------------------------------------------------------------
-void __fastcall TSFTPFileSystem::CreateDirectory(const UnicodeString DirName)
+void __fastcall TSFTPFileSystem::CreateDirectory(const UnicodeString & DirName, bool Encrypt)
 {
   TSFTPPacket Packet(SSH_FXP_MKDIR);
   UnicodeString CanonifiedName = Canonify(DirName);
-  Packet.AddPathString(CanonifiedName, FUtfStrings);
+  AddPathString(Packet, CanonifiedName, Encrypt);
   Packet.AddProperties(NULL, 0, true, FVersion, FUtfStrings, NULL);
   SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS);
 }
@@ -3829,15 +3864,16 @@ void __fastcall TSFTPFileSystem::CreateLink(const UnicodeString FileName,
     FinalPointTo = Canonify(PointTo);
   }
 
+  // creating symlinks is not allowed when encryption is enabled, so we are not considering encryption here
   if (!Buggy)
   {
-    Packet.AddPathString(FinalFileName, FUtfStrings);
-    Packet.AddPathString(FinalPointTo, FUtfStrings);
+    AddPathString(Packet, FinalFileName);
+    AddPathString(Packet, FinalPointTo);
   }
   else
   {
-    Packet.AddPathString(FinalPointTo, FUtfStrings);
-    Packet.AddPathString(FinalFileName, FUtfStrings);
+    AddPathString(Packet, FinalPointTo);
+    AddPathString(Packet, FinalFileName);
   }
 
   if (UseLink)
@@ -3894,7 +3930,7 @@ void __fastcall TSFTPFileSystem::ChangeFileProperties(const UnicodeString FileNa
     }
 
     TSFTPPacket Packet(SSH_FXP_SETSTAT);
-    Packet.AddPathString(RealFileName, FUtfStrings);
+    AddPathString(Packet, RealFileName);
     Packet.AddProperties(&Properties, *File->Rights, File->IsDirectory, FVersion, FUtfStrings, &Action);
     SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_STATUS);
   }
@@ -4154,7 +4190,7 @@ void __fastcall TSFTPFileSystem::SpaceAvailable(const UnicodeString Path,
   {
     TSFTPPacket Packet(SSH_FXP_EXTENDED);
     Packet.AddString(SFTP_EXT_SPACE_AVAILABLE);
-    Packet.AddPathString(LocalCanonify(Path), FUtfStrings);
+    AddPathString(Packet, LocalCanonify(Path));
 
     SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_EXTENDED_REPLY);
 
@@ -4185,7 +4221,7 @@ void __fastcall TSFTPFileSystem::SpaceAvailable(const UnicodeString Path,
     // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
     TSFTPPacket Packet(SSH_FXP_EXTENDED);
     Packet.AddString(SFTP_EXT_STATVFS);
-    Packet.AddPathString(LocalCanonify(Path), FUtfStrings);
+    AddPathString(Packet, LocalCanonify(Path));
 
     SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_EXTENDED_REPLY);
 
@@ -4257,7 +4293,7 @@ void __fastcall TSFTPFileSystem::SFTPConfirmOverwrite(
   const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
   TSFTPOverwriteMode & OverwriteMode, const TOverwriteFileParams * FileParams)
 {
-  bool CanAppend = (FVersion < 4) || !OperationProgress->AsciiTransfer;
+  bool CanAppend = !FTerminal->IsEncryptingFiles() && ((FVersion < 4) || !OperationProgress->AsciiTransfer);
   unsigned int Answer;
 
   {
@@ -4459,9 +4495,11 @@ void __fastcall TSFTPFileSystem::Source(
   __int64 ResumeOffset;
 
   // should we check for interrupted transfer?
-  ResumeAllowed = !OperationProgress->AsciiTransfer &&
+  ResumeAllowed =
+    !OperationProgress->AsciiTransfer &&
     CopyParam->AllowResume(OperationProgress->LocalSize) &&
-    IsCapable(fcRename);
+    IsCapable(fcRename) &&
+    !FTerminal->IsEncryptingFiles();
 
   TOpenRemoteFileParams OpenParams;
   OpenParams.OverwriteMode = omOverwrite;
@@ -4622,7 +4660,7 @@ void __fastcall TSFTPFileSystem::Source(
   TRights Rights;
   if (SetProperties)
   {
-    PropertiesRequest.AddPathString(DestFullName, FUtfStrings);
+    AddPathString(PropertiesRequest, DestFullName);
     if (CopyParam->PreserveRights)
     {
       Rights = CopyParam->RemoteFileRights(Handle.Attrs);
@@ -4661,7 +4699,9 @@ void __fastcall TSFTPFileSystem::Source(
       OperationProgress->AddResumed(ResumeOffset);
     }
 
-    TSFTPUploadQueue Queue(this);
+    TEncryption Encryption(FTerminal->GetEncryptKey());
+    bool Encrypt = FTerminal->IsFileEncrypted(DestFullName, CopyParam->EncryptNewFiles);
+    TSFTPUploadQueue Queue(this, (Encrypt ? &Encryption : NULL));
     try
     {
       int ConvertParams =
@@ -4851,11 +4891,11 @@ void __fastcall TSFTPFileSystem::Source(
 }
 //---------------------------------------------------------------------------
 RawByteString __fastcall TSFTPFileSystem::SFTPOpenRemoteFile(
-  const UnicodeString & FileName, unsigned int OpenType, __int64 Size)
+  const UnicodeString & FileName, unsigned int OpenType, bool EncryptNewFiles, __int64 Size)
 {
   TSFTPPacket Packet(SSH_FXP_OPEN);
 
-  Packet.AddPathString(FileName, FUtfStrings);
+  AddPathString(Packet, FileName, EncryptNewFiles);
   if (FVersion < 5)
   {
     Packet.AddCardinal(OpenType);
@@ -4945,8 +4985,8 @@ int __fastcall TSFTPFileSystem::SFTPOpenRemote(void * AOpenParams, void * /*Para
         OpenType |= SSH_FXF_TEXT;
       }
 
-      OpenParams->RemoteFileHandle = SFTPOpenRemoteFile(
-        OpenParams->RemoteFileName, OpenType, OperationProgress->LocalSize);
+      OpenParams->RemoteFileHandle =
+        SFTPOpenRemoteFile(OpenParams->RemoteFileName, OpenType, OpenParams->CopyParam->EncryptNewFiles, OperationProgress->LocalSize);
 
       Success = true;
     }
@@ -5150,6 +5190,19 @@ void __fastcall TSFTPFileSystem::DirectorySunk(
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TSFTPFileSystem::WriteLocalFile(
+  TStream * FileStream, TFileBuffer & BlockBuf, const UnicodeString & LocalFileName,
+  TFileOperationProgressType * OperationProgress)
+{
+  FILE_OPERATION_LOOP_BEGIN
+  {
+    BlockBuf.WriteToStream(FileStream, BlockBuf.Size);
+  }
+  FILE_OPERATION_LOOP_END(FMTLOAD(WRITE_ERROR, (LocalFileName)));
+
+  OperationProgress->AddLocallyUsed(BlockBuf.Size);
+}
+//---------------------------------------------------------------------------
 void __fastcall TSFTPFileSystem::Sink(
   const UnicodeString & FileName, const TRemoteFile * File,
   const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
@@ -5371,6 +5424,8 @@ void __fastcall TSFTPFileSystem::Sink(
         unsigned long DataLen = 0;
         unsigned long BlockSize;
         bool ConvertToken = false;
+        TEncryption Encryption(FTerminal->GetEncryptKey());
+        bool Decrypt = FTerminal->IsFileEncrypted(FileName);
 
         while (!Eof)
         {
@@ -5459,13 +5514,12 @@ void __fastcall TSFTPFileSystem::Sink(
               OperationProgress->SetLocalSize(OperationProgress->LocalSize - PrevBlockSize + BlockBuf.Size);
             }
 
-            FILE_OPERATION_LOOP_BEGIN
+            if (Decrypt)
             {
-              BlockBuf.WriteToStream(FileStream, BlockBuf.Size);
+              Encryption.Decrypt(BlockBuf);
             }
-            FILE_OPERATION_LOOP_END(FMTLOAD(WRITE_ERROR, (LocalFileName)));
 
-            OperationProgress->AddLocallyUsed(BlockBuf.Size);
+            WriteLocalFile(FileStream, BlockBuf, LocalFileName, OperationProgress);
           }
 
           if (OperationProgress->Cancel != csContinue)
@@ -5485,6 +5539,15 @@ void __fastcall TSFTPFileSystem::Sink(
         {
           FTerminal->LogEvent(FORMAT(L"%d requests to fill %d data gaps were issued.", (GapFillCount, GapCount)));
         }
+
+        if (Decrypt)
+        {
+          TFileBuffer BlockBuf;
+          if (Encryption.DecryptEnd(BlockBuf))
+          {
+            WriteLocalFile(FileStream, BlockBuf, LocalFileName, OperationProgress);
+          }
+        }
       }
       __finally
       {
@@ -5580,3 +5643,9 @@ void __fastcall TSFTPFileSystem::ClearCaches()
 {
   // noop
 }
+//---------------------------------------------------------------------------
+void TSFTPFileSystem::AddPathString(TSFTPPacket & Packet, const UnicodeString & Value, bool EncryptNewFiles)
+{
+  UnicodeString EncryptedPath = FTerminal->EncryptFileName(Value, EncryptNewFiles);
+  Packet.AddPathString(EncryptedPath, FUtfStrings);
+}

+ 7 - 2
source/core/SftpFileSystem.h

@@ -8,6 +8,7 @@ class TSFTPPacket;
 class TOverwriteFileParams;
 struct TSFTPSupport;
 class TSecureShell;
+class TEncryption;
 //---------------------------------------------------------------------------
 enum TSFTPOverwriteMode { omOverwrite, omAppend, omResume };
 extern const int SFTPMaxVersion;
@@ -64,7 +65,7 @@ public:
     const UnicodeString & TargetDir, UnicodeString & DestFileName, int Attrs,
     const TCopyParamType * CopyParam, int Params, TFileOperationProgressType * OperationProgress,
     unsigned int Flags, TDownloadSessionAction & Action);
-  virtual void __fastcall CreateDirectory(const UnicodeString DirName);
+  virtual void __fastcall CreateDirectory(const UnicodeString & DirName, bool Encrypt);
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   virtual void __fastcall DeleteFile(const UnicodeString FileName,
     const TRemoteFile * File, int Params, TRmSessionAction & Action);
@@ -172,7 +173,7 @@ protected:
     TFileOperationProgressType * OperationProgress, unsigned int Flags,
     TUploadSessionAction & Action, bool & ChildError);
   RawByteString __fastcall SFTPOpenRemoteFile(const UnicodeString & FileName,
-    unsigned int OpenType, __int64 Size = -1);
+    unsigned int OpenType, bool EncryptNewFiles = false, __int64 Size = -1);
   int __fastcall SFTPOpenRemote(void * AOpenParams, void * Param2);
   void __fastcall SFTPCloseRemote(const RawByteString Handle,
     const UnicodeString FileName, TFileOperationProgressType * OperationProgress,
@@ -193,6 +194,10 @@ protected:
     TFileOperationProgressType * OperationProgress);
   inline int __fastcall PacketLength(unsigned char * LenBuf, int ExpectedType);
   void __fastcall Progress(TFileOperationProgressType * OperationProgress);
+  void AddPathString(TSFTPPacket & Packet, const UnicodeString & Value, bool EncryptNewFiles = false);
+  void __fastcall WriteLocalFile(
+    TStream * FileStream, TFileBuffer & BlockBuf, const UnicodeString & LocalFileName,
+    TFileOperationProgressType * OperationProgress);
 };
 //---------------------------------------------------------------------------
 #endif // SftpFileSystemH

+ 124 - 11
source/core/Terminal.cpp

@@ -24,6 +24,7 @@
 #include "HelpCore.h"
 #include "CoreMain.h"
 #include "Queue.h"
+#include "Cryptography.h"
 #include <openssl/pkcs12.h>
 #include <openssl/err.h>
 
@@ -1258,6 +1259,8 @@ void __fastcall TTerminal::Open()
   ReflectSettings();
   try
   {
+    FEncryptKey = HexToBytes(FSessionData->EncryptKey);
+
     DoInformation(L"", true, 1);
     try
     {
@@ -2349,6 +2352,8 @@ void __fastcall TTerminal::ClearCaches()
     FCommandSession->ClearCaches();
   }
   FFileSystem->ClearCaches();
+  FEncryptedFileNames.clear();
+  FFoldersScannedForEncryptedFiles.clear();
 }
 //---------------------------------------------------------------------------
 void __fastcall TTerminal::ClearCachedFileList(const UnicodeString Path,
@@ -3426,6 +3431,12 @@ void __fastcall TTerminal::CustomReadDirectory(TRemoteFileList * FileList)
   {
     try
     {
+      // Record even an attempt, to avoid listing a directory over and over again, when it is not readable actually
+      if (IsEncryptingFiles())
+      {
+        FFoldersScannedForEncryptedFiles.insert(FileList->Directory);
+      }
+
       FFileSystem->ReadDirectory(FileList);
     }
     catch (Exception & E)
@@ -3887,11 +3898,12 @@ TUsableCopyParamAttrs __fastcall TTerminal::UsableCopyParamAttrs(int Params)
     FLAGMASK(!IsCapable[fcRemoveCtrlZUpload], cpaNoRemoveCtrlZ) |
     FLAGMASK(!IsCapable[fcRemoveBOMUpload], cpaNoRemoveBOM) |
     FLAGMASK(!IsCapable[fcPreservingTimestampDirs], cpaNoPreserveTimeDirs) |
-    FLAGMASK(!IsCapable[fcResumeSupport], cpaNoResumeSupport);
+    FLAGMASK(!IsCapable[fcResumeSupport], cpaNoResumeSupport) |
+    FLAGMASK(!IsEncryptingFiles(), cpaNoEncryptNewFiles);
   Result.Download = Result.General | cpaNoClearArchive |
     cpaNoIgnorePermErrors |
     // May be already set in General flags, but it's unconditional here
-    cpaNoRights | cpaNoRemoveCtrlZ | cpaNoRemoveBOM;
+    cpaNoRights | cpaNoRemoveCtrlZ | cpaNoRemoveBOM | cpaNoEncryptNewFiles;
   Result.Upload = Result.General | cpaNoPreserveReadOnly |
     FLAGMASK(!IsCapable[fcPreservingTimestampUpload], cpaNoPreserveTime);
   return Result;
@@ -3985,6 +3997,8 @@ void __fastcall TTerminal::DeleteFile(UnicodeString FileName,
     LogEvent(FORMAT(L"Deleting file \"%s\".", (FileName)));
     FileModified(File, FileName, true);
     DoDeleteFile(FileName, File, Params);
+    // Forget if file was or was not encrypted and use user preferences, if we ever recreate it.
+    FEncryptedFileNames.erase(AbsolutePath(FileName, true));
     ReactOnCommand(fsDeleteFile);
   }
 }
@@ -4594,17 +4608,19 @@ bool __fastcall TTerminal::CopyFiles(TStrings * FileList, const UnicodeString Ta
   return ProcessFiles(FileList, foRemoteCopy, CopyFile, &Params);
 }
 //---------------------------------------------------------------------------
-void __fastcall TTerminal::CreateDirectory(const UnicodeString DirName,
-  const TRemoteProperties * Properties)
+void __fastcall TTerminal::CreateDirectory(const UnicodeString & DirName, const TRemoteProperties * Properties)
 {
   DebugAssert(FFileSystem);
+  DebugAssert(Properties != NULL);
   EnsureNonExistence(DirName);
   FileModified(NULL, DirName);
 
   LogEvent(FORMAT(L"Creating directory \"%s\".", (DirName)));
-  DoCreateDirectory(DirName);
+  bool Encrypt = Properties->Valid.Contains(vpEncrypt) && Properties->Encrypt;
+  DoCreateDirectory(DirName, Encrypt);
 
-  if ((Properties != NULL) && !Properties->Valid.Empty())
+  TValidProperties RemainingPropeties = Properties->Valid - (TValidProperties() << vpEncrypt);
+  if (!RemainingPropeties.Empty())
   {
     DoChangeFileProperties(DirName, NULL, Properties);
   }
@@ -4612,7 +4628,7 @@ void __fastcall TTerminal::CreateDirectory(const UnicodeString DirName,
   ReactOnCommand(fsCreateDirectory);
 }
 //---------------------------------------------------------------------------
-void __fastcall TTerminal::DoCreateDirectory(const UnicodeString DirName)
+void __fastcall TTerminal::DoCreateDirectory(const UnicodeString & DirName, bool Encrypt)
 {
   TRetryOperationLoop RetryLoop(this);
   do
@@ -4621,7 +4637,7 @@ void __fastcall TTerminal::DoCreateDirectory(const UnicodeString DirName)
     try
     {
       DebugAssert(FFileSystem);
-      FFileSystem->CreateDirectory(DirName);
+      FFileSystem->CreateDirectory(DirName, Encrypt);
     }
     catch(Exception & E)
     {
@@ -6731,9 +6747,11 @@ void __fastcall TTerminal::CreateTargetDirectory(
   TRemoteProperties Properties;
   if (CopyParam->PreserveRights)
   {
-    Properties.Valid = TValidProperties() << vpRights;
+    Properties.Valid = Properties.Valid << vpRights;
     Properties.Rights = CopyParam->RemoteFileRights(Attrs);
   }
+  Properties.Valid = Properties.Valid << vpEncrypt;
+  Properties.Encrypt = CopyParam->EncryptNewFiles;
   CreateDirectory(DirectoryPath, &Properties);
 }
 //---------------------------------------------------------------------------
@@ -7304,8 +7322,13 @@ void __fastcall TTerminal::Sink(
 
     // Suppose same data size to transfer as to write
     // (not true with ASCII transfer)
-    OperationProgress->SetTransferSize(File->Size);
-    OperationProgress->SetLocalSize(OperationProgress->TransferSize);
+    __int64 TransferSize = File->Size;
+    OperationProgress->SetLocalSize(TransferSize);
+    if (IsFileEncrypted(FileName))
+    {
+      TransferSize += TEncryption::GetOverhead();
+    }
+    OperationProgress->SetTransferSize(TransferSize);
 
     int Attrs;
     FILE_OPERATION_LOOP_BEGIN
@@ -7696,6 +7719,96 @@ bool __fastcall TTerminal::IsThisOrChild(TTerminal * Terminal)
     ((FCommandSession != NULL) && (FCommandSession == Terminal));
 }
 //---------------------------------------------------------------------------
+TTerminal::TEncryptedFileNames::const_iterator __fastcall TTerminal::GetEncryptedFileName(const UnicodeString & Path)
+{
+  UnicodeString FileDir = UnixExtractFileDir(Path);
+
+  // If we haven't been in this folder yet, read it to collect mapping to encrypted file names.
+  if (FFoldersScannedForEncryptedFiles.find(FileDir) == FFoldersScannedForEncryptedFiles.end())
+  {
+    try
+    {
+      delete DoReadDirectoryListing(FileDir, true);
+    }
+    catch (Exception & E)
+    {
+      if (!Active)
+      {
+        throw;
+      }
+    }
+
+    FFoldersScannedForEncryptedFiles.insert(FileDir);
+  }
+
+  TEncryptedFileNames::const_iterator Result = FEncryptedFileNames.find(Path);
+  return Result;
+}
+//---------------------------------------------------------------------------
+bool __fastcall TTerminal::IsFileEncrypted(const UnicodeString & Path, bool EncryptNewFiles)
+{
+  // can be optimized
+  bool Result = (EncryptFileName(Path, EncryptNewFiles) != Path);
+  return Result;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TTerminal::EncryptFileName(const UnicodeString & Path, bool EncryptNewFiles)
+{
+  UnicodeString Result = Path;
+  if (IsEncryptingFiles() && !IsUnixRootPath(Path))
+  {
+    UnicodeString FileName = UnixExtractFileName(Path);
+    UnicodeString FileDir = UnixExtractFileDir(Path);
+
+    if (!FileName.IsEmpty() && (FileName != PARENTDIRECTORY) && (FileName != THISDIRECTORY))
+    {
+      TEncryptedFileNames::const_iterator I = GetEncryptedFileName(Path);
+      if (I != FEncryptedFileNames.end())
+      {
+        FileName = I->second;
+      }
+      else if (EncryptNewFiles)
+      {
+        TEncryption Encryption(FEncryptKey);
+        FileName = Encryption.EncryptFileName(FileName);
+        FEncryptedFileNames.insert(std::make_pair(Path, FileName));
+      }
+    }
+
+    FileDir = EncryptFileName(FileDir, EncryptNewFiles);
+    Result = UnixCombinePaths(FileDir, FileName);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TTerminal::DecryptFileName(const UnicodeString & Path)
+{
+  UnicodeString Result = Path;
+  if (IsEncryptingFiles() && !IsUnixRootPath(Path))
+  {
+    UnicodeString FileName = UnixExtractFileName(Path);
+    UnicodeString FileNameEncrypted = FileName;
+
+    bool Encrypted = TEncryption::IsEncryptedFileName(FileName);
+    if (Encrypted)
+    {
+      TEncryption Encryption(FEncryptKey);
+      FileName = Encryption.DecryptFileName(FileName);
+
+      UnicodeString FileDir = UnixExtractFileDir(Path);
+      FileDir = DecryptFileName(FileDir);
+      Result = UnixCombinePaths(FileDir, FileName);
+    }
+
+    if (Encrypted || (FEncryptedFileNames.find(Result) == FEncryptedFileNames.end()))
+    {
+      // This may overwrite another variant of encryption
+      FEncryptedFileNames[Result] = FileNameEncrypted;
+    }
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 //---------------------------------------------------------------------------
 __fastcall TSecondaryTerminal::TSecondaryTerminal(TTerminal * MainTerminal,
   TSessionData * ASessionData, TConfiguration * Configuration, const UnicodeString & Name) :

+ 13 - 3
source/core/Terminal.h

@@ -216,6 +216,10 @@ private:
   UnicodeString FDestFileName;
   bool FMultipleDestinationFiles;
   bool FFileTransferAny;
+  typedef std::map<UnicodeString, UnicodeString> TEncryptedFileNames;
+  TEncryptedFileNames FEncryptedFileNames;
+  std::set<UnicodeString> FFoldersScannedForEncryptedFiles;
+  RawByteString FEncryptKey;
 
   void __fastcall CommandError(Exception * E, const UnicodeString Msg);
   unsigned int __fastcall CommandError(Exception * E, const UnicodeString Msg,
@@ -255,7 +259,7 @@ protected:
   void __fastcall DoStartReadDirectory();
   void __fastcall DoReadDirectoryProgress(int Progress, int ResolvedLinks, bool & Cancel);
   void __fastcall DoReadDirectory(bool ReloadOnly);
-  void __fastcall DoCreateDirectory(const UnicodeString DirName);
+  void __fastcall DoCreateDirectory(const UnicodeString & DirName, bool Encrypt);
   void __fastcall DoDeleteFile(const UnicodeString FileName, const TRemoteFile * File,
     int Params);
   void __fastcall DoCustomCommandOnFile(UnicodeString FileName,
@@ -450,6 +454,11 @@ protected:
     const UnicodeString & DestFullName, const TRemoteFile * File, const TCopyParamType * CopyParam, int Attrs);
   void __fastcall UpdateTargetTime(HANDLE Handle, TDateTime Modification, TDSTMode DSTMode);
 
+  UnicodeString __fastcall EncryptFileName(const UnicodeString & Path, bool EncryptNewFiles);
+  UnicodeString __fastcall DecryptFileName(const UnicodeString & Path);
+  TEncryptedFileNames::const_iterator __fastcall GetEncryptedFileName(const UnicodeString & Path);
+  bool __fastcall IsFileEncrypted(const UnicodeString & Path, bool EncryptNewFiles = false);
+
   __property TFileOperationProgressType * OperationProgress = { read=FOperationProgress };
 
 public:
@@ -485,8 +494,7 @@ public:
     const UnicodeString TargetDir, const TCopyParamType * CopyParam, int Params, TParallelOperation * ParallelOperation);
   int __fastcall CopyToParallel(TParallelOperation * ParallelOperation, TFileOperationProgressType * OperationProgress);
   void __fastcall LogParallelTransfer(TParallelOperation * ParallelOperation);
-  void __fastcall CreateDirectory(const UnicodeString DirName,
-    const TRemoteProperties * Properties = NULL);
+  void __fastcall CreateDirectory(const UnicodeString & DirName, const TRemoteProperties * Properties);
   void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   void __fastcall DeleteFile(UnicodeString FileName,
     const TRemoteFile * File = NULL, void * Params = NULL);
@@ -566,6 +574,8 @@ public:
   UnicodeString __fastcall ChangeFileName(const TCopyParamType * CopyParam,
     UnicodeString FileName, TOperationSide Side, bool FirstLevel);
   UnicodeString __fastcall GetBaseFileName(UnicodeString FileName);
+  bool __fastcall IsEncryptingFiles() { return !FEncryptKey.IsEmpty(); }
+  RawByteString __fastcall GetEncryptKey() { return FEncryptKey; }
 
   static UnicodeString __fastcall ExpandFileName(UnicodeString Path,
     const UnicodeString BasePath);

+ 1 - 1
source/core/WebDAVFileSystem.cpp

@@ -1057,7 +1057,7 @@ void __fastcall TWebDAVFileSystem::CopyFile(const UnicodeString FileName, const
   CheckStatus(NeonStatus);
 }
 //---------------------------------------------------------------------------
-void __fastcall TWebDAVFileSystem::CreateDirectory(const UnicodeString DirName)
+void __fastcall TWebDAVFileSystem::CreateDirectory(const UnicodeString & DirName, bool /*Encrypt*/)
 {
   ClearNeonError();
   TOperationVisualizer Visualizer(FTerminal->UseBusyCursor);

+ 1 - 1
source/core/WebDAVFileSystem.h

@@ -55,7 +55,7 @@ public:
     const TCopyParamType * CopyParam, int Params,
     TFileOperationProgressType * OperationProgress, unsigned int Flags,
     TUploadSessionAction & Action, bool & ChildError);
-  virtual void __fastcall CreateDirectory(const UnicodeString DirName);
+  virtual void __fastcall CreateDirectory(const UnicodeString & DirName, bool Encrypt);
   virtual void __fastcall CreateLink(const UnicodeString FileName, const UnicodeString PointTo, bool Symbolic);
   virtual void __fastcall DeleteFile(const UnicodeString FileName,
     const TRemoteFile * File, int Params, TRmSessionAction & Action);

+ 4 - 0
source/forms/CopyParams.cpp

@@ -99,6 +99,7 @@ void __fastcall TCopyParamsFrame::SetParams(TCopyParamType value)
   SpeedCombo->Text = SetSpeedLimit(value.CPSLimit);
 
   NewerOnlyCheck->Checked = value.NewerOnly;
+  EncryptNewFilesCheck->Checked = value.EncryptNewFiles;
 
   *FParams = value;
 
@@ -153,6 +154,7 @@ TCopyParamType __fastcall TCopyParamsFrame::GetParams()
   Result.CPSLimit = GetSpeedLimit(SpeedCombo->Text);
 
   Result.NewerOnly = NewerOnlyCheck->Checked;
+  Result.EncryptNewFiles = EncryptNewFilesCheck->Checked;
 
   return Result;
 }
@@ -212,6 +214,8 @@ void __fastcall TCopyParamsFrame::UpdateControls()
     FLAGCLEAR(CopyParamAttrs, cpaIncludeMaskOnly));
   EnableControl(NewerOnlyCheck, FLAGCLEAR(CopyParamAttrs, cpaIncludeMaskOnly) &&
     FLAGCLEAR(CopyParamAttrs, cpaNoNewerOnly) && Enabled);
+  EnableControl(EncryptNewFilesCheck, FLAGCLEAR(CopyParamAttrs, cpaIncludeMaskOnly) &&
+    FLAGCLEAR(CopyParamAttrs, cpaNoEncryptNewFiles) && Enabled);
 }
 //---------------------------------------------------------------------------
 void __fastcall TCopyParamsFrame::ControlChange(TObject * /*Sender*/)

+ 9 - 0
source/forms/CopyParams.dfm

@@ -163,6 +163,15 @@ object CopyParamsFrame: TCopyParamsFrame
       TabOrder = 4
       OnClick = ControlChange
     end
+    object EncryptNewFilesCheck: TCheckBox
+      Left = 16
+      Top = 150
+      Width = 173
+      Height = 17
+      Caption = '&Encrypt new files'
+      TabOrder = 5
+      OnClick = ControlChange
+    end
   end
   object ChangeCaseGroup: TGroupBox
     Left = 267

+ 1 - 0
source/forms/CopyParams.h

@@ -50,6 +50,7 @@ __published:
   TCheckBox *NewerOnlyCheck;
   TCheckBox *RemoveCtrlZAndBOMCheck;
   TCheckBox *PreserveTimeDirsCheck;
+  TCheckBox *EncryptNewFilesCheck;
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall ValidateMaskComboExit(TObject *Sender);
   void __fastcall RightsEditButtonClick(TObject *Sender);

+ 9 - 1
source/forms/CustomScpExplorer.cpp

@@ -4019,6 +4019,14 @@ bool __fastcall TCustomScpExplorerForm::RemoteTransferFiles(
   return Result;
 }
 //---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::CreateRemoteDirectory(
+  const UnicodeString & Path, TRemoteProperties & Properties)
+{
+  Properties.Valid = Properties.Valid << vpEncrypt;
+  Properties.Encrypt = GUIConfiguration->CurrentCopyParam.EncryptNewFiles;
+  RemoteDirView->CreateDirectoryEx(Path, &Properties);
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::CreateDirectory(TOperationSide Side)
 {
   Side = GetSide(Side);
@@ -4038,7 +4046,7 @@ void __fastcall TCustomScpExplorerForm::CreateDirectory(TOperationSide Side)
       {
         GUIConfiguration->NewDirectoryProperties = Properties;
       }
-      RemoteDirView->CreateDirectoryEx(Name, &Properties);
+      CreateRemoteDirectory(Name, Properties);
     }
     else
     {

+ 1 - 0
source/forms/CustomScpExplorer.h

@@ -599,6 +599,7 @@ protected:
   virtual void __fastcall UpdateImages();
   void __fastcall UpdatePixelsPerInchMainWindowCounter();
   void __fastcall CopyPopup(TControl * DestControl, TControl * SourceControl);
+  void __fastcall CreateRemoteDirectory(const UnicodeString & Path, TRemoteProperties & Properties);
 
 public:
   virtual __fastcall ~TCustomScpExplorerForm();

+ 1 - 1
source/forms/ScpCommander.cpp

@@ -1035,7 +1035,7 @@ void __fastcall TScpCommanderForm::CreateRemoteDirectory(const UnicodeString & P
     CreateRemoteDirectory(Dir);
   }
   TRemoteProperties Properties = GUIConfiguration->NewDirectoryProperties;
-  RemoteDirView->CreateDirectoryEx(Path, &Properties);
+  TCustomScpExplorerForm::CreateRemoteDirectory(Path, Properties);
 }
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::SynchronizeBrowsingLocal(

+ 63 - 0
source/forms/SiteAdvanced.cpp

@@ -7,8 +7,10 @@
 #include <Common.h>
 #include <TextsWin.h>
 #include <TextsCore.h>
+#include <HelpCore.h>
 #include <HelpWin.h>
 #include <VCLCommon.h>
+#include <Cryptography.h>
 
 #include "WinInterface.h"
 #include "SiteAdvanced.h"
@@ -397,6 +399,10 @@ void __fastcall TSiteAdvancedDialog::LoadSession()
     // Note page
     NoteMemo->Lines->Text = FSessionData->Note;
 
+    // Encryption page
+    EncryptFilesCheck->Checked = !FSessionData->EncryptKey.IsEmpty();
+    GetEncryptKeyEdit()->Text = FSessionData->EncryptKey;
+
     // color
     SetSessionColor((TColor)FSessionData->Color);
   }
@@ -638,6 +644,9 @@ void __fastcall TSiteAdvancedDialog::SaveSession()
   // Note page
   FSessionData->Note = NoteMemo->Lines->Text;
 
+  // Encryption page
+  FSessionData->EncryptKey = EncryptFilesCheck->Checked ? GetEncryptKeyEdit()->Text : UnicodeString();
+
   // color
   FSessionData->Color = FColor;
 }
@@ -993,6 +1002,10 @@ void __fastcall TSiteAdvancedDialog::UpdateControls()
     // TLS/SSL session reuse is not configurable for WebDAV/S3 yet
     SslSessionReuseCheck->Enabled = SslSheet->Enabled && FtpProtocol;
 
+    // encryption sheet
+    EncryptionSheet->Enabled = SftpProtocol;
+    EnableControl(EncryptFilesGroup, EncryptFilesCheck->Checked);
+
     UpdateNavigationTree();
 
     // color
@@ -1270,6 +1283,8 @@ void __fastcall TSiteAdvancedDialog::FormCloseQuery(TObject * /*Sender*/,
     // for tunnel SSH version is not configurable
     VerifyKey(StripPathQuotes(TunnelPrivateKeyEdit3->Text), ssh2only);
     VerifyCertificate(StripPathQuotes(TlsCertificateFileEdit->Text));
+    // Particularly for EncryptKey*Edit's
+    ExitActiveControl(this);
   }
 }
 //---------------------------------------------------------------------------
@@ -1580,3 +1595,51 @@ void __fastcall TSiteAdvancedDialog::PrivateKeyViewButtonClick(TObject * /*Sende
   MoreMessageDialog(Message, Messages.get(), qtInformation, Answers, HELP_LOGIN_AUTHORIZED_KEYS, &Params);
 }
 //---------------------------------------------------------------------------
+TCustomEdit * __fastcall TSiteAdvancedDialog::GetEncryptKeyEdit(bool AShow)
+{
+  bool Show = (ShowEncryptionKeyCheck->Checked == AShow);
+  return (Show ? static_cast<TCustomEdit *>(EncryptKeyVisibleEdit) : static_cast<TCustomEdit *>(EncryptKeyPasswordEdit));
+}
+//---------------------------------------------------------------------------
+void __fastcall TSiteAdvancedDialog::ShowEncryptionKeyCheckClick(TObject * /*Sender*/)
+{
+  TCustomEdit * ShowEdit = GetEncryptKeyEdit();
+  TCustomEdit * HideEdit = GetEncryptKeyEdit(false);
+  if (DebugAlwaysTrue(ShowEdit->Visible != HideEdit->Visible) &&
+      DebugAlwaysTrue(!ShowEdit->Visible))
+  {
+    UnicodeString Key = HideEdit->Text;
+    ShowEdit->Visible = true;
+    ShowEdit->Text = Key;
+    HideEdit->Visible = false;
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TSiteAdvancedDialog::GenerateKeyButtonClick(TObject * /*Sender*/)
+{
+  UnicodeString Key = BytesToHex(GenerateEncryptKey());
+  GetEncryptKeyEdit()->Text = Key;
+
+  TClipboardHandler ClipboardHandler;
+  ClipboardHandler.Text = Key;
+
+  TMessageParams Params;
+  TQueryButtonAlias Aliases[1];
+  Aliases[0].Button = qaRetry;
+  Aliases[0].Alias = LoadStr(COPY_KEY_BUTTON);
+  Aliases[0].OnSubmit = &ClipboardHandler.Copy;
+  Params.Aliases = Aliases;
+  Params.AliasesCount = LENOF(Aliases);
+
+  MessageDialog(LoadStr(ENCRYPT_KEY_GENERATED), qtInformation, qaOK | qaRetry, HELP_FILE_ENCRYPTION, &Params);
+}
+//---------------------------------------------------------------------------
+void __fastcall TSiteAdvancedDialog::EncryptKeyEditExit(TObject * /*Sender*/)
+{
+  UnicodeString HexKey = GetEncryptKeyEdit()->Text;
+  if (!HexKey.IsEmpty())
+  {
+    ValidateEncryptKey(HexToBytes(HexKey));
+  }
+}
+//---------------------------------------------------------------------------

+ 96 - 19
source/forms/SiteAdvanced.dfm

@@ -423,6 +423,82 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
           end
         end
       end
+      object EncryptionSheet: TTabSheet
+        Tag = 2
+        Caption = 'Encryption'
+        TabVisible = False
+        DesignSize = (
+          401
+          382)
+        object EncryptFilesCheck: TCheckBox
+          Left = 12
+          Top = 8
+          Width = 382
+          Height = 17
+          Anchors = [akLeft, akTop, akRight]
+          Caption = '&Encrypt files'
+          TabOrder = 0
+          OnClick = DataChange
+        end
+        object EncryptFilesGroup: TGroupBox
+          Left = 0
+          Top = 32
+          Width = 393
+          Height = 121
+          Anchors = [akLeft, akTop, akRight]
+          Caption = 'Encryption options'
+          TabOrder = 1
+          object Label13: TLabel
+            Left = 12
+            Top = 18
+            Width = 75
+            Height = 13
+            Caption = 'Encryption &key:'
+            FocusControl = EncryptKeyPasswordEdit
+          end
+          object EncryptKeyVisibleEdit: TEdit
+            Left = 12
+            Top = 34
+            Width = 370
+            Height = 21
+            MaxLength = 64
+            TabOrder = 1
+            Text = 'EncryptKeyVisibleEdit'
+            Visible = False
+            OnChange = DataChange
+            OnExit = EncryptKeyEditExit
+          end
+          object EncryptKeyPasswordEdit: TPasswordEdit
+            Left = 12
+            Top = 34
+            Width = 370
+            Height = 21
+            MaxLength = 64
+            TabOrder = 0
+            Text = 'EncryptKeyPasswordEdit'
+            OnChange = DataChange
+            OnExit = EncryptKeyEditExit
+          end
+          object ShowEncryptionKeyCheck: TCheckBox
+            Left = 12
+            Top = 61
+            Width = 97
+            Height = 17
+            Caption = '&Show key'
+            TabOrder = 2
+            OnClick = ShowEncryptionKeyCheckClick
+          end
+          object GenerateKeyButton: TButton
+            Left = 12
+            Top = 84
+            Width = 117
+            Height = 25
+            Caption = '&Generate Key'
+            TabOrder = 3
+            OnClick = GenerateKeyButtonClick
+          end
+        end
+      end
       object SftpSheet: TTabSheet
         Tag = 2
         HelpType = htKeyword
@@ -2385,28 +2461,29 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
         OnCollapsing = NavigationTreeCollapsing
         Items.NodeData = {
           030400000036000000000000000000000000000000FFFFFFFF00000000000000
-          0005000000010C45006E007600690072006F006E006D0065006E007400580036
+          0006000000010C45006E007600690072006F006E006D0065006E007400580036
           000000000000000000000000000000FFFFFFFF00000000000000000000000001
           0C4400690072006500630074006F007200690065007300580036000000000000
           000000000000000000FFFFFFFF000000000000000000000000010C5200650063
-          00790063006C0065002000620069006E00580028000000000000000000000000
-          000000FFFFFFFF00000000000000000000000001055300460054005000580026
-          000000000000000000000000000000FFFFFFFF00000000000000000000000001
-          045300430050005800260000000000000000000000FFFFFFFFFFFFFFFF000000
-          0000000000000000000104460054005000580034000000000000000000000000
-          000000FFFFFFFF000000000000000002000000010B43006F006E006E00650063
-          00740069006F006E0058002A000000000000000000000000000000FFFFFFFF00
-          00000000000000000000000106500072006F007800790058002C000000000000
-          000000000000000000FFFFFFFF0000000000000000000000000107540075006E
-          006E0065006C00580026000000000000000000000000000000FFFFFFFF000000
-          0000000000030000000104530053004800580038000000000000000000000000
-          000000FFFFFFFF000000000000000000000000010D4B00650078002000650078
-          006300680061006E006700650058003C000000000000000000000000000000FF
-          FFFFFF000000000000000000000000010F410075007400680065006E00740069
-          0063006100740069006F006E00580028000000000000000000000000000000FF
-          FFFFFF0000000000000000000000000105420075006700730058002800000000
-          0000000000000000000000FFFFFFFF00000000000000000000000001054E006F
-          00740065005800}
+          00790063006C0065002000620069006E005800340000000000000000000000FF
+          FFFFFFFFFFFFFF000000000000000000000000010B45006E0063007200790070
+          00740069006F006E00580028000000000000000000000000000000FFFFFFFF00
+          0000000000000000000000010553004600540050005800260000000000000000
+          00000000000000FFFFFFFF000000000000000000000000010453004300500058
+          00260000000000000000000000FFFFFFFFFFFFFFFF0000000000000000000000
+          000104460054005000580034000000000000000000000000000000FFFFFFFF00
+          0000000000000002000000010B43006F006E006E0065006300740069006F006E
+          0058002A000000000000000000000000000000FFFFFFFF000000000000000000
+          0000000106500072006F007800790058002C0000000000000000000000000000
+          00FFFFFFFF0000000000000000000000000107540075006E006E0065006C0058
+          0026000000000000000000000000000000FFFFFFFF0000000000000000030000
+          000104530053004800580038000000000000000000000000000000FFFFFFFF00
+          0000000000000000000000010D4B00650078002000650078006300680061006E
+          006700650058003C000000000000000000000000000000FFFFFFFF0000000000
+          00000000000000010F410075007400680065006E007400690063006100740069
+          006F006E00580028000000000000000000000000000000FFFFFFFF0000000000
+          0000000000000001054200750067007300580028000000000000000000000000
+          000000FFFFFFFF00000000000000000000000001054E006F00740065005800}
       end
     end
   end

+ 12 - 0
source/forms/SiteAdvanced.h

@@ -262,6 +262,14 @@ __published:
   TMenuItem *PrivateKeyGenerateItem;
   TMenuItem *PrivateKeyUploadItem;
   TButton *PrivateKeyViewButton;
+  TTabSheet *EncryptionSheet;
+  TCheckBox *EncryptFilesCheck;
+  TGroupBox *EncryptFilesGroup;
+  TLabel *Label13;
+  TPasswordEdit *EncryptKeyPasswordEdit;
+  TCheckBox *ShowEncryptionKeyCheck;
+  TButton *GenerateKeyButton;
+  TEdit *EncryptKeyVisibleEdit;
   void __fastcall DataChange(TObject *Sender);
   void __fastcall FormShow(TObject *Sender);
   void __fastcall PageControlChange(TObject *Sender);
@@ -298,6 +306,9 @@ __published:
   void __fastcall PrivateKeyGenerateItemClick(TObject *Sender);
   void __fastcall PrivateKeyToolsButtonClick(TObject *Sender);
   void __fastcall PrivateKeyViewButtonClick(TObject *Sender);
+  void __fastcall ShowEncryptionKeyCheckClick(TObject *Sender);
+  void __fastcall GenerateKeyButtonClick(TObject *Sender);
+  void __fastcall EncryptKeyEditExit(TObject *Sender);
 
 
 public:
@@ -342,6 +353,7 @@ private:
   int __fastcall TlsVersionToIndex(TTlsVersion TlsVersion);
   bool __fastcall IsNeon(TFSProtocol FSProtocol);
   void __fastcall PrivateKeyCreatedOrModified(TObject * Sender, const UnicodeString FileName);
+  TCustomEdit * __fastcall GetEncryptKeyEdit(bool AShow = true);
 };
 //----------------------------------------------------------------------------
 #endif

+ 3 - 1
source/packages/filemng/CustomDirView.pas

@@ -22,7 +22,7 @@ const
   oiLink = $02;
   oiBrokenLink = $04;
   oiPartial = $08;
-  oiShared = $10; // not used
+  oiEncrypted = $10;
   DefaultHistoryCount = 200;
 
 const
@@ -536,6 +536,7 @@ const
   ResLink = 'LINK%2.2d';
   ResBrokenLink = 'BROKEN%2.2d';
   ResPartial = 'PARTIAL%2.2d';
+  ResEncrypted = 'ENCRYPTED%2.2d';
 
 var
   WinDir: string;
@@ -771,6 +772,7 @@ begin
   GetOverlayBitmap(Result, Format(ResLink, [Size]));
   GetOverlayBitmap(Result, Format(ResBrokenLink, [Size]));
   GetOverlayBitmap(Result, Format(ResPartial, [Size]));
+  GetOverlayBitmap(Result, Format(ResEncrypted, [Size]));
 end;
 
 

BIN
source/packages/filemng/DirImg.res


+ 2 - 0
source/putty/puttyexp.h

@@ -68,6 +68,8 @@ void call_aes_free_context(void * handle);
 void call_aes_setup(void * ctx, int blocklen, unsigned char * key, int keylen);
 void call_aes_encrypt(void * ctx, unsigned int * block);
 void call_aes_decrypt(void * ctx, unsigned int * block);
+void call_aes_sdctr(unsigned char *blk, int len, void *ctx);
+void aes_iv(void *handle, unsigned char *iv);
 
 // from sshsha.c
 

+ 5 - 0
source/putty/sshaes.c

@@ -1296,5 +1296,10 @@ void call_aes_decrypt(void * ctx, unsigned int * block)
   aes_decrypt((AESContext *)ctx, block);
 }
 
+void call_aes_sdctr(unsigned char *blk, int len, void *ctx)
+{
+  aes_sdctr(blk, len, (AESContext *)ctx);
+}
+
 #endif
 #endif // WINSCP_VS

+ 1 - 0
source/resource/HelpCore.h

@@ -34,5 +34,6 @@
 #define HELP_STATUSMSG_DISCONNECTED            "message_disconnected"
 #define HELP_SFTP_INITIALIZE_ERROR             "message_cannot_initialize_sftp_protocol"
 #define HELP_AUTH_TRANSL_KEY_REFUSED           "message_key_refused"
+#define HELP_FILE_ENCRYPTION                   "file_encryption"
 
 #endif // HelpCoreH

+ 3 - 0
source/resource/TextsCore.h

@@ -268,6 +268,8 @@
 #define S3_ERROR_FURTHER_DETAILS 744
 #define S3_ERROR_EXTRA_DETAILS  745
 #define S3_STATUS_ACCESS_DENIED 746
+#define UNKNOWN_FILE_ENCRYPTION 747
+#define INVALID_ENCRYPT_KEY     748
 
 #define CORE_CONFIRMATION_STRINGS 300
 #define CONFIRM_PROLONG_TIMEOUT3 301
@@ -476,6 +478,7 @@
 #define KEY_DETAILS             561
 #define COPY_KEY_ACTION         562
 #define COPY_CERTIFICATE_ACTION 563
+#define COPY_INFO_DONT_ENCRYPT_NEW_FILES 564
 
 #define CORE_VARIABLE_STRINGS   600
 #define PUTTY_BASED_ON          601

+ 3 - 0
source/resource/TextsCore1.rc

@@ -239,6 +239,8 @@ BEGIN
   S3_STATUS_ACCESS_DENIED, "Access denied."
   DUPLICATE_FOLDER_NOT_SUPPORTED, "Direct duplication of folders is not supported. Use a duplication via a local temporary copy."
   MISSING_TARGET_BUCKET, "Specify target bucket."
+  UNKNOWN_FILE_ENCRYPTION, "File is not encrypted using a known encryption."
+  INVALID_ENCRYPT_KEY, "**Invalid encryption key.**\n\nEncryption key for %s encryption must have %d bytes. It must be entered in hexadecimal representation (i.e. %d characters)."
 
   CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION"
   CONFIRM_PROLONG_TIMEOUT3, "Host is not communicating for %d seconds.\n\nWait for another %0:d seconds?"
@@ -445,6 +447,7 @@ BEGIN
   KEY_DETAILS, "    Algorithm:\t%s\n    SHA-256:\t%s\n    MD5:\t%s"
   COPY_KEY_ACTION, "&Copy key fingerprints to clipboard"
   COPY_CERTIFICATE_ACTION, "&Copy certificate fingerprint to clipboard"
+  COPY_INFO_DONT_ENCRYPT_NEW_FILES, "Do not encrypt new files"
 
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"

+ 1 - 0
source/resource/TextsWin.h

@@ -286,6 +286,7 @@
 #define PASSWORD_CHANGED        1576
 #define OPEN_TARGET_FOLDER      1577
 #define OPEN_DOWNLOADED_FILE    1578
+#define ENCRYPT_KEY_GENERATED   1579
 
 #define WIN_FORMS_STRINGS       1600
 #define COPY_FILE               1605

+ 1 - 0
source/resource/TextsWin1.rc

@@ -289,6 +289,7 @@ BEGIN
         PASSWORD_CHANGED, "Password has been changed."
         OPEN_TARGET_FOLDER, "Open &Target Folder"
         OPEN_DOWNLOADED_FILE, "Open &Downloaded File"
+        ENCRYPT_KEY_GENERATED, "**Encryption key was generated.**\n\nYou should safely backup the generated encryption key. If you lose it, you won't be able to read your files."
 
         WIN_FORMS_STRINGS, "WIN_FORMS_STRINGS"
         COPY_FILE, "%s file '%s' to %s:"