**** Merged from MCS ****
[mono-project.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / CipherSuite.cs
blob2f62128e00fedb9b6780a7c81057de362829ae1b
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
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 Mono.Security.X509;
33 using M = Mono.Security.Cryptography;
35 namespace Mono.Security.Protocol.Tls
37 internal abstract class CipherSuite
39 #region Static Fields
41 public static byte[] EmptyArray = new byte[0];
43 #endregion
45 #region Fields
47 private short code;
48 private string name;
49 private CipherAlgorithmType cipherAlgorithmType;
50 private HashAlgorithmType hashAlgorithmType;
51 private ExchangeAlgorithmType exchangeAlgorithmType;
52 private bool isExportable;
53 private CipherMode cipherMode;
54 private byte keyMaterialSize;
55 private int keyBlockSize;
56 private byte expandedKeyMaterialSize;
57 private short effectiveKeyBits;
58 private byte ivSize;
59 private byte blockSize;
60 private Context context;
61 private SymmetricAlgorithm encryptionAlgorithm;
62 private ICryptoTransform encryptionCipher;
63 private SymmetricAlgorithm decryptionAlgorithm;
64 private ICryptoTransform decryptionCipher;
65 private KeyedHashAlgorithm clientHMAC;
66 private KeyedHashAlgorithm serverHMAC;
68 #endregion
70 #region Protected Properties
72 protected ICryptoTransform EncryptionCipher
74 get { return this.encryptionCipher; }
77 protected ICryptoTransform DecryptionCipher
79 get { return this.decryptionCipher; }
82 protected KeyedHashAlgorithm ClientHMAC
84 get { return this.clientHMAC; }
87 protected KeyedHashAlgorithm ServerHMAC
89 get { return this.serverHMAC; }
92 #endregion
94 #region Properties
96 public CipherAlgorithmType CipherAlgorithmType
98 get { return this.cipherAlgorithmType; }
101 public string HashAlgorithmName
103 get
105 switch (this.hashAlgorithmType)
107 case HashAlgorithmType.Md5:
108 return "MD5";
110 case HashAlgorithmType.Sha1:
111 return "SHA1";
113 default:
114 return "None";
119 public HashAlgorithmType HashAlgorithmType
121 get { return this.hashAlgorithmType; }
124 public int HashSize
126 get
128 switch (this.hashAlgorithmType)
130 case HashAlgorithmType.Md5:
131 return 16;
133 case HashAlgorithmType.Sha1:
134 return 20;
136 default:
137 return 0;
142 public ExchangeAlgorithmType ExchangeAlgorithmType
144 get { return this.exchangeAlgorithmType; }
147 public CipherMode CipherMode
149 get { return this.cipherMode; }
152 public short Code
154 get { return this.code; }
157 public string Name
159 get { return this.name; }
162 public bool IsExportable
164 get { return this.isExportable; }
167 public byte KeyMaterialSize
169 get { return this.keyMaterialSize; }
172 public int KeyBlockSize
174 get { return this.keyBlockSize; }
177 public byte ExpandedKeyMaterialSize
179 get { return this.expandedKeyMaterialSize; }
182 public byte EffectiveKeyBits
184 get { return this.EffectiveKeyBits; }
187 public byte IvSize
189 get { return this.ivSize; }
193 public byte BlockSize
195 get { return this.blockSize; }
199 public Context Context
201 get { return this.context; }
202 set
204 this.context = value;
208 #endregion
210 #region Constructors
212 public CipherSuite(
213 short code, string name, CipherAlgorithmType cipherAlgorithmType,
214 HashAlgorithmType hashAlgorithmType, ExchangeAlgorithmType exchangeAlgorithmType,
215 bool exportable, bool blockMode, byte keyMaterialSize,
216 byte expandedKeyMaterialSize, short effectiveKeyBytes,
217 byte ivSize, byte blockSize)
219 this.code = code;
220 this.name = name;
221 this.cipherAlgorithmType = cipherAlgorithmType;
222 this.hashAlgorithmType = hashAlgorithmType;
223 this.exchangeAlgorithmType = exchangeAlgorithmType;
224 this.isExportable = exportable;
225 if (blockMode)
227 this.cipherMode = CipherMode.CBC;
229 this.keyMaterialSize = keyMaterialSize;
230 this.expandedKeyMaterialSize= expandedKeyMaterialSize;
231 this.effectiveKeyBits = effectiveKeyBits;
232 this.ivSize = ivSize;
233 this.blockSize = blockSize;
234 this.keyBlockSize = (this.keyMaterialSize + this.HashSize + this.ivSize) << 1;
237 #endregion
239 #region Methods
241 public void InitializeCipher()
243 this.createEncryptionCipher();
244 this.createDecryptionCipher();
247 public void UpdateClientCipherIV(byte[] iv)
249 if (this.cipherMode == CipherMode.CBC)
251 // Set the new IV
252 this.encryptionAlgorithm.IV = iv;
254 // Create encryption cipher with the new IV
255 this.encryptionCipher = this.encryptionAlgorithm.CreateEncryptor();
260 public void UpdateServerCipherIV(byte[] iv)
262 if (this.cipherMode == CipherMode.CBC)
264 // Set the new IV
265 this.decryptionAlgorithm.IV = iv;
267 // Create encryption cipher with the new IV
268 this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
273 public byte[] EncryptRecord(byte[] fragment, byte[] mac)
275 // Encryption ( fragment + mac [+ padding + padding_length] )
276 MemoryStream ms = new MemoryStream();
277 CryptoStream cs = new CryptoStream(ms, this.EncryptionCipher, CryptoStreamMode.Write);
279 cs.Write(fragment, 0, fragment.Length);
280 cs.Write(mac, 0, mac.Length);
281 if (this.CipherMode == CipherMode.CBC)
283 // Calculate padding_length
284 byte fragmentLength = (byte)(fragment.Length + mac.Length + 1);
285 byte paddingLength = (byte)(this.blockSize - fragmentLength % this.blockSize);
286 if (paddingLength == this.blockSize)
288 paddingLength = 0;
291 // Write padding length byte
292 byte[] padding = new byte[(paddingLength + 1)];
293 for (int i = 0; i < (paddingLength + 1); i++)
295 padding[i] = paddingLength;
298 cs.Write(padding, 0, padding.Length);
300 cs.FlushFinalBlock();
301 cs.Close();
303 return ms.ToArray();
306 public void DecryptRecord(byte[] fragment, ref byte[] dcrFragment, ref byte[] dcrMAC)
308 int fragmentSize = 0;
309 int paddingLength = 0;
311 // Decrypt message fragment ( fragment + mac [+ padding + padding_length] )
312 byte[] buffer = new byte[fragment.Length];
313 this.DecryptionCipher.TransformBlock(fragment, 0, fragment.Length, buffer, 0);
315 // Calculate fragment size
316 if (this.CipherMode == CipherMode.CBC)
318 // Calculate padding_length
319 paddingLength = buffer[buffer.Length - 1];
320 fragmentSize = (buffer.Length - (paddingLength + 1)) - this.HashSize;
322 else
324 fragmentSize = buffer.Length - this.HashSize;
327 dcrFragment = new byte[fragmentSize];
328 dcrMAC = new byte[HashSize];
330 Buffer.BlockCopy(buffer, 0, dcrFragment, 0, dcrFragment.Length);
331 Buffer.BlockCopy(buffer, dcrFragment.Length, dcrMAC, 0, dcrMAC.Length);
334 #endregion
336 #region Abstract Methods
338 public abstract byte[] ComputeClientRecordMAC(ContentType contentType, byte[] fragment);
340 public abstract byte[] ComputeServerRecordMAC(ContentType contentType, byte[] fragment);
342 public abstract void ComputeMasterSecret(byte[] preMasterSecret);
344 public abstract void ComputeKeys();
346 #endregion
348 #region Key Generation Methods
350 public byte[] CreatePremasterSecret()
352 TlsStream stream = new TlsStream();
353 ClientContext context = (ClientContext)this.context;
355 // Write protocol version
356 // We need to send here the protocol version used in
357 // the ClientHello message, that can be different than the actual
358 // protocol version
359 stream.Write(context.ClientHelloProtocol);
361 // Generate random bytes
362 stream.Write(this.context.GetSecureRandomBytes(46));
364 byte[] preMasterSecret = stream.ToArray();
366 stream.Reset();
368 return preMasterSecret;
371 public byte[] PRF(byte[] secret, string label, byte[] data, int length)
373 HashAlgorithm md5 = MD5.Create();
374 HashAlgorithm sha1 = SHA1.Create();
376 /* Secret Length calc exmplain from the RFC2246. Section 5
378 * S1 and S2 are the two halves of the secret and each is the same
379 * length. S1 is taken from the first half of the secret, S2 from the
380 * second half. Their length is created by rounding up the length of the
381 * overall secret divided by two; thus, if the original secret is an odd
382 * number of bytes long, the last byte of S1 will be the same as the
383 * first byte of S2.
386 // split secret in 2
387 int secretLen = secret.Length >> 1;
388 // rounding up
389 if ((secret.Length & 0x1) == 0x1)
390 secretLen++;
392 // Seed
393 TlsStream seedStream = new TlsStream();
394 seedStream.Write(Encoding.ASCII.GetBytes(label));
395 seedStream.Write(data);
396 byte[] seed = seedStream.ToArray();
397 seedStream.Reset();
399 // Secret 1
400 byte[] secret1 = new byte[secretLen];
401 Buffer.BlockCopy(secret, 0, secret1, 0, secretLen);
403 // Secret2
404 byte[] secret2 = new byte[secretLen];
405 Buffer.BlockCopy(secret, (secret.Length - secretLen), secret2, 0, secretLen);
407 // Secret 1 processing
408 byte[] p_md5 = Expand("MD5", secret1, seed, length);
410 // Secret 2 processing
411 byte[] p_sha = Expand("SHA1", secret2, seed, length);
413 // Perfor XOR of both results
414 byte[] masterSecret = new byte[length];
415 for (int i = 0; i < masterSecret.Length; i++)
417 masterSecret[i] = (byte)(p_md5[i] ^ p_sha[i]);
420 return masterSecret;
423 public byte[] Expand(string hashName, byte[] secret, byte[] seed, int length)
425 int hashLength = hashName == "MD5" ? 16 : 20;
426 int iterations = (int)(length / hashLength);
427 if ((length % hashLength) > 0)
429 iterations++;
432 M.HMAC hmac = new M.HMAC(hashName, secret);
433 TlsStream resMacs = new TlsStream();
435 byte[][] hmacs = new byte[iterations + 1][];
436 hmacs[0] = seed;
437 for (int i = 1; i <= iterations; i++)
439 TlsStream hcseed = new TlsStream();
440 hmac.TransformFinalBlock(hmacs[i-1], 0, hmacs[i-1].Length);
441 hmacs[i] = hmac.Hash;
442 hcseed.Write(hmacs[i]);
443 hcseed.Write(seed);
444 hmac.TransformFinalBlock(hcseed.ToArray(), 0, (int)hcseed.Length);
445 resMacs.Write(hmac.Hash);
446 hcseed.Reset();
449 byte[] res = new byte[length];
451 Buffer.BlockCopy(resMacs.ToArray(), 0, res, 0, res.Length);
453 resMacs.Reset();
455 return res;
458 #endregion
460 #region Private Methods
462 private void createEncryptionCipher()
464 // Create and configure the symmetric algorithm
465 switch (this.cipherAlgorithmType)
467 case CipherAlgorithmType.Des:
468 this.encryptionAlgorithm = DES.Create();
469 break;
471 case CipherAlgorithmType.Rc2:
472 this.encryptionAlgorithm = RC2.Create();
473 break;
475 case CipherAlgorithmType.Rc4:
476 this.encryptionAlgorithm = new ARC4Managed();
477 break;
479 case CipherAlgorithmType.TripleDes:
480 this.encryptionAlgorithm = TripleDES.Create();
481 break;
483 case CipherAlgorithmType.Rijndael:
484 this.encryptionAlgorithm = Rijndael.Create();
485 break;
488 // If it's a block cipher
489 if (this.cipherMode == CipherMode.CBC)
491 // Configure encrypt algorithm
492 this.encryptionAlgorithm.Mode = this.cipherMode;
493 this.encryptionAlgorithm.Padding = PaddingMode.None;
494 this.encryptionAlgorithm.KeySize = this.expandedKeyMaterialSize * 8;
495 this.encryptionAlgorithm.BlockSize = this.blockSize * 8;
498 // Set the key and IV for the algorithm
499 if (this.context is ClientContext)
501 this.encryptionAlgorithm.Key = this.context.ClientWriteKey;
502 this.encryptionAlgorithm.IV = this.context.ClientWriteIV;
504 else
506 this.encryptionAlgorithm.Key = this.context.ServerWriteKey;
507 this.encryptionAlgorithm.IV = this.context.ServerWriteIV;
510 // Create encryption cipher
511 this.encryptionCipher = this.encryptionAlgorithm.CreateEncryptor();
513 // Create the HMAC algorithm
514 if (this.context is ClientContext)
516 this.clientHMAC = new M.HMAC(
517 this.HashAlgorithmName,
518 this.context.ClientWriteMAC);
520 else
522 this.serverHMAC = new M.HMAC(
523 this.HashAlgorithmName,
524 this.context.ServerWriteMAC);
528 private void createDecryptionCipher()
530 // Create and configure the symmetric algorithm
531 switch (this.cipherAlgorithmType)
533 case CipherAlgorithmType.Des:
534 this.decryptionAlgorithm = DES.Create();
535 break;
537 case CipherAlgorithmType.Rc2:
538 this.decryptionAlgorithm = RC2.Create();
539 break;
541 case CipherAlgorithmType.Rc4:
542 this.decryptionAlgorithm = new ARC4Managed();
543 break;
545 case CipherAlgorithmType.TripleDes:
546 this.decryptionAlgorithm = TripleDES.Create();
547 break;
549 case CipherAlgorithmType.Rijndael:
550 this.decryptionAlgorithm = Rijndael.Create();
551 break;
554 // If it's a block cipher
555 if (this.cipherMode == CipherMode.CBC)
557 // Configure encrypt algorithm
558 this.decryptionAlgorithm.Mode = this.cipherMode;
559 this.decryptionAlgorithm.Padding = PaddingMode.None;
560 this.decryptionAlgorithm.KeySize = this.expandedKeyMaterialSize * 8;
561 this.decryptionAlgorithm.BlockSize = this.blockSize * 8;
564 // Set the key and IV for the algorithm
565 if (this.context is ClientContext)
567 this.decryptionAlgorithm.Key = this.context.ServerWriteKey;
568 this.decryptionAlgorithm.IV = this.context.ServerWriteIV;
570 else
572 this.decryptionAlgorithm.Key = this.context.ClientWriteKey;
573 this.decryptionAlgorithm.IV = this.context.ClientWriteIV;
576 // Create decryption cipher
577 this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
579 // Create the HMAC
580 if (this.context is ClientContext)
582 this.serverHMAC = new M.HMAC(
583 this.HashAlgorithmName,
584 this.context.ServerWriteMAC);
586 else
588 this.clientHMAC = new M.HMAC(
589 this.HashAlgorithmName,
590 this.context.ClientWriteMAC);
594 #endregion