2010-04-07 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System.ServiceModel / Mono.Security.Protocol.Tls / CipherSuite.cs
blob66f3a6536cdfda3221b8eedbc0c670b87e1b5794
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Copyright (C) 2006 Novell, Inc (http://www.novell.com)
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 using System;
26 using System.IO;
27 using System.Text;
28 using System.Security.Cryptography;
30 using Mono.Security;
31 using Mono.Security.Cryptography;
32 using M = Mono.Security.Cryptography;
34 namespace Mono.Security.Protocol.Tls
36 internal abstract class CipherSuite
38 #region Static Fields
40 public static byte[] EmptyArray = new byte[0];
42 #endregion
44 #region Fields
46 private short code;
47 private string name;
48 private CipherAlgorithmType cipherAlgorithmType;
49 private HashAlgorithmType hashAlgorithmType;
50 private ExchangeAlgorithmType exchangeAlgorithmType;
51 private bool isExportable;
52 private CipherMode cipherMode;
53 private byte keyMaterialSize;
54 private int keyBlockSize;
55 private byte expandedKeyMaterialSize;
56 private short effectiveKeyBits;
57 private byte ivSize;
58 private byte blockSize;
59 private Context context;
60 private SymmetricAlgorithm encryptionAlgorithm;
61 private ICryptoTransform encryptionCipher;
62 private SymmetricAlgorithm decryptionAlgorithm;
63 private ICryptoTransform decryptionCipher;
64 private KeyedHashAlgorithm clientHMAC;
65 private KeyedHashAlgorithm serverHMAC;
67 #endregion
69 #region Protected Properties
71 protected ICryptoTransform EncryptionCipher
73 get { return this.encryptionCipher; }
76 protected ICryptoTransform DecryptionCipher
78 get { return this.decryptionCipher; }
81 protected KeyedHashAlgorithm ClientHMAC
83 get { return this.clientHMAC; }
86 protected KeyedHashAlgorithm ServerHMAC
88 get { return this.serverHMAC; }
91 #endregion
93 #region Properties
95 public CipherAlgorithmType CipherAlgorithmType
97 get { return this.cipherAlgorithmType; }
100 public string HashAlgorithmName
102 get
104 switch (this.hashAlgorithmType)
106 case HashAlgorithmType.Md5:
107 return "MD5";
109 case HashAlgorithmType.Sha1:
110 return "SHA1";
112 default:
113 return "None";
118 public HashAlgorithmType HashAlgorithmType
120 get { return this.hashAlgorithmType; }
123 public int HashSize
125 get
127 switch (this.hashAlgorithmType)
129 case HashAlgorithmType.Md5:
130 return 16;
132 case HashAlgorithmType.Sha1:
133 return 20;
135 default:
136 return 0;
141 public ExchangeAlgorithmType ExchangeAlgorithmType
143 get { return this.exchangeAlgorithmType; }
146 public CipherMode CipherMode
148 get { return this.cipherMode; }
151 public short Code
153 get { return this.code; }
156 public string Name
158 get { return this.name; }
161 public bool IsExportable
163 get { return this.isExportable; }
166 public byte KeyMaterialSize
168 get { return this.keyMaterialSize; }
171 public int KeyBlockSize
173 get { return this.keyBlockSize; }
176 public byte ExpandedKeyMaterialSize
178 get { return this.expandedKeyMaterialSize; }
181 public short EffectiveKeyBits
183 get { return this.effectiveKeyBits; }
186 public byte IvSize
188 get { return this.ivSize; }
192 public byte BlockSize
194 get { return this.blockSize; }
198 public Context Context
200 get { return this.context; }
201 set
203 this.context = value;
207 #endregion
209 #region Constructors
211 public CipherSuite(
212 short code, string name, CipherAlgorithmType cipherAlgorithmType,
213 HashAlgorithmType hashAlgorithmType, ExchangeAlgorithmType exchangeAlgorithmType,
214 bool exportable, bool blockMode, byte keyMaterialSize,
215 byte expandedKeyMaterialSize, short effectiveKeyBits,
216 byte ivSize, byte blockSize)
218 this.code = code;
219 this.name = name;
220 this.cipherAlgorithmType = cipherAlgorithmType;
221 this.hashAlgorithmType = hashAlgorithmType;
222 this.exchangeAlgorithmType = exchangeAlgorithmType;
223 this.isExportable = exportable;
224 if (blockMode)
226 this.cipherMode = CipherMode.CBC;
228 this.keyMaterialSize = keyMaterialSize;
229 this.expandedKeyMaterialSize= expandedKeyMaterialSize;
230 this.effectiveKeyBits = effectiveKeyBits;
231 this.ivSize = ivSize;
232 this.blockSize = blockSize;
233 this.keyBlockSize = (this.keyMaterialSize + this.HashSize + this.ivSize) << 1;
236 #endregion
238 #region Methods
240 internal void Write (byte[] array, int offset, short value)
242 if (offset > array.Length - 2)
243 throw new ArgumentException ("offset");
245 array [offset ] = (byte) (value >> 8);
246 array [offset + 1] = (byte) value;
249 internal void Write (byte[] array, int offset, ulong value)
251 if (offset > array.Length - 8)
252 throw new ArgumentException ("offset");
254 array [offset ] = (byte) (value >> 56);
255 array [offset + 1] = (byte) (value >> 48);
256 array [offset + 2] = (byte) (value >> 40);
257 array [offset + 3] = (byte) (value >> 32);
258 array [offset + 4] = (byte) (value >> 24);
259 array [offset + 5] = (byte) (value >> 16);
260 array [offset + 6] = (byte) (value >> 8);
261 array [offset + 7] = (byte) value;
264 public void InitializeCipher()
266 this.createEncryptionCipher();
267 this.createDecryptionCipher();
270 public byte[] EncryptRecord(byte[] fragment, byte[] mac)
272 // Encryption ( fragment + mac [+ padding + padding_length] )
273 int length = fragment.Length + mac.Length;
274 int padlen = 0;
275 if (this.CipherMode == CipherMode.CBC) {
276 // Calculate padding_length
277 length++; // keep an extra byte
278 padlen = (this.blockSize - length % this.blockSize);
279 if (padlen == this.blockSize) {
280 padlen = 0;
282 length += padlen;
285 byte[] plain = new byte [length];
286 Buffer.BlockCopy (fragment, 0, plain, 0, fragment.Length);
287 Buffer.BlockCopy (mac, 0, plain, fragment.Length, mac.Length);
288 if (padlen > 0) {
289 int start = fragment.Length + mac.Length;
290 for (int i = start; i < (start + padlen + 1); i++) {
291 plain[i] = (byte)padlen;
295 this.EncryptionCipher.TransformBlock (plain, 0, plain.Length, plain, 0);
296 return plain;
299 public void DecryptRecord(byte[] fragment, out byte[] dcrFragment, out byte[] dcrMAC)
301 int fragmentSize = 0;
302 int paddingLength = 0;
304 // Decrypt message fragment ( fragment + mac [+ padding + padding_length] )
305 this.DecryptionCipher.TransformBlock(fragment, 0, fragment.Length, fragment, 0);
306 // optimization: decrypt "in place", worst case: padding will reduce the size of the data
307 // this will cut in half the memory allocations (dcrFragment and dcrMAC remains)
309 // Calculate fragment size
310 if (this.CipherMode == CipherMode.CBC)
312 // Calculate padding_length
313 paddingLength = fragment[fragment.Length - 1];
314 fragmentSize = (fragment.Length - (paddingLength + 1)) - this.HashSize;
316 else
318 fragmentSize = fragment.Length - this.HashSize;
321 dcrFragment = new byte[fragmentSize];
322 dcrMAC = new byte[HashSize];
324 Buffer.BlockCopy(fragment, 0, dcrFragment, 0, dcrFragment.Length);
325 Buffer.BlockCopy(fragment, dcrFragment.Length, dcrMAC, 0, dcrMAC.Length);
328 #endregion
330 #region Abstract Methods
332 public abstract byte[] ComputeClientRecordMAC(ContentType contentType, byte[] fragment);
334 public abstract byte[] ComputeServerRecordMAC(ContentType contentType, byte[] fragment);
336 public abstract void ComputeMasterSecret(byte[] preMasterSecret);
338 public abstract void ComputeKeys();
340 #endregion
342 #region Key Generation Methods
344 public byte[] CreatePremasterSecret()
346 ClientContext context = (ClientContext)this.context;
348 // Generate random bytes (total size)
349 byte[] preMasterSecret = this.context.GetSecureRandomBytes (48);
350 // and replace the first two bytes with the protocol version
351 // (maximum support version not actual)
352 preMasterSecret [0] = (byte)(context.ClientHelloProtocol >> 8);
353 preMasterSecret [1] = (byte)context.ClientHelloProtocol;
355 return preMasterSecret;
358 public byte[] PRF(byte[] secret, string label, byte[] data, int length)
360 /* Secret Length calc exmplain from the RFC2246. Section 5
362 * S1 and S2 are the two halves of the secret and each is the same
363 * length. S1 is taken from the first half of the secret, S2 from the
364 * second half. Their length is created by rounding up the length of the
365 * overall secret divided by two; thus, if the original secret is an odd
366 * number of bytes long, the last byte of S1 will be the same as the
367 * first byte of S2.
370 // split secret in 2
371 int secretLen = secret.Length >> 1;
372 // rounding up
373 if ((secret.Length & 0x1) == 0x1)
374 secretLen++;
376 // Seed
377 TlsStream seedStream = new TlsStream();
378 seedStream.Write(Encoding.ASCII.GetBytes(label));
379 seedStream.Write(data);
380 byte[] seed = seedStream.ToArray();
381 seedStream.Reset();
383 // Secret 1
384 byte[] secret1 = new byte[secretLen];
385 Buffer.BlockCopy(secret, 0, secret1, 0, secretLen);
387 // Secret2
388 byte[] secret2 = new byte[secretLen];
389 Buffer.BlockCopy(secret, (secret.Length - secretLen), secret2, 0, secretLen);
391 // Secret 1 processing
392 byte[] p_md5 = Expand("MD5", secret1, seed, length);
394 // Secret 2 processing
395 byte[] p_sha = Expand("SHA1", secret2, seed, length);
397 // Perfor XOR of both results
398 byte[] masterSecret = new byte[length];
399 for (int i = 0; i < masterSecret.Length; i++)
401 masterSecret[i] = (byte)(p_md5[i] ^ p_sha[i]);
404 return masterSecret;
407 public byte[] Expand(string hashName, byte[] secret, byte[] seed, int length)
409 int hashLength = hashName == "MD5" ? 16 : 20;
410 int iterations = (int)(length / hashLength);
411 if ((length % hashLength) > 0)
413 iterations++;
416 M.HMAC hmac = new M.HMAC(hashName, secret);
417 TlsStream resMacs = new TlsStream();
419 byte[][] hmacs = new byte[iterations + 1][];
420 hmacs[0] = seed;
421 for (int i = 1; i <= iterations; i++)
423 TlsStream hcseed = new TlsStream();
424 hmac.TransformFinalBlock(hmacs[i-1], 0, hmacs[i-1].Length);
425 hmacs[i] = hmac.Hash;
426 hcseed.Write(hmacs[i]);
427 hcseed.Write(seed);
428 hmac.TransformFinalBlock(hcseed.ToArray(), 0, (int)hcseed.Length);
429 resMacs.Write(hmac.Hash);
430 hcseed.Reset();
433 byte[] res = new byte[length];
435 Buffer.BlockCopy(resMacs.ToArray(), 0, res, 0, res.Length);
437 resMacs.Reset();
439 return res;
442 #endregion
444 #region Private Methods
446 private void createEncryptionCipher()
448 // Create and configure the symmetric algorithm
449 switch (this.cipherAlgorithmType)
451 case CipherAlgorithmType.Des:
452 this.encryptionAlgorithm = DES.Create();
453 break;
455 case CipherAlgorithmType.Rc2:
456 this.encryptionAlgorithm = RC2.Create();
457 break;
459 case CipherAlgorithmType.Rc4:
460 this.encryptionAlgorithm = new ARC4Managed();
461 break;
463 case CipherAlgorithmType.TripleDes:
464 this.encryptionAlgorithm = TripleDES.Create();
465 break;
467 case CipherAlgorithmType.Rijndael:
468 this.encryptionAlgorithm = Rijndael.Create();
469 break;
472 // If it's a block cipher
473 if (this.cipherMode == CipherMode.CBC)
475 // Configure encrypt algorithm
476 this.encryptionAlgorithm.Mode = this.cipherMode;
477 this.encryptionAlgorithm.Padding = PaddingMode.None;
478 this.encryptionAlgorithm.KeySize = this.expandedKeyMaterialSize * 8;
479 this.encryptionAlgorithm.BlockSize = this.blockSize * 8;
482 // Set the key and IV for the algorithm
483 if (this.context is ClientContext)
485 this.encryptionAlgorithm.Key = this.context.ClientWriteKey;
486 this.encryptionAlgorithm.IV = this.context.ClientWriteIV;
488 else
490 this.encryptionAlgorithm.Key = this.context.ServerWriteKey;
491 this.encryptionAlgorithm.IV = this.context.ServerWriteIV;
494 // Create encryption cipher
495 this.encryptionCipher = this.encryptionAlgorithm.CreateEncryptor();
497 // Create the HMAC algorithm
498 if (this.context is ClientContext)
500 this.clientHMAC = new M.HMAC(
501 this.HashAlgorithmName,
502 this.context.Negotiating.ClientWriteMAC);
504 else
506 this.serverHMAC = new M.HMAC(
507 this.HashAlgorithmName,
508 this.context.Negotiating.ServerWriteMAC);
512 private void createDecryptionCipher()
514 // Create and configure the symmetric algorithm
515 switch (this.cipherAlgorithmType)
517 case CipherAlgorithmType.Des:
518 this.decryptionAlgorithm = DES.Create();
519 break;
521 case CipherAlgorithmType.Rc2:
522 this.decryptionAlgorithm = RC2.Create();
523 break;
525 case CipherAlgorithmType.Rc4:
526 this.decryptionAlgorithm = new ARC4Managed();
527 break;
529 case CipherAlgorithmType.TripleDes:
530 this.decryptionAlgorithm = TripleDES.Create();
531 break;
533 case CipherAlgorithmType.Rijndael:
534 this.decryptionAlgorithm = Rijndael.Create();
535 break;
538 // If it's a block cipher
539 if (this.cipherMode == CipherMode.CBC)
541 // Configure encrypt algorithm
542 this.decryptionAlgorithm.Mode = this.cipherMode;
543 this.decryptionAlgorithm.Padding = PaddingMode.None;
544 this.decryptionAlgorithm.KeySize = this.expandedKeyMaterialSize * 8;
545 this.decryptionAlgorithm.BlockSize = this.blockSize * 8;
548 // Set the key and IV for the algorithm
549 if (this.context is ClientContext)
551 this.decryptionAlgorithm.Key = this.context.ServerWriteKey;
552 this.decryptionAlgorithm.IV = this.context.ServerWriteIV;
554 else
556 this.decryptionAlgorithm.Key = this.context.ClientWriteKey;
557 this.decryptionAlgorithm.IV = this.context.ClientWriteIV;
560 // Create decryption cipher
561 this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
563 // Create the HMAC
564 if (this.context is ClientContext)
566 this.serverHMAC = new M.HMAC(
567 this.HashAlgorithmName,
568 this.context.Negotiating.ServerWriteMAC);
570 else
572 this.clientHMAC = new M.HMAC(
573 this.HashAlgorithmName,
574 this.context.Negotiating.ClientWriteMAC);
578 #endregion