Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/duplicati/duplicati.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Martikyan <kamartikyan@gmail.com>2020-10-04 23:36:47 +0300
committerGitHub <noreply@github.com>2020-10-04 23:36:47 +0300
commit2c4ccc27d314bdf1d544cabdff7e71c3ac4e4a3c (patch)
treefac620ced83ba8bde5c4949ef05d11dafd519ca8 /Duplicati/Library/Backend
parenta994ce8f7d75445e661c1a90f310b158d60377ee (diff)
Feature/telegram backend enc sessions (#2)
* Added initial state for encrypted sessions * Bugs fixed in encrypted sessions * Working state for encrypted sessions * Fixed exposing phone number/identity * Performance problems fixed, bug fixes made Co-authored-by: Karen Martikyan <karen.martikyan@digitain.com>
Diffstat (limited to 'Duplicati/Library/Backend')
-rw-r--r--Duplicati/Library/Backend/Telegram/Duplicati.Library.Backend.Telegram.csproj5
-rw-r--r--Duplicati/Library/Backend/Telegram/EncryptedFileSessionStore.cs152
-rw-r--r--Duplicati/Library/Backend/Telegram/InMemorySessionStore.cs41
-rw-r--r--Duplicati/Library/Backend/Telegram/StreamReadHelper.cs21
-rw-r--r--Duplicati/Library/Backend/Telegram/Strings.cs2
-rw-r--r--Duplicati/Library/Backend/Telegram/TelegramBackend.cs59
-rw-r--r--Duplicati/Library/Backend/Telegram/packages.config1
7 files changed, 204 insertions, 77 deletions
diff --git a/Duplicati/Library/Backend/Telegram/Duplicati.Library.Backend.Telegram.csproj b/Duplicati/Library/Backend/Telegram/Duplicati.Library.Backend.Telegram.csproj
index d6be3457c..a28e9c213 100644
--- a/Duplicati/Library/Backend/Telegram/Duplicati.Library.Backend.Telegram.csproj
+++ b/Duplicati/Library/Backend/Telegram/Duplicati.Library.Backend.Telegram.csproj
@@ -36,6 +36,9 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
+ <Reference Include="SharpAESCrypt, Version=1.3.3.0, Culture=neutral, PublicKeyToken=null">
+ <HintPath>..\..\..\..\packages\SharpAESCrypt.exe.1.3.3\lib\netstandard2.0\SharpAESCrypt.exe</HintPath>
+ </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
@@ -48,7 +51,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ChannelFileInfo.cs" />
- <Compile Include="InMemorySessionStore.cs" />
+ <Compile Include="EncryptedFileSessionStore.cs" />
<Compile Include="StreamReadHelper.cs" />
<Compile Include="TelegramBackend.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
diff --git a/Duplicati/Library/Backend/Telegram/EncryptedFileSessionStore.cs b/Duplicati/Library/Backend/Telegram/EncryptedFileSessionStore.cs
new file mode 100644
index 000000000..076507662
--- /dev/null
+++ b/Duplicati/Library/Backend/Telegram/EncryptedFileSessionStore.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using TLSharp.Core;
+
+namespace Duplicati.Library.Backend
+{
+ public class EncryptedFileSessionStore : ISessionStore
+ {
+ private static readonly object m_lockObj = new object();
+ private static readonly uint[] m_lookup32 = CreateLookup32();
+
+ private readonly string m_teleDataPath;
+ private readonly string m_password;
+ private readonly SHA256 m_sha = SHA256.Create();
+ private static readonly ConcurrentDictionary<string, byte[]> m_userIdLastSessionMap = new ConcurrentDictionary<string, byte[]>();
+
+ public EncryptedFileSessionStore(string password)
+ {
+ if (string.IsNullOrWhiteSpace(password))
+ {
+ throw new ArgumentNullException(nameof(password));
+ }
+
+ var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ m_teleDataPath = Path.Combine(appData, nameof(Duplicati), nameof(Telegram));
+ m_password = password;
+
+ Directory.CreateDirectory(m_teleDataPath);
+ }
+
+
+ public void Save(Session session)
+ {
+ var sessionId = session.SessionUserId;
+ var filePath = GetSessionFilePath(sessionId);
+ var sessionBytes = session.ToBytes();
+
+ if (m_userIdLastSessionMap.TryGetValue(sessionId, out var sessionCache))
+ {
+ if (sessionCache.SequenceEqual(sessionBytes))
+ {
+ return;
+ }
+ }
+
+ WriteToEncryptedStorage(sessionBytes, m_password, filePath, sessionId);
+ }
+
+ public Session Load(string userId)
+ {
+ if (m_userIdLastSessionMap.TryGetValue(userId, out var cachedBytes))
+ {
+ var cachedSession = Session.FromBytes(cachedBytes, this, userId);
+ return cachedSession;
+ }
+
+ var filePath = GetSessionFilePath(userId);
+ var sessionBytes = ReadFromEncryptedStorage(m_password, filePath);
+
+ if (sessionBytes == null)
+ {
+ return null;
+ }
+
+ var session = Session.FromBytes(sessionBytes, this, userId);
+ return session;
+ }
+
+ private string GetSessionFilePath(string userId)
+ {
+ userId = userId.TrimStart('+');
+ var sha = GetShortSha(userId);
+ var sessionFilePath = Path.Combine(m_teleDataPath, $"t_{sha}.dat");
+
+ return sessionFilePath;
+ }
+
+ private static void WriteToEncryptedStorage(byte[] bytesToWrite, string pass, string path, string sessionId)
+ {
+ lock (m_lockObj)
+ {
+ using (var sessionMs = new MemoryStream(bytesToWrite))
+ using (var file = File.Open(path, FileMode.Create, FileAccess.Write))
+ {
+ SharpAESCrypt.SharpAESCrypt.Encrypt(pass, sessionMs, file);
+ }
+
+ m_userIdLastSessionMap[sessionId] = bytesToWrite;
+ }
+ }
+
+ private byte[] ReadFromEncryptedStorage(string pass, string path)
+ {
+ var fileInfo = new FileInfo(path);
+ if (fileInfo.Exists == false || fileInfo.Length == 0)
+ {
+ return null;
+ }
+
+ lock (m_lockObj)
+ {
+ using (var sessionMs = new MemoryStream())
+ using (var file = File.Open(path, FileMode.Open, FileAccess.Read))
+ {
+ SharpAESCrypt.SharpAESCrypt.Decrypt(pass, file, sessionMs);
+ return sessionMs.ToArray();
+ }
+ }
+ }
+
+ private string GetShortSha(string input)
+ {
+ var inputBytes = Encoding.UTF8.GetBytes(input);
+
+ var longShaBytes = m_sha.ComputeHash(inputBytes);
+ var longSha = ByteArrayToHexViaLookup32(longShaBytes);
+ var result = longSha.Substring(0, 16);
+
+ return result;
+ }
+
+ private static uint[] CreateLookup32()
+ {
+ var result = new uint[256];
+ for (var i = 0; i < 256; i++)
+ {
+ var s = i.ToString("X2");
+ result[i] = s[0] + ((uint)s[1] << 16);
+ }
+
+ return result;
+ }
+
+ private static string ByteArrayToHexViaLookup32(byte[] bytes)
+ {
+ var lookup32 = m_lookup32;
+ var result = new char[bytes.Length * 2];
+ for (var i = 0; i < bytes.Length; i++)
+ {
+ var val = lookup32[bytes[i]];
+ result[2 * i] = (char)val;
+ result[2 * i + 1] = (char)(val >> 16);
+ }
+
+ return new string(result);
+ }
+ }
+} \ No newline at end of file
diff --git a/Duplicati/Library/Backend/Telegram/InMemorySessionStore.cs b/Duplicati/Library/Backend/Telegram/InMemorySessionStore.cs
deleted file mode 100644
index 8b63618a0..000000000
--- a/Duplicati/Library/Backend/Telegram/InMemorySessionStore.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System.Collections.Concurrent;
-using TLSharp.Core;
-
-namespace Duplicati.Library.Backend
-{
- public class InMemorySessionStore : ISessionStore
- {
- private readonly ConcurrentDictionary<string, byte[]> m_phoneSessionBytesMap = new ConcurrentDictionary<string, byte[]>();
- private readonly ConcurrentDictionary<string, string> m_phonePhoneCodeHashMap = new ConcurrentDictionary<string, string>();
-
- public void Save(Session session)
- {
- m_phoneSessionBytesMap[session.SessionUserId] = session.ToBytes();
- }
-
- public Session Load(string phone)
- {
- if (m_phoneSessionBytesMap.TryGetValue(phone, out var sessionBytes))
- {
- return Session.FromBytes(sessionBytes, this, phone);
- }
-
- return null;
- }
-
- public void SetPhoneHash(string phone, string phoneCodeHash)
- {
- m_phonePhoneCodeHashMap[phone] = phoneCodeHash;
- }
-
- public string GetPhoneHash(string phone)
- {
- if (m_phonePhoneCodeHashMap.TryGetValue(phone, out var result))
- {
- return result;
- }
-
- return null;
- }
- }
-} \ No newline at end of file
diff --git a/Duplicati/Library/Backend/Telegram/StreamReadHelper.cs b/Duplicati/Library/Backend/Telegram/StreamReadHelper.cs
index 1821f8d84..d436bdb27 100644
--- a/Duplicati/Library/Backend/Telegram/StreamReadHelper.cs
+++ b/Duplicati/Library/Backend/Telegram/StreamReadHelper.cs
@@ -1,25 +1,28 @@
-namespace Duplicati.Library.Backend
+using System.IO;
+using Duplicati.Library.Utility;
+
+namespace Duplicati.Library.Backend
{
/// <summary>
- /// Private helper class to fix a bug with the StreamReader
+ /// Private helper class to fix a bug with the StreamReader
/// </summary>
- internal class StreamReadHelper : Utility.OverrideableStream
+ internal class StreamReadHelper : OverrideableStream
{
/// <summary>
- /// Once the stream has returned 0 as the read count it is disposed,
- /// and subsequent read requests will throw an ObjectDisposedException
+ /// Once the stream has returned 0 as the read count it is disposed,
+ /// and subsequent read requests will throw an ObjectDisposedException
/// </summary>
private bool m_empty;
/// <summary>
- /// Basic initialization, just pass the stream to the super class
+ /// Basic initialization, just pass the stream to the super class
/// </summary>
/// <param name="stream"></param>
- public StreamReadHelper(System.IO.Stream stream) : base(stream)
+ public StreamReadHelper(Stream stream) : base(stream)
{ }
/// <summary>
- /// Override the read function to make sure that we only return less than the requested amount of data if the stream is exhausted
+ /// Override the read function to make sure that we only return less than the requested amount of data if the stream is exhausted
/// </summary>
/// <param name="buffer">The buffer to place data in</param>
/// <param name="offset">The offset into the buffer to start at</param>
@@ -27,7 +30,7 @@
/// <returns>The number of bytes read</returns>
public override int Read(byte[] buffer, int offset, int count)
{
- int readCount = 0;
+ var readCount = 0;
int a;
while (!m_empty && count > 0)
diff --git a/Duplicati/Library/Backend/Telegram/Strings.cs b/Duplicati/Library/Backend/Telegram/Strings.cs
index 877d1ab4e..c622f3c67 100644
--- a/Duplicati/Library/Backend/Telegram/Strings.cs
+++ b/Duplicati/Library/Backend/Telegram/Strings.cs
@@ -50,7 +50,7 @@ namespace Duplicati.Library.Backend
public static string ChannelName => LC.L("The channel name of the backup");
#endregion
-
+
#region Formats
public const string TELEGRAM_FLOOD = "It's required to wait {0} seconds before continuing";
diff --git a/Duplicati/Library/Backend/Telegram/TelegramBackend.cs b/Duplicati/Library/Backend/Telegram/TelegramBackend.cs
index 78cd0b3d9..6ef4b8419 100644
--- a/Duplicati/Library/Backend/Telegram/TelegramBackend.cs
+++ b/Duplicati/Library/Backend/Telegram/TelegramBackend.cs
@@ -19,12 +19,10 @@ namespace Duplicati.Library.Backend
{
public class Telegram : IStreamingBackend, IBackend
{
- private const int MEBIBYTE_IN_BYTES = 1048576;
+ private TelegramClient m_telegramClient;
+ private readonly EncryptedFileSessionStore m_encSessionStore;
- private static readonly InMemorySessionStore m_sessionStore = new InMemorySessionStore();
- private static readonly string m_logTag = Log.LogTagFromType(typeof(Telegram));
private static readonly object m_lockObj = new object();
- private static TelegramClient m_telegramClient;
private readonly int m_apiId;
private readonly string m_apiHash;
@@ -34,6 +32,10 @@ namespace Duplicati.Library.Backend
private readonly string m_phoneNumber;
private TLChannel m_channelCache;
+ private static string m_phoneCodeHash;
+ private static readonly string m_logTag = Log.LogTagFromType(typeof(Telegram));
+ private const int BYTES_IN_MEBIBYTE = 1048576;
+
public Telegram()
{ }
@@ -89,19 +91,20 @@ namespace Duplicati.Library.Backend
throw new UserInformationException(Strings.NoChannelNameError, nameof(Strings.NoChannelNameError));
}
+ m_encSessionStore = new EncryptedFileSessionStore($"{m_apiHash}_{m_apiId}");
InitializeTelegramClient(m_apiId, m_apiHash, m_phoneNumber);
}
- private static void InitializeTelegramClient(int apiId, string apiHash, string phoneNumber)
+ private void InitializeTelegramClient(int apiId, string apiHash, string phoneNumber)
{
var tmpTelegramClient = m_telegramClient;
- m_telegramClient = new TelegramClient(apiId, apiHash, m_sessionStore, phoneNumber);
+ m_telegramClient = new TelegramClient(apiId, apiHash, m_encSessionStore, phoneNumber);
tmpTelegramClient?.Dispose();
}
public void Dispose()
{
- // Do not dispose m_telegramClient because of "session-based" authentication.
+ m_telegramClient?.Dispose();
}
public string DisplayName { get; } = Strings.DisplayName;
@@ -136,14 +139,14 @@ namespace Duplicati.Library.Backend
var file = m_telegramClient.UploadFile(remotename, sr, cancelToken).GetAwaiter().GetResult();
cancelToken.ThrowIfCancellationRequested();
- var inputPeerChannel = new TLInputPeerChannel { ChannelId = channel.Id, AccessHash = (long)channel.AccessHash };
+ var inputPeerChannel = new TLInputPeerChannel {ChannelId = channel.Id, AccessHash = (long)channel.AccessHash};
var fileNameAttribute = new TLDocumentAttributeFilename
{
FileName = remotename
};
EnsureConnected(cancelToken);
- m_telegramClient.SendUploadedDocument(inputPeerChannel, file, remotename, "application/zip", new TLVector<TLAbsDocumentAttribute> { fileNameAttribute }, cancelToken).GetAwaiter().GetResult();
+ m_telegramClient.SendUploadedDocument(inputPeerChannel, file, remotename, "application/zip", new TLVector<TLAbsDocumentAttribute> {fileNameAttribute}, cancelToken).GetAwaiter().GetResult();
}
},
nameof(PutAsync));
@@ -158,7 +161,7 @@ namespace Duplicati.Library.Backend
var fileInfo = ListChannelFileInfos().First(fi => fi.Name == remotename);
var fileLocation = fileInfo.ToFileLocation();
- var limit = MEBIBYTE_IN_BYTES;
+ var limit = BYTES_IN_MEBIBYTE;
var currentOffset = 0;
@@ -219,7 +222,7 @@ namespace Duplicati.Library.Backend
ChannelId = channel.Id,
AccessHash = channel.AccessHash.Value
},
- Id = new TLVector<int> { fileInfo.MessageId }
+ Id = new TLVector<int> {fileInfo.MessageId}
};
EnsureConnected();
@@ -232,7 +235,7 @@ namespace Duplicati.Library.Backend
{
var channel = GetChannel();
- var inputPeerChannel = new TLInputPeerChannel { ChannelId = channel.Id, AccessHash = channel.AccessHash.Value };
+ var inputPeerChannel = new TLInputPeerChannel {ChannelId = channel.Id, AccessHash = channel.AccessHash.Value};
var result = new List<ChannelFileInfo>();
var cMinId = (int?)0;
@@ -352,8 +355,7 @@ namespace Duplicati.Library.Backend
yield return chat;
}
- if (tlDialogs?.Dialogs?.Count < 100 ||
- tlDialogsSlice?.Dialogs?.Count < 100)
+ if (tlDialogs?.Dialogs?.Count < 100 || tlDialogsSlice?.Dialogs?.Count < 100)
{
yield break;
}
@@ -389,25 +391,21 @@ namespace Duplicati.Library.Backend
try
{
- var phoneCodeHash = m_sessionStore.GetPhoneHash(m_phoneNumber);
- if (phoneCodeHash == null)
+ if (m_phoneCodeHash == null)
{
EnsureConnected();
- phoneCodeHash = m_telegramClient.SendCodeRequestAsync(m_phoneNumber).GetAwaiter().GetResult();
- m_sessionStore.SetPhoneHash(m_phoneNumber, phoneCodeHash);
+ m_phoneCodeHash = m_telegramClient.SendCodeRequestAsync(m_phoneNumber).GetAwaiter().GetResult();
m_telegramClient.Session.Save();
if (string.IsNullOrEmpty(m_authCode))
{
throw new UserInformationException(Strings.NoAuthCodeError, nameof(Strings.NoAuthCodeError));
}
- else
- {
- throw new UserInformationException(Strings.WrongAuthCodeError, nameof(Strings.WrongAuthCodeError));
- }
+
+ throw new UserInformationException(Strings.WrongAuthCodeError, nameof(Strings.WrongAuthCodeError));
}
- m_telegramClient.MakeAuthAsync(m_phoneNumber, phoneCodeHash, m_authCode).GetAwaiter().GetResult();
+ m_telegramClient.MakeAuthAsync(m_phoneNumber, m_phoneCodeHash, m_authCode).GetAwaiter().GetResult();
}
catch (CloudPasswordNeededException)
{
@@ -425,17 +423,28 @@ namespace Duplicati.Library.Backend
m_telegramClient.Session.Save();
}
+
private void EnsureConnected(CancellationToken? cancelToken = null)
{
var isConnected = false;
-
while (isConnected == false)
{
cancelToken?.ThrowIfCancellationRequested();
+ var innerCancelTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
+ var innerCancelToken = innerCancelTokenSource.Token;
try
{
- isConnected = m_telegramClient.ConnectAsync().Wait(TimeSpan.FromSeconds(5));
+ var connectTask = m_telegramClient.ConnectAsync(false, innerCancelToken);
+ connectTask.Wait(cancelToken ?? default);
+ isConnected = true;
+ }
+ catch (OperationCanceledException canceledException)
+ {
+ if (canceledException.CancellationToken != innerCancelToken)
+ {
+ throw;
+ }
}
catch (Exception)
{
diff --git a/Duplicati/Library/Backend/Telegram/packages.config b/Duplicati/Library/Backend/Telegram/packages.config
index b87a4d435..ecd8f06db 100644
--- a/Duplicati/Library/Backend/Telegram/packages.config
+++ b/Duplicati/Library/Backend/Telegram/packages.config
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
+ <package id="SharpAESCrypt.exe" version="1.3.3" targetFramework="net471" />
<package id="TLSharp" version="0.1.0.574" targetFramework="net471" />
</packages> \ No newline at end of file