From d02f456f6032a295d4a2c634792b55ff47f79e00 Mon Sep 17 00:00:00 2001 From: "Vault.ooo" Date: Thu, 1 Apr 2021 21:29:24 +0200 Subject: [PATCH] Add Vault.ooo uploader service --- ShareX.UploadersLib/Enums.cs | 4 + .../FileUploaders/Vault_ooo.cs | 270 ++++++++++++++++++ .../ImageUploaders/Vault_ooo.cs | 40 +++ 3 files changed, 314 insertions(+) create mode 100644 ShareX.UploadersLib/FileUploaders/Vault_ooo.cs create mode 100644 ShareX.UploadersLib/ImageUploaders/Vault_ooo.cs diff --git a/ShareX.UploadersLib/Enums.cs b/ShareX.UploadersLib/Enums.cs index ad72320d9..71699861d 100644 --- a/ShareX.UploadersLib/Enums.cs +++ b/ShareX.UploadersLib/Enums.cs @@ -47,6 +47,8 @@ public enum ImageDestination Chevereto, [Description("vgy.me")] Vgyme, + [Description("Vault.ooo (encrypted)")] + Vault_ooo, CustomImageUploader, // Localized FileUploader // Localized } @@ -143,6 +145,8 @@ public enum FileDestination Plik, [Description("YouTube")] YouTube, + [Description("Vault.ooo (encrypted)")] + Vault_ooo, SharedFolder, // Localized Email, // Localized CustomFileUploader // Localized diff --git a/ShareX.UploadersLib/FileUploaders/Vault_ooo.cs b/ShareX.UploadersLib/FileUploaders/Vault_ooo.cs new file mode 100644 index 000000000..0964c3efc --- /dev/null +++ b/ShareX.UploadersLib/FileUploaders/Vault_ooo.cs @@ -0,0 +1,270 @@ +#region License Information (GPL v3) + +/* + ShareX - A program that allows you to take screenshots and share any file type + Copyright (c) 2007-2020 ShareX Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Optionally you can also view the license at . +*/ + +#endregion License Information (GPL v3) + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using ShareX.HelpersLib; +using Newtonsoft.Json; + +namespace ShareX.UploadersLib.FileUploaders +{ + public class Vault_oooFileUploaderService : FileUploaderService + { + public override FileDestination EnumValue { get; } = FileDestination.Vault_ooo; + + public override bool CheckConfig(UploadersConfig config) + { + return true; + } + + public override GenericUploader CreateUploader(UploadersConfig config, TaskReferenceHelper taskInfo) + { + return new Vault_ooo(); + } + } + + public sealed class Vault_ooo : FileUploader + { + private const string APIURL = "https://vault.ooo"; + private const int PBKDF2_ITERATIONS = 10000; + private const int AES_KEY_SIZE = 256; // Bits + private const int AES_BLOCK_SIZE = 128; // Bits + private const int BYTE_CHUNK_SIZE = 256 * 1024 * 381; // Copied from web client (99 MB) + private static readonly DateTime ORIGIN_TIME = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + public override UploadResult Upload(Stream stream, string fileName) + { + #region Calculating sizes + + long fileSize = stream.Length; + int chunks = (int)Math.Ceiling((double)fileSize / BYTE_CHUNK_SIZE); + long fullUploadSize = 16; // 16 bytes header + + List uploadSizes = new List(); + uploadSizes.Add(0); + LoopStartEnd((chunkStart, chunkEnd, i) => + { + int chunkLength = chunkEnd - chunkStart; + fullUploadSize += chunkLength + 16 - (chunkLength % 16); + uploadSizes.Add(fullUploadSize); + }, chunks, fileSize); + + #endregion + + string randomKey = GenerateRandomKey(); + byte[] randomKeyBytes = Encoding.UTF8.GetBytes(randomKey); + Vault_oooCryptoData cryptoData = DeriveCryptoData(randomKeyBytes); + + #region Building filename + + byte[] fileNameBytes = Encoding.UTF8.GetBytes(fileName); + string encryptedFileName; + using (MemoryStream ms = new MemoryStream()) // Encrypting file name + { + ms.Write(cryptoData.Salt, 0, cryptoData.Salt.Length); + byte[] encryptedFn = EncryptBytes(cryptoData, fileNameBytes); + ms.Write(encryptedFn, 0, encryptedFn.Length); + encryptedFileName = Helpers.BytesToHex(ms.ToArray()); + } + string bytesLengthHex = fullUploadSize.ToString("X4"); // To Hex + DateTime expiryTime = DateTime.UtcNow.AddDays(30); // Defaults from the web client + string expiryTimeHex = ((long)(expiryTime - ORIGIN_TIME).TotalSeconds).ToString("X4"); // Expiry date in UNIX seconds in hex + string fullFileName = $"{expiryTimeHex}-b-{bytesLengthHex}-{encryptedFileName}".ToLower(); + + #endregion + + Dictionary requestHeaders = new Dictionary(); + requestHeaders.Add("X-Get-Raw-File", "1"); + Dictionary postRequestJson = new Dictionary(); + postRequestJson.Add("chunks", chunks); + postRequestJson.Add("fileLength", fullUploadSize); + + + string postResult = SendRequest(HttpMethod.POST, URLHelpers.CombineURL(APIURL, fullFileName), JsonConvert.SerializeObject(postRequestJson), RequestHelpers.ContentTypeJSON, requestHeaders); + Vault_oooMetaInfo metaInfo = JsonConvert.DeserializeObject(postResult); + + if (string.IsNullOrEmpty(metaInfo.UrlPathName)) + throw new InvalidOperationException("No correct metaInfo returned"); + + #region Upload in chunks + + List dumpStash = new List(); + LoopStartEnd((chunkStart, chunkEnd, i) => + { + int chunkLength = chunkEnd - chunkStart; + byte[] plainBytes = new byte[chunkLength]; + stream.Read(plainBytes, 0, chunkLength); + + byte[] encryptedBytes = EncryptBytes(cryptoData, plainBytes); + + int prependSize = 0; + if(dumpStash.Count > 0) + { + using(MemoryStream ms = new MemoryStream()) + { + ms.Write(dumpStash.ToArray(), 0, dumpStash.Count); + ms.Write(encryptedBytes, 0, encryptedBytes.Length); + encryptedBytes = ms.ToArray(); + } + + prependSize = dumpStash.Count; + dumpStash.Clear(); + } + + if(encryptedBytes.Length + (i == 0 ? 16 : 0) > BYTE_CHUNK_SIZE) // 16 bytes for the salt header + { + dumpStash.AddRange(encryptedBytes.Skip(BYTE_CHUNK_SIZE - (i == 0 ? 16 : 0))); + encryptedBytes = encryptedBytes.Take(BYTE_CHUNK_SIZE - (i == 0 ? 16 : 0)).ToArray(); + } + + using (MemoryStream ms = new MemoryStream()) + { + if (i == 0) + { + ms.Write(Encoding.UTF8.GetBytes("Salted__"), 0, 8); // Write header + ms.Write(cryptoData.Salt, 0, cryptoData.Salt.Length); // 8 bytes + ms.Write(encryptedBytes, 0, encryptedBytes.Length); + } + else + { + ms.Write(encryptedBytes, 0, encryptedBytes.Length); // Write encrypted bytes + } + + NameValueCollection headers = new NameValueCollection(); + headers.Add("X-Get-Raw-File", "1"); + int uploadChunkStart = (int)(uploadSizes[i] - prependSize); + headers.Add("X-Put-Chunk-Start", uploadChunkStart.ToString()); + headers.Add("X-Put-Chunk-End", (uploadChunkStart + ms.Length).ToString()); + headers.Add("X-Put-JWT", metaInfo.Token); + + SendRequest(HttpMethod.PUT, URLHelpers.CombineURL(APIURL, metaInfo.UrlPathName), ms, "application/octet-stream", null, headers); + } + }, chunks, fileSize); + + #endregion + + + UploadResult res = new UploadResult(); + res.IsURLExpected = true; + res.URL = URLHelpers.CombineURL(APIURL, metaInfo.UrlPathName) + "#" + randomKey; // Full url with the encryption key + + return res; + } + + private delegate void StartEndCallback(int chunkStart, int chunkEnd, int i); + + private static void LoopStartEnd(StartEndCallback callback, int chunks, long fileSize) + { + int lastChunkEnd = 0; + for (int i = 0; i < chunks; i++) + { + int chunkStart, chunkEnd; + + if (i == 0) + { + chunkStart = 0; + lastChunkEnd = chunkEnd = (int)Math.Min(fileSize, BYTE_CHUNK_SIZE); + } + else + { + chunkStart = lastChunkEnd; + lastChunkEnd = chunkEnd = (int)Math.Min(fileSize, lastChunkEnd + BYTE_CHUNK_SIZE); + } + + callback(chunkStart, chunkEnd, i); + } + } + + #region Crypto + + + private static string GenerateRandomKey() + { + return Guid.NewGuid().ToString(); // The web client uses random uuids as keys + } + + private static Vault_oooCryptoData DeriveCryptoData(byte[] key) + { + byte[] salt = new byte[8]; // 8 bytes salt like in the web client + RandomNumberGenerator rng = RandomNumberGenerator.Create(); // Cryptographically secure + rng.GetBytes(salt); + + Rfc2898DeriveBytes rfcDeriver = new Rfc2898DeriveBytes(key, salt, PBKDF2_ITERATIONS, HashAlgorithmName.SHA256); + + return new Vault_oooCryptoData + { + Salt = salt, + Key = rfcDeriver.GetBytes(AES_KEY_SIZE / 8), // Derive the bytes from the rfcDeriver; Divide by 8 to input byte count + IV = rfcDeriver.GetBytes(AES_BLOCK_SIZE / 8) + }; + } + + private static byte[] EncryptBytes(Vault_oooCryptoData crypto, byte[] bytes) + { + using (AesManaged aes = new AesManaged()) + { + aes.Mode = CipherMode.CBC; + aes.KeySize = AES_KEY_SIZE; + aes.BlockSize = AES_BLOCK_SIZE; + + aes.Key = crypto.Key; + aes.IV = crypto.IV; + + using (MemoryStream ms = new MemoryStream()) + { + using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(crypto.Key, crypto.IV), CryptoStreamMode.Write)) + { + cs.Write(bytes, 0, bytes.Length); // Write all bytes into the CryptoStream + cs.Close(); + return ms.ToArray(); + } + } + } + } + + private class Vault_oooCryptoData + { + public byte[] Salt { get; set; } + public byte[] Key { get; set; } + public byte[] IV { get; set; } + } + + #endregion + + private class Vault_oooMetaInfo + { + [JsonProperty("urlPathName")] + public string UrlPathName { get; set; } + + [JsonProperty("token")] + public string Token { get; set; } + } + } +} \ No newline at end of file diff --git a/ShareX.UploadersLib/ImageUploaders/Vault_ooo.cs b/ShareX.UploadersLib/ImageUploaders/Vault_ooo.cs new file mode 100644 index 000000000..f8be179f2 --- /dev/null +++ b/ShareX.UploadersLib/ImageUploaders/Vault_ooo.cs @@ -0,0 +1,40 @@ +#region License Information (GPL v3) + +/* + ShareX - A program that allows you to take screenshots and share any file type + Copyright (c) 2007-2020 ShareX Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Optionally you can also view the license at . +*/ + +#endregion License Information (GPL v3) + + +namespace ShareX.UploadersLib.ImageUploaders +{ + public class Vault_oooUploaderService : ImageUploaderService + { + public override ImageDestination EnumValue { get; } = ImageDestination.Vault_ooo; + + public override bool CheckConfig(UploadersConfig config) => true; + + public override GenericUploader CreateUploader(UploadersConfig config, TaskReferenceHelper taskInfo) + { + return new FileUploaders.Vault_ooo(); + } + } +}