aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/Communications/Cache/CryptoGridAssetClient.cs
diff options
context:
space:
mode:
authorAdam Frisby2008-10-14 08:54:46 +0000
committerAdam Frisby2008-10-14 08:54:46 +0000
commit54d7be8a49cbbd47217df84c131221b19e66f0e3 (patch)
tree0c29dd79163dda99da1855d523e4c8c0b70c32f0 /OpenSim/Framework/Communications/Cache/CryptoGridAssetClient.cs
parent* Cleaned up tons of code duplication in ODEPrim (diff)
downloadopensim-SC-54d7be8a49cbbd47217df84c131221b19e66f0e3.zip
opensim-SC-54d7be8a49cbbd47217df84c131221b19e66f0e3.tar.gz
opensim-SC-54d7be8a49cbbd47217df84c131221b19e66f0e3.tar.bz2
opensim-SC-54d7be8a49cbbd47217df84c131221b19e66f0e3.tar.xz
* Adding CrytoGridAssetClient support - allows encrypting assets that are stored on a potentially hostile grid. This is not DRM, not should be relied on until after it's been security audited. I'll write a blog post on this explaining how/why/when you should use this, and what it does.
Diffstat (limited to 'OpenSim/Framework/Communications/Cache/CryptoGridAssetClient.cs')
-rw-r--r--OpenSim/Framework/Communications/Cache/CryptoGridAssetClient.cs527
1 files changed, 527 insertions, 0 deletions
diff --git a/OpenSim/Framework/Communications/Cache/CryptoGridAssetClient.cs b/OpenSim/Framework/Communications/Cache/CryptoGridAssetClient.cs
new file mode 100644
index 0000000..a8e6efb
--- /dev/null
+++ b/OpenSim/Framework/Communications/Cache/CryptoGridAssetClient.cs
@@ -0,0 +1,527 @@
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
33using System;
34using System.Collections.Generic;
35using System.IO;
36using System.Reflection;
37using System.Text;
38using System.Xml.Serialization;
39using log4net;
40using OpenSim.Framework.Servers;
41using System.Security.Cryptography;
42
43namespace OpenSim.Framework.Communications.Cache
44{
45 public class CryptoGridAssetClient : AssetServerBase
46 {
47 #region Keyfile Classes
48 [Serializable]
49 private class RjinKeyfile
50 {
51 public string Secret;
52 public string AlsoKnownAs;
53 public int Keysize;
54 public string IVBytes;
55 public string Description = "OpenSim Key";
56
57 private static string SHA1Hash(byte[] bytes)
58 {
59 SHA1 sha1 = SHA1CryptoServiceProvider.Create();
60 byte[] dataMd5 = sha1.ComputeHash(bytes);
61 StringBuilder sb = new StringBuilder();
62 for (int i = 0; i < dataMd5.Length; i++)
63 sb.AppendFormat("{0:x2}", dataMd5[i]);
64 return sb.ToString();
65 }
66
67 public void GenerateRandom()
68 {
69 RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();
70
71 byte[] genSec = new byte[32];
72 byte[] genAKA = new byte[32];
73 byte[] genIV = new byte[32];
74
75 Gen.GetBytes(genSec);
76 Gen.GetBytes(genAKA);
77 Gen.GetBytes(genIV);
78
79 Secret = SHA1Hash(genSec);
80 AlsoKnownAs = SHA1Hash(genAKA);
81 IVBytes = SHA1Hash(genIV).Substring(0, 16);
82 Keysize = 256;
83 }
84 }
85 #endregion
86
87 #region Rjindael
88 /// <summary>
89 /// This class uses a symmetric key algorithm (Rijndael/AES) to encrypt and
90 /// decrypt data. As long as encryption and decryption routines use the same
91 /// parameters to generate the keys, the keys are guaranteed to be the same.
92 /// The class uses static functions with duplicate code to make it easier to
93 /// demonstrate encryption and decryption logic. In a real-life application,
94 /// this may not be the most efficient way of handling encryption, so - as
95 /// soon as you feel comfortable with it - you may want to redesign this class.
96 /// </summary>
97 private class UtilRijndael
98 {
99 /// <summary>
100 /// Encrypts specified plaintext using Rijndael symmetric key algorithm
101 /// and returns a base64-encoded result.
102 /// </summary>
103 /// <param name="plainText">
104 /// Plaintext value to be encrypted.
105 /// </param>
106 /// <param name="passPhrase">
107 /// Passphrase from which a pseudo-random password will be derived. The
108 /// derived password will be used to generate the encryption key.
109 /// Passphrase can be any string. In this example we assume that this
110 /// passphrase is an ASCII string.
111 /// </param>
112 /// <param name="saltValue">
113 /// Salt value used along with passphrase to generate password. Salt can
114 /// be any string. In this example we assume that salt is an ASCII string.
115 /// </param>
116 /// <param name="hashAlgorithm">
117 /// Hash algorithm used to generate password. Allowed values are: "MD5" and
118 /// "SHA1". SHA1 hashes are a bit slower, but more secure than MD5 hashes.
119 /// </param>
120 /// <param name="passwordIterations">
121 /// Number of iterations used to generate password. One or two iterations
122 /// should be enough.
123 /// </param>
124 /// <param name="initVector">
125 /// Initialization vector (or IV). This value is required to encrypt the
126 /// first block of plaintext data. For RijndaelManaged class IV must be
127 /// exactly 16 ASCII characters long.
128 /// </param>
129 /// <param name="keySize">
130 /// Size of encryption key in bits. Allowed values are: 128, 192, and 256.
131 /// Longer keys are more secure than shorter keys.
132 /// </param>
133 /// <returns>
134 /// Encrypted value formatted as a base64-encoded string.
135 /// </returns>
136 public static byte[] Encrypt(byte[] plainText,
137 string passPhrase,
138 string saltValue,
139 string hashAlgorithm,
140 int passwordIterations,
141 string initVector,
142 int keySize)
143 {
144 // Convert strings into byte arrays.
145 // Let us assume that strings only contain ASCII codes.
146 // If strings include Unicode characters, use Unicode, UTF7, or UTF8
147 // encoding.
148 byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
149 byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
150
151 // Convert our plaintext into a byte array.
152 // Let us assume that plaintext contains UTF8-encoded characters.
153 byte[] plainTextBytes = plainText;
154
155 // First, we must create a password, from which the key will be derived.
156 // This password will be generated from the specified passphrase and
157 // salt value. The password will be created using the specified hash
158 // algorithm. Password creation can be done in several iterations.
159 PasswordDeriveBytes password = new PasswordDeriveBytes(
160 passPhrase,
161 saltValueBytes,
162 hashAlgorithm,
163 passwordIterations);
164
165 // Use the password to generate pseudo-random bytes for the encryption
166 // key. Specify the size of the key in bytes (instead of bits).
167 byte[] keyBytes = password.GetBytes(keySize / 8);
168
169 // Create uninitialized Rijndael encryption object.
170 RijndaelManaged symmetricKey = new RijndaelManaged();
171
172 // It is reasonable to set encryption mode to Cipher Block Chaining
173 // (CBC). Use default options for other symmetric key parameters.
174 symmetricKey.Mode = CipherMode.CBC;
175
176 // Generate encryptor from the existing key bytes and initialization
177 // vector. Key size will be defined based on the number of the key
178 // bytes.
179 ICryptoTransform encryptor = symmetricKey.CreateEncryptor(
180 keyBytes,
181 initVectorBytes);
182
183 // Define memory stream which will be used to hold encrypted data.
184 MemoryStream memoryStream = new MemoryStream();
185
186 // Define cryptographic stream (always use Write mode for encryption).
187 CryptoStream cryptoStream = new CryptoStream(memoryStream,
188 encryptor,
189 CryptoStreamMode.Write);
190 // Start encrypting.
191 cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
192
193 // Finish encrypting.
194 cryptoStream.FlushFinalBlock();
195
196 // Convert our encrypted data from a memory stream into a byte array.
197 byte[] cipherTextBytes = memoryStream.ToArray();
198
199 // Close both streams.
200 memoryStream.Close();
201 cryptoStream.Close();
202
203 // Return encrypted string.
204 return cipherTextBytes;
205 }
206
207 /// <summary>
208 /// Decrypts specified ciphertext using Rijndael symmetric key algorithm.
209 /// </summary>
210 /// <param name="cipherText">
211 /// Base64-formatted ciphertext value.
212 /// </param>
213 /// <param name="passPhrase">
214 /// Passphrase from which a pseudo-random password will be derived. The
215 /// derived password will be used to generate the encryption key.
216 /// Passphrase can be any string. In this example we assume that this
217 /// passphrase is an ASCII string.
218 /// </param>
219 /// <param name="saltValue">
220 /// Salt value used along with passphrase to generate password. Salt can
221 /// be any string. In this example we assume that salt is an ASCII string.
222 /// </param>
223 /// <param name="hashAlgorithm">
224 /// Hash algorithm used to generate password. Allowed values are: "MD5" and
225 /// "SHA1". SHA1 hashes are a bit slower, but more secure than MD5 hashes.
226 /// </param>
227 /// <param name="passwordIterations">
228 /// Number of iterations used to generate password. One or two iterations
229 /// should be enough.
230 /// </param>
231 /// <param name="initVector">
232 /// Initialization vector (or IV). This value is required to encrypt the
233 /// first block of plaintext data. For RijndaelManaged class IV must be
234 /// exactly 16 ASCII characters long.
235 /// </param>
236 /// <param name="keySize">
237 /// Size of encryption key in bits. Allowed values are: 128, 192, and 256.
238 /// Longer keys are more secure than shorter keys.
239 /// </param>
240 /// <returns>
241 /// Decrypted string value.
242 /// </returns>
243 /// <remarks>
244 /// Most of the logic in this function is similar to the Encrypt
245 /// logic. In order for decryption to work, all parameters of this function
246 /// - except cipherText value - must match the corresponding parameters of
247 /// the Encrypt function which was called to generate the
248 /// ciphertext.
249 /// </remarks>
250 public static byte[] Decrypt(byte[] cipherText,
251 string passPhrase,
252 string saltValue,
253 string hashAlgorithm,
254 int passwordIterations,
255 string initVector,
256 int keySize)
257 {
258 // Convert strings defining encryption key characteristics into byte
259 // arrays. Let us assume that strings only contain ASCII codes.
260 // If strings include Unicode characters, use Unicode, UTF7, or UTF8
261 // encoding.
262 byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
263 byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
264
265 // Convert our ciphertext into a byte array.
266 byte[] cipherTextBytes = cipherText;
267
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(
274 passPhrase,
275 saltValueBytes,
276 hashAlgorithm,
277 passwordIterations);
278
279 // Use the password to generate pseudo-random bytes for the encryption
280 // key. Specify the size of the key in bytes (instead of bits).
281 byte[] keyBytes = password.GetBytes(keySize / 8);
282
283 // Create uninitialized Rijndael encryption object.
284 RijndaelManaged symmetricKey = new RijndaelManaged();
285
286 // It is reasonable to set encryption mode to Cipher Block Chaining
287 // (CBC). Use default options for other symmetric key parameters.
288 symmetricKey.Mode = CipherMode.CBC;
289
290 // Generate decryptor from the existing key bytes and initialization
291 // vector. Key size will be defined based on the number of the key
292 // bytes.
293 ICryptoTransform decryptor = symmetricKey.CreateDecryptor(
294 keyBytes,
295 initVectorBytes);
296
297 // Define memory stream which will be used to hold encrypted data.
298 MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
299
300 // Define cryptographic stream (always use Read mode for encryption).
301 CryptoStream cryptoStream = new CryptoStream(memoryStream,
302 decryptor,
303 CryptoStreamMode.Read);
304
305 // Since at this point we don't know what the size of decrypted data
306 // will be, allocate the buffer long enough to hold ciphertext;
307 // plaintext is never longer than ciphertext.
308 byte[] plainTextBytes = new byte[cipherTextBytes.Length];
309
310 // Start decrypting.
311 int decryptedByteCount = cryptoStream.Read(plainTextBytes,
312 0,
313 plainTextBytes.Length);
314
315 // Close both streams.
316 memoryStream.Close();
317 cryptoStream.Close();
318
319 byte[] plainText = new byte[decryptedByteCount];
320 int i;
321 for (i = 0; i < decryptedByteCount; i++)
322 plainText[i] = plainTextBytes[i];
323
324 // Return decrypted string.
325 return plainText;
326 }
327 }
328 #endregion
329
330 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
331
332 private readonly string _assetServerUrl;
333 private readonly bool m_encryptOnUpload;
334 private readonly RjinKeyfile m_encryptKey;
335 private readonly Dictionary<string,RjinKeyfile> m_keyfiles = new Dictionary<string, RjinKeyfile>();
336
337 public CryptoGridAssetClient(string serverUrl, string keydir, bool decOnly)
338 {
339 _assetServerUrl = serverUrl;
340
341 string[] keys = Directory.GetFiles(keydir, "*.deckey");
342 foreach (string key in keys)
343 {
344 XmlSerializer xs = new XmlSerializer(typeof (RjinKeyfile));
345 FileStream file = new FileStream(key, FileMode.Open, FileAccess.Read);
346
347 RjinKeyfile rjkey = (RjinKeyfile) xs.Deserialize(file);
348
349 file.Close();
350
351 m_keyfiles.Add(rjkey.AlsoKnownAs, rjkey);
352 }
353
354
355 keys = Directory.GetFiles(keydir, "*.enckey");
356 if (keys.Length == 1)
357 {
358 string Ekey = keys[0];
359 XmlSerializer Exs = new XmlSerializer(typeof (RjinKeyfile));
360 FileStream Efile = new FileStream(Ekey, FileMode.Open, FileAccess.Read);
361
362 RjinKeyfile Erjkey = (RjinKeyfile) Exs.Deserialize(Efile);
363
364 Efile.Close();
365
366 m_keyfiles.Add(Erjkey.AlsoKnownAs, Erjkey);
367
368 m_encryptKey = Erjkey;
369 } else
370 {
371 if (keys.Length > 1)
372 throw new Exception(
373 "You have more than one asset *encryption* key. (You should never have more than one)," +
374 "If you downloaded this key from someone, rename it to <filename>.deckey to convert it to" +
375 "a decryption-only key.");
376
377 m_log.Warn("No encryption key found, generating a new one for you...");
378 RjinKeyfile encKey = new RjinKeyfile();
379 encKey.GenerateRandom();
380
381 m_encryptKey = encKey;
382
383 FileStream encExportFile = new FileStream("mysecretkey_rename_me.enckey",FileMode.CreateNew);
384 XmlSerializer xs = new XmlSerializer(typeof(RjinKeyfile));
385 xs.Serialize(encExportFile, encKey);
386 encExportFile.Flush();
387 encExportFile.Close();
388
389 m_log.Info(
390 "Encryption file generated, please rename 'mysecretkey_rename_me.enckey' to something more appropriate (however preserve the file extension).");
391 }
392
393 // If Decrypt-Only, dont encrypt on upload
394 m_encryptOnUpload = !decOnly;
395 }
396
397 private static void EncryptAssetBase(AssetBase x, RjinKeyfile file)
398 {
399 // Make a salt
400 RNGCryptoServiceProvider RandomGen = new RNGCryptoServiceProvider();
401 byte[] rand = new byte[32];
402 RandomGen.GetBytes(rand);
403
404 string salt = Convert.ToBase64String(rand);
405
406 x.Data = UtilRijndael.Encrypt(x.Data, file.Secret, salt, "SHA1", 2, file.IVBytes, file.Keysize);
407 x.Description = String.Format("ENCASS#:~:#{0}#:~:#{1}#:~:#{2}#:~:#{3}",
408 "OPENSIM_AES_AF1",
409 file.AlsoKnownAs,
410 salt,
411 x.Description);
412 }
413
414 private bool DecryptAssetBase(AssetBase x)
415 {
416 // Check it's encrypted first.
417 if (!x.Description.Contains("ENCASS"))
418 return true;
419
420 // ENCASS:ALG:AKA:SALT:Description
421 // 0 1 2 3 4
422 string[] splitchars = new string[1];
423 splitchars[0] = "#:~:#";
424
425 string[] meta = x.Description.Split(splitchars, StringSplitOptions.None);
426 if (meta.Length < 5)
427 {
428 m_log.Warn("[ENCASSETS] Recieved Encrypted Asset, but header is corrupt");
429 return false;
430 }
431
432 // Check if we have a matching key
433 if (m_keyfiles.ContainsKey(meta[2]))
434 {
435 RjinKeyfile deckey = m_keyfiles[meta[2]];
436 x.Description = meta[4];
437 switch (meta[1])
438 {
439 case "OPENSIM_AES_AF1":
440 x.Data = UtilRijndael.Decrypt(x.Data,
441 deckey.Secret,
442 meta[3],
443 "SHA1",
444 2,
445 deckey.IVBytes,
446 deckey.Keysize);
447 // Decrypted Successfully
448 return true;
449 default:
450 m_log.Warn(
451 "[ENCASSETS] Recieved Encrypted Asset, but we dont know how to decrypt '" + meta[1] + "'.");
452 // We dont understand this encryption scheme
453 return false;
454 }
455 }
456
457 m_log.Warn("[ENCASSETS] Recieved Encrypted Asset, but we do not have the decryption key.");
458 return false;
459 }
460
461 #region IAssetServer Members
462
463 protected override AssetBase GetAsset(AssetRequest req)
464 {
465#if DEBUG
466 //m_log.DebugFormat("[GRID ASSET CLIENT]: Querying for {0}", req.AssetID.ToString());
467#endif
468
469 RestClient rc = new RestClient(_assetServerUrl);
470 rc.AddResourcePath("assets");
471 rc.AddResourcePath(req.AssetID.ToString());
472 if (req.IsTexture)
473 rc.AddQueryParameter("texture");
474
475 rc.RequestMethod = "GET";
476
477 Stream s = rc.Request();
478
479 if (s == null)
480 return null;
481
482 if (s.Length > 0)
483 {
484 XmlSerializer xs = new XmlSerializer(typeof(AssetBase));
485
486 AssetBase encAsset = (AssetBase)xs.Deserialize(s);
487
488 // Try decrypt it
489 if (DecryptAssetBase(encAsset))
490 return encAsset;
491 }
492
493 return null;
494 }
495
496 public override void UpdateAsset(AssetBase asset)
497 {
498 throw new Exception("The method or operation is not implemented.");
499 }
500
501 public override void StoreAsset(AssetBase asset)
502 {
503 if (m_encryptOnUpload)
504 EncryptAssetBase(asset, m_encryptKey);
505
506 try
507 {
508 string assetUrl = _assetServerUrl + "/assets/";
509
510 m_log.InfoFormat("[CRYPTO GRID ASSET CLIENT]: Sending store request for asset {0}", asset.FullID);
511
512 RestObjectPoster.BeginPostObject<AssetBase>(assetUrl, asset);
513 }
514 catch (Exception e)
515 {
516 m_log.ErrorFormat("[CRYPTO GRID ASSET CLIENT]: {0}", e);
517 }
518 }
519
520 public override void Close()
521 {
522 throw new Exception("The method or operation is not implemented.");
523 }
524
525 #endregion
526 }
527}