CryptoGridAssetClient.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. /*
  2. * Copyright (c) Contributors, http://www.openmetaverse.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. /*
  28. * This file includes content derived from Obviex.
  29. * Copyright (C) 2002 Obviex(TM). All rights reserved.
  30. * http://www.obviex.com/samples/Encryption.aspx
  31. */
  32. using System;
  33. using System.Collections.Generic;
  34. using System.IO;
  35. using System.Reflection;
  36. using System.Security.Cryptography;
  37. using System.Text;
  38. using System.Xml.Serialization;
  39. using log4net;
  40. using OpenSim.Framework.Servers.HttpServer;
  41. namespace OpenSim.Framework.Communications.Cache
  42. {
  43. public class CryptoGridAssetClient : AssetServerBase
  44. {
  45. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  46. private string _assetServerUrl;
  47. private bool m_encryptOnUpload;
  48. private RjinKeyfile m_encryptKey;
  49. private readonly Dictionary<string,RjinKeyfile> m_keyfiles = new Dictionary<string, RjinKeyfile>();
  50. #region IPlugin
  51. public override string Name
  52. {
  53. get { return "Crypto"; }
  54. }
  55. public override string Version
  56. {
  57. get { return "1.0"; }
  58. }
  59. public override void Initialise(ConfigSettings p_set, string p_url, string p_dir, bool p_t)
  60. {
  61. m_log.Debug("[CRYPTOGRID] Plugin configured initialisation");
  62. Initialise(p_url, p_dir, p_t);
  63. }
  64. #endregion
  65. #region Keyfile Classes
  66. [Serializable]
  67. public class RjinKeyfile
  68. {
  69. public string Secret;
  70. public string AlsoKnownAs;
  71. public int Keysize;
  72. public string IVBytes;
  73. public string Description = "OpenSim Key";
  74. private static string SHA1Hash(byte[] bytes)
  75. {
  76. SHA1 sha1 = SHA1CryptoServiceProvider.Create();
  77. byte[] dataMd5 = sha1.ComputeHash(bytes);
  78. StringBuilder sb = new StringBuilder();
  79. for (int i = 0; i < dataMd5.Length; i++)
  80. sb.AppendFormat("{0:x2}", dataMd5[i]);
  81. return sb.ToString();
  82. }
  83. public void GenerateRandom()
  84. {
  85. RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();
  86. byte[] genSec = new byte[32];
  87. byte[] genAKA = new byte[32];
  88. byte[] genIV = new byte[32];
  89. Gen.GetBytes(genSec);
  90. Gen.GetBytes(genAKA);
  91. Gen.GetBytes(genIV);
  92. Secret = SHA1Hash(genSec);
  93. AlsoKnownAs = SHA1Hash(genAKA);
  94. IVBytes = SHA1Hash(genIV).Substring(0, 16);
  95. Keysize = 256;
  96. }
  97. }
  98. #endregion
  99. #region Rjindael
  100. /// <summary>
  101. /// This class uses a symmetric key algorithm (Rijndael/AES) to encrypt and
  102. /// decrypt data. As long as encryption and decryption routines use the same
  103. /// parameters to generate the keys, the keys are guaranteed to be the same.
  104. /// The class uses static functions with duplicate code to make it easier to
  105. /// demonstrate encryption and decryption logic. In a real-life application,
  106. /// this may not be the most efficient way of handling encryption, so - as
  107. /// soon as you feel comfortable with it - you may want to redesign this class.
  108. /// </summary>
  109. public class UtilRijndael
  110. {
  111. /// <summary>
  112. /// Encrypts specified plaintext using Rijndael symmetric key algorithm
  113. /// and returns a base64-encoded result.
  114. /// </summary>
  115. /// <param name="plainText">
  116. /// Plaintext value to be encrypted.
  117. /// </param>
  118. /// <param name="passPhrase">
  119. /// Passphrase from which a pseudo-random password will be derived. The
  120. /// derived password will be used to generate the encryption key.
  121. /// Passphrase can be any string. In this example we assume that this
  122. /// passphrase is an ASCII string.
  123. /// </param>
  124. /// <param name="saltValue">
  125. /// Salt value used along with passphrase to generate password. Salt can
  126. /// be any string. In this example we assume that salt is an ASCII string.
  127. /// </param>
  128. /// <param name="hashAlgorithm">
  129. /// Hash algorithm used to generate password. Allowed values are: "MD5" and
  130. /// "SHA1". SHA1 hashes are a bit slower, but more secure than MD5 hashes.
  131. /// </param>
  132. /// <param name="passwordIterations">
  133. /// Number of iterations used to generate password. One or two iterations
  134. /// should be enough.
  135. /// </param>
  136. /// <param name="initVector">
  137. /// Initialization vector (or IV). This value is required to encrypt the
  138. /// first block of plaintext data. For RijndaelManaged class IV must be
  139. /// exactly 16 ASCII characters long.
  140. /// </param>
  141. /// <param name="keySize">
  142. /// Size of encryption key in bits. Allowed values are: 128, 192, and 256.
  143. /// Longer keys are more secure than shorter keys.
  144. /// </param>
  145. /// <returns>
  146. /// Encrypted value formatted as a base64-encoded string.
  147. /// </returns>
  148. public static byte[] Encrypt(byte[] plainText,
  149. string passPhrase,
  150. string saltValue,
  151. string hashAlgorithm,
  152. int passwordIterations,
  153. string initVector,
  154. int keySize)
  155. {
  156. // Convert strings into byte arrays.
  157. // Let us assume that strings only contain ASCII codes.
  158. // If strings include Unicode characters, use Unicode, UTF7, or UTF8
  159. // encoding.
  160. byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
  161. byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
  162. // Convert our plaintext into a byte array.
  163. // Let us assume that plaintext contains UTF8-encoded characters.
  164. byte[] plainTextBytes = plainText;
  165. // First, we must create a password, from which the key will be derived.
  166. // This password will be generated from the specified passphrase and
  167. // salt value. The password will be created using the specified hash
  168. // algorithm. Password creation can be done in several iterations.
  169. PasswordDeriveBytes password = new PasswordDeriveBytes(
  170. passPhrase,
  171. saltValueBytes,
  172. hashAlgorithm,
  173. passwordIterations);
  174. // Use the password to generate pseudo-random bytes for the encryption
  175. // key. Specify the size of the key in bytes (instead
  176. // of bits).
  177. #pragma warning disable 0618
  178. byte[] keyBytes = password.GetBytes(keySize / 8);
  179. #pragma warning restore 0618
  180. // Create uninitialized Rijndael encryption object.
  181. RijndaelManaged symmetricKey = new RijndaelManaged();
  182. // It is reasonable to set encryption mode to Cipher Block Chaining
  183. // (CBC). Use default options for other symmetric key parameters.
  184. symmetricKey.Mode = CipherMode.CBC;
  185. // Generate encryptor from the existing key bytes and initialization
  186. // vector. Key size will be defined based on the number of the key
  187. // bytes.
  188. ICryptoTransform encryptor = symmetricKey.CreateEncryptor(
  189. keyBytes,
  190. initVectorBytes);
  191. // Define memory stream which will be used to hold encrypted data.
  192. MemoryStream memoryStream = new MemoryStream();
  193. // Define cryptographic stream (always use Write mode for encryption).
  194. CryptoStream cryptoStream = new CryptoStream(memoryStream,
  195. encryptor,
  196. CryptoStreamMode.Write);
  197. // Start encrypting.
  198. cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
  199. // Finish encrypting.
  200. cryptoStream.FlushFinalBlock();
  201. // Convert our encrypted data from a memory stream into a byte array.
  202. byte[] cipherTextBytes = memoryStream.ToArray();
  203. // Close both streams.
  204. memoryStream.Close();
  205. cryptoStream.Close();
  206. // Return encrypted string.
  207. return cipherTextBytes;
  208. }
  209. /// <summary>
  210. /// Decrypts specified ciphertext using Rijndael symmetric key algorithm.
  211. /// </summary>
  212. /// <param name="cipherText">
  213. /// Base64-formatted ciphertext value.
  214. /// </param>
  215. /// <param name="passPhrase">
  216. /// Passphrase from which a pseudo-random password will be derived. The
  217. /// derived password will be used to generate the encryption key.
  218. /// Passphrase can be any string. In this example we assume that this
  219. /// passphrase is an ASCII string.
  220. /// </param>
  221. /// <param name="saltValue">
  222. /// Salt value used along with passphrase to generate password. Salt can
  223. /// be any string. In this example we assume that salt is an ASCII string.
  224. /// </param>
  225. /// <param name="hashAlgorithm">
  226. /// Hash algorithm used to generate password. Allowed values are: "MD5" and
  227. /// "SHA1". SHA1 hashes are a bit slower, but more secure than MD5 hashes.
  228. /// </param>
  229. /// <param name="passwordIterations">
  230. /// Number of iterations used to generate password. One or two iterations
  231. /// should be enough.
  232. /// </param>
  233. /// <param name="initVector">
  234. /// Initialization vector (or IV). This value is required to encrypt the
  235. /// first block of plaintext data. For RijndaelManaged class IV must be
  236. /// exactly 16 ASCII characters long.
  237. /// </param>
  238. /// <param name="keySize">
  239. /// Size of encryption key in bits. Allowed values are: 128, 192, and 256.
  240. /// Longer keys are more secure than shorter keys.
  241. /// </param>
  242. /// <returns>
  243. /// Decrypted string value.
  244. /// </returns>
  245. /// <remarks>
  246. /// Most of the logic in this function is similar to the Encrypt
  247. /// logic. In order for decryption to work, all parameters of this function
  248. /// - except cipherText value - must match the corresponding parameters of
  249. /// the Encrypt function which was called to generate the
  250. /// ciphertext.
  251. /// </remarks>
  252. public static byte[] Decrypt(byte[] cipherText,
  253. string passPhrase,
  254. string saltValue,
  255. string hashAlgorithm,
  256. int passwordIterations,
  257. string initVector,
  258. int keySize)
  259. {
  260. // Convert strings defining encryption key characteristics into byte
  261. // arrays. Let us assume that strings only contain ASCII codes.
  262. // If strings include Unicode characters, use Unicode, UTF7, or UTF8
  263. // encoding.
  264. byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
  265. byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
  266. // Convert our ciphertext into a byte array.
  267. byte[] cipherTextBytes = cipherText;
  268. // First, we must create a password, from which the key will be
  269. // derived. This password will be generated from the specified
  270. // passphrase and salt value. The password will be created using
  271. // the specified hash algorithm. Password creation can be done in
  272. // several iterations.
  273. PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase,
  274. saltValueBytes,
  275. hashAlgorithm,
  276. passwordIterations);
  277. // Use the password to generate pseudo-random bytes for the encryption
  278. // key. Specify the size of the key in bytes (instead
  279. // of bits).
  280. #pragma warning disable 0618
  281. byte[] keyBytes = password.GetBytes(keySize / 8);
  282. #pragma warning restore 0618
  283. // Create uninitialized Rijndael encryption object.
  284. RijndaelManaged symmetricKey = new RijndaelManaged();
  285. // It is reasonable to set encryption mode to Cipher Block Chaining
  286. // (CBC). Use default options for other symmetric key parameters.
  287. symmetricKey.Mode = CipherMode.CBC;
  288. // Generate decryptor from the existing key bytes and initialization
  289. // vector. Key size will be defined based on the number of the key
  290. // bytes.
  291. ICryptoTransform decryptor = symmetricKey.CreateDecryptor(
  292. keyBytes,
  293. initVectorBytes);
  294. // Define memory stream which will be used to hold encrypted data.
  295. MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
  296. // Define cryptographic stream (always use Read mode for encryption).
  297. CryptoStream cryptoStream = new CryptoStream(memoryStream,
  298. decryptor,
  299. CryptoStreamMode.Read);
  300. // Since at this point we don't know what the size of decrypted data
  301. // will be, allocate the buffer long enough to hold ciphertext;
  302. // plaintext is never longer than ciphertext.
  303. byte[] plainTextBytes = new byte[cipherTextBytes.Length];
  304. // Start decrypting.
  305. int decryptedByteCount = cryptoStream.Read(plainTextBytes,
  306. 0,
  307. plainTextBytes.Length);
  308. // Close both streams.
  309. memoryStream.Close();
  310. cryptoStream.Close();
  311. byte[] plainText = new byte[decryptedByteCount];
  312. int i;
  313. for (i = 0; i < decryptedByteCount; i++)
  314. plainText[i] = plainTextBytes[i];
  315. // Return decrypted string.
  316. return plainText;
  317. }
  318. }
  319. #endregion
  320. public CryptoGridAssetClient() {}
  321. public CryptoGridAssetClient(string serverUrl, string keydir, bool decOnly)
  322. {
  323. m_log.Debug("[CRYPTOGRID] Direct constructor");
  324. Initialise(serverUrl, keydir, decOnly);
  325. }
  326. public void Initialise(string serverUrl, string keydir, bool decOnly)
  327. {
  328. m_log.Debug("[CRYPTOGRID] Common constructor");
  329. _assetServerUrl = serverUrl;
  330. string[] keys = Directory.GetFiles(keydir, "*.deckey");
  331. foreach (string key in keys)
  332. {
  333. XmlSerializer xs = new XmlSerializer(typeof (RjinKeyfile));
  334. FileStream file = new FileStream(key, FileMode.Open, FileAccess.Read);
  335. RjinKeyfile rjkey = (RjinKeyfile) xs.Deserialize(file);
  336. file.Close();
  337. m_keyfiles.Add(rjkey.AlsoKnownAs, rjkey);
  338. }
  339. keys = Directory.GetFiles(keydir, "*.enckey");
  340. if (keys.Length == 1)
  341. {
  342. string Ekey = keys[0];
  343. XmlSerializer Exs = new XmlSerializer(typeof (RjinKeyfile));
  344. FileStream Efile = new FileStream(Ekey, FileMode.Open, FileAccess.Read);
  345. RjinKeyfile Erjkey = (RjinKeyfile) Exs.Deserialize(Efile);
  346. Efile.Close();
  347. m_keyfiles.Add(Erjkey.AlsoKnownAs, Erjkey);
  348. m_encryptKey = Erjkey;
  349. } else
  350. {
  351. if (keys.Length > 1)
  352. throw new Exception(
  353. "You have more than one asset *encryption* key. (You should never have more than one)," +
  354. "If you downloaded this key from someone, rename it to <filename>.deckey to convert it to" +
  355. "a decryption-only key.");
  356. m_log.Warn("No encryption key found, generating a new one for you...");
  357. RjinKeyfile encKey = new RjinKeyfile();
  358. encKey.GenerateRandom();
  359. m_encryptKey = encKey;
  360. FileStream encExportFile = new FileStream("mysecretkey_rename_me.enckey",FileMode.CreateNew);
  361. XmlSerializer xs = new XmlSerializer(typeof(RjinKeyfile));
  362. xs.Serialize(encExportFile, encKey);
  363. encExportFile.Flush();
  364. encExportFile.Close();
  365. m_log.Info(
  366. "Encryption file generated, please rename 'mysecretkey_rename_me.enckey' to something more appropriate (however preserve the file extension).");
  367. }
  368. // If Decrypt-Only, dont encrypt on upload
  369. m_encryptOnUpload = !decOnly;
  370. }
  371. private static void EncryptAssetBase(AssetBase x, RjinKeyfile file)
  372. {
  373. // Make a salt
  374. RNGCryptoServiceProvider RandomGen = new RNGCryptoServiceProvider();
  375. byte[] rand = new byte[32];
  376. RandomGen.GetBytes(rand);
  377. string salt = Convert.ToBase64String(rand);
  378. x.Data = UtilRijndael.Encrypt(x.Data, file.Secret, salt, "SHA1", 2, file.IVBytes, file.Keysize);
  379. x.Description = String.Format("ENCASS#:~:#{0}#:~:#{1}#:~:#{2}#:~:#{3}",
  380. "OPENSIM_AES_AF1",
  381. file.AlsoKnownAs,
  382. salt,
  383. x.Description);
  384. }
  385. private bool DecryptAssetBase(AssetBase x)
  386. {
  387. // Check it's encrypted first.
  388. if (!x.Description.Contains("ENCASS"))
  389. return true;
  390. // ENCASS:ALG:AKA:SALT:Description
  391. // 0 1 2 3 4
  392. string[] splitchars = new string[1];
  393. splitchars[0] = "#:~:#";
  394. string[] meta = x.Description.Split(splitchars, StringSplitOptions.None);
  395. if (meta.Length < 5)
  396. {
  397. m_log.Warn("[ENCASSETS] Recieved Encrypted Asset, but header is corrupt");
  398. return false;
  399. }
  400. // Check if we have a matching key
  401. if (m_keyfiles.ContainsKey(meta[2]))
  402. {
  403. RjinKeyfile deckey = m_keyfiles[meta[2]];
  404. x.Description = meta[4];
  405. switch (meta[1])
  406. {
  407. case "OPENSIM_AES_AF1":
  408. x.Data = UtilRijndael.Decrypt(x.Data,
  409. deckey.Secret,
  410. meta[3],
  411. "SHA1",
  412. 2,
  413. deckey.IVBytes,
  414. deckey.Keysize);
  415. // Decrypted Successfully
  416. return true;
  417. default:
  418. m_log.Warn(
  419. "[ENCASSETS] Recieved Encrypted Asset, but we dont know how to decrypt '" + meta[1] + "'.");
  420. // We dont understand this encryption scheme
  421. return false;
  422. }
  423. }
  424. m_log.Warn("[ENCASSETS] Recieved Encrypted Asset, but we do not have the decryption key.");
  425. return false;
  426. }
  427. #region IAssetServer Members
  428. protected override AssetBase GetAsset(AssetRequest req)
  429. {
  430. #if DEBUG
  431. //m_log.DebugFormat("[GRID ASSET CLIENT]: Querying for {0}", req.AssetID.ToString());
  432. #endif
  433. RestClient rc = new RestClient(_assetServerUrl);
  434. rc.AddResourcePath("assets");
  435. rc.AddResourcePath(req.AssetID.ToString());
  436. if (req.IsTexture)
  437. rc.AddQueryParameter("texture");
  438. rc.RequestMethod = "GET";
  439. Stream s = rc.Request();
  440. if (s == null)
  441. return null;
  442. if (s.Length > 0)
  443. {
  444. XmlSerializer xs = new XmlSerializer(typeof(AssetBase));
  445. AssetBase encAsset = (AssetBase)xs.Deserialize(s);
  446. // Try decrypt it
  447. if (DecryptAssetBase(encAsset))
  448. return encAsset;
  449. }
  450. return null;
  451. }
  452. public override void UpdateAsset(AssetBase asset)
  453. {
  454. throw new Exception("The method or operation is not implemented.");
  455. }
  456. public override void StoreAsset(AssetBase asset)
  457. {
  458. if (m_encryptOnUpload)
  459. EncryptAssetBase(asset, m_encryptKey);
  460. try
  461. {
  462. string assetUrl = _assetServerUrl + "/assets/";
  463. m_log.InfoFormat("[CRYPTO GRID ASSET CLIENT]: Sending store request for asset {0}", asset.FullID);
  464. RestObjectPoster.BeginPostObject<AssetBase>(assetUrl, asset);
  465. }
  466. catch (Exception e)
  467. {
  468. m_log.ErrorFormat("[CRYPTO GRID ASSET CLIENT]: {0}", e);
  469. }
  470. }
  471. #endregion
  472. }
  473. }