#region License Information (GPL v3) /* ShareX - A program that allows you to take screenshots and share any file type Copyright (c) 2007-2016 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) // Credits: https://github.com/Upload using Newtonsoft.Json; using Security.Cryptography; using ShareX.HelpersLib; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; namespace ShareX.UploadersLib.FileUploaders { public class Up1FileUploaderService : FileUploaderService { public override FileDestination EnumValue { get; } = FileDestination.Up1; public override bool CheckConfig(UploadersConfig uploadersConfig) => true; public override FileUploader CreateUploader(UploadersConfig uploadersConfig, TaskReferenceHelper taskInfo) { return new Up1(uploadersConfig.Up1Host, uploadersConfig.Up1Key); } } public sealed class Up1 : FileUploader { /* * Up1 is an encrypted image uploader. The idea is that any URL (for example, https://up1.ca/#hsd2mdSuIkzTUR6saZpn1Q) contains * what is called a "seed". In this case, the seed is "hsd2mdSuIkzTUR6saZpn1Q". With this, we use sha512(seed) (output 64 bytes) * in order to derive an AES key (32 bytes), a CCM IV (16 bytes), and a server-side identifier (16 bytes). These are used to * encrypt and store the data. * * Within the encrypted blob, There is a double-null-terminated UTF-16 JSON object that contains metadata like the filename and mimetype. * This is prepended to the image data. */ private const int MacSize = 64; public string SystemUrl { get; set; } public string ApiKey { get; set; } public Up1(string systemUrl, string apiKey) { if (string.IsNullOrEmpty(systemUrl)) { SystemUrl = "https://up1.ca"; } else { SystemUrl = systemUrl; } if (string.IsNullOrEmpty(apiKey)) { ApiKey = "c61540b5ceecd05092799f936e27755f"; } else { ApiKey = apiKey; } } /* Since we're dealing with URLs, the regular base64 encoding using +, / and = will mess things up * Therefore we'll use the URL base64 standard (as defined in the RFC) */ private static string UrlBase64Encode(byte[] input) { return Convert.ToBase64String(input).Replace("=", "").Replace("+", "-").Replace("/", "_"); } /* SJCL (the Javascript library powering the crypto in the browser) depends on the length of the input * in order to calculate CCM's IV length. This is the algorithm it uses. */ private static long FindIVLen(long bufferLength) { if (bufferLength < 0xFFFF) return 15 - 2; if (bufferLength < 0xFFFFFF) return 15 - 3; return 15 - 4; } /* Given an input seed, derive the required output. */ private static void DeriveParams(byte[] seed, out byte[] key, out byte[] iv, out string ident) { // Hash the output using sha512 byte[] seed_output; using (SHA512CryptoServiceProvider sha512csp = new SHA512CryptoServiceProvider()) { seed_output = sha512csp.ComputeHash(seed); } // Take key from first 32 bytes key = new byte[32]; Buffer.BlockCopy(seed_output, 0, key, 0, 32); // Take IV from next 16 bytes iv = new byte[16]; Buffer.BlockCopy(seed_output, 32, iv, 0, 16); // Take server identifier (the "ident") from last 16 bytes and base64url encode it byte[] ident_raw = new byte[16]; Buffer.BlockCopy(seed_output, 48, ident_raw, 0, 16); ident = UrlBase64Encode(ident_raw); } private static MemoryStream Encrypt(Stream source, string fileName, out string seed_encoded, out string ident) { // Randomly generate a new seed for upload byte[] seed = new byte[16]; using (RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider()) { rngCsp.GetBytes(seed); } seed_encoded = UrlBase64Encode(seed); // Derive the parameters (key, IV, ident) from the seed byte[] key, iv; DeriveParams(seed, out key, out iv, out ident); // Create a new String->String map for JSON blob, and define filename and metadata Dictionary metadataMap = new Dictionary(); metadataMap["mime"] = Helpers.IsTextFile(fileName) ? "text/plain" : Helpers.GetMimeType(fileName); metadataMap["name"] = fileName; // Encode the metadata with UTF-16 and a double-null-byte terminator, and append data // Unfortunately, the CCM cipher mode can't stream the encryption, and so we have to GetBytes() on the source. // We do limit the source to 50MB however byte[] data = Encoding.BigEndianUnicode.GetBytes(JsonConvert.SerializeObject(metadataMap)).Concat(new byte[] { 0, 0 }).Concat(source.GetBytes()).ToArray(); // Calculate the length of the CCM IV and copy it over long ccmIVLen = FindIVLen(data.Length); byte[] ccmIV = new byte[ccmIVLen]; Array.Copy(iv, ccmIV, ccmIVLen); // http://blogs.msdn.com/b/shawnfa/archive/2009/03/17/authenticated-symmetric-encryption-in-net.aspx using (AuthenticatedAesCng aes = new AuthenticatedAesCng()) { aes.CngMode = CngChainingMode.Ccm; aes.Key = key; aes.IV = ccmIV; aes.TagSize = MacSize; MemoryStream ms = new MemoryStream(); using (IAuthenticatedCryptoTransform encryptor = aes.CreateAuthenticatedEncryptor()) { CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write); cs.Write(data, 0, data.Length); cs.FlushFinalBlock(); byte[] tag = encryptor.GetTag(); ms.Write(tag, 0, tag.Length); return ms; } } } public override UploadResult Upload(Stream input, string fileName) { // Make sure the file (or memory stream) is less than 50MB if (input.Length > 50000000) { throw new ArgumentException("Input files for Up1 cannot be more than 50MB in size."); } // Initialize the encrypted stream UploadResult result; string seed, ident; using (MemoryStream encryptedStream = Encrypt(input, fileName, out seed, out ident)) { // Set up the form upload Dictionary uploadArgs = new Dictionary(); uploadArgs["ident"] = ident; uploadArgs["api_key"] = ApiKey; // Upload and stream encrypt result = UploadData(encryptedStream, URLHelpers.CombineURL(SystemUrl, "up"), "blob", "file", uploadArgs); } if (result.IsSuccess) { // Return the output URLs result.URL = URLHelpers.CombineURL(SystemUrl, "#" + seed); Up1Response response = JsonConvert.DeserializeObject(result.Response); if (response != null) { result.DeletionURL = URLHelpers.CombineURL(SystemUrl, string.Format("del?ident={0}&delkey={1}", ident, response.DelKey)); } } return result; } private class Up1Response { public string DelKey { get; set; } } } }