159 lines
5.0 KiB
C#
159 lines
5.0 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Duplicati.Library.AutoUpdater;
|
|
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);
|
|
var appName = AutoUpdateSettings.AppName;
|
|
m_teleDataPath = Path.Combine(appData, appName, nameof(Telegram));
|
|
m_password = password;
|
|
|
|
Directory.CreateDirectory(m_teleDataPath);
|
|
}
|
|
|
|
|
|
public void Save(Session session)
|
|
{
|
|
if (session.AuthKey == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
} |