Merge pull request #823 from DavidKarlas/master
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / CipherSuite.cs
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.
23 //
24
25 using System;
26 using System.IO;
27 using System.Text;
28 using System.Security.Cryptography;
29
30 using Mono.Security;
31 using Mono.Security.Cryptography;
32 using M = Mono.Security.Cryptography;
33
34 namespace Mono.Security.Protocol.Tls
35 {
36         internal abstract class CipherSuite
37         {
38                 #region Static Fields
39
40                 public static byte[] EmptyArray = new byte[0];
41
42                 #endregion
43
44                 #region Fields
45
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;
66                         
67                 #endregion
68
69                 #region Protected Properties
70
71                 protected ICryptoTransform EncryptionCipher
72                 {
73                         get { return this.encryptionCipher; }
74                 }
75
76                 protected ICryptoTransform DecryptionCipher
77                 {
78                         get { return this.decryptionCipher; }
79                 }
80
81                 protected KeyedHashAlgorithm ClientHMAC
82                 {
83                         get { return this.clientHMAC; }
84                 }
85                 
86                 protected KeyedHashAlgorithm ServerHMAC
87                 {
88                         get { return this.serverHMAC; }
89                 }
90
91                 #endregion
92
93                 #region Properties
94
95                 public CipherAlgorithmType CipherAlgorithmType
96                 {
97                         get { return this.cipherAlgorithmType; }
98                 }
99
100                 public string HashAlgorithmName
101                 {
102                         get 
103                         {  
104                                 switch (this.hashAlgorithmType)
105                                 {
106                                         case HashAlgorithmType.Md5:
107                                                 return "MD5";
108
109                                         case HashAlgorithmType.Sha1:
110                                                 return "SHA1";
111
112                                         default:
113                                                 return "None";
114                                 }
115                         }
116                 }
117
118                 internal HashAlgorithm CreateHashAlgorithm ()
119                 {
120                         switch (hashAlgorithmType) {
121                         case HashAlgorithmType.Md5:
122                                 return MD5.Create ();
123                         case HashAlgorithmType.Sha1:
124                                 return SHA1.Create ();
125                         default:
126                                 return null;
127                         }
128                 }
129
130                 public HashAlgorithmType HashAlgorithmType
131                 {
132                         get { return this.hashAlgorithmType; }
133                 }
134
135                 public int HashSize
136                 {
137                         get 
138                         { 
139                                 switch (this.hashAlgorithmType)
140                                 {
141                                         case HashAlgorithmType.Md5:
142                                                 return 16;
143
144                                         case HashAlgorithmType.Sha1:
145                                                 return 20;
146
147                                         default:
148                                                 return 0;
149                                 }
150                         }       
151                 }
152                 
153                 public ExchangeAlgorithmType ExchangeAlgorithmType
154                 {
155                         get { return this.exchangeAlgorithmType; }
156                 }
157
158                 public CipherMode CipherMode
159                 {
160                         get { return this.cipherMode; }
161                 }
162
163                 public short Code
164                 {
165                         get { return this.code; }
166                 }
167
168                 public string Name
169                 {
170                         get { return this.name; }
171                 }
172
173                 public bool IsExportable
174                 {
175                         get { return this.isExportable; }
176                 }
177
178                 public byte     KeyMaterialSize
179                 {
180                         get { return this.keyMaterialSize; }
181                 }
182
183                 public int KeyBlockSize
184                 {
185                         get { return this.keyBlockSize; }
186                 }
187
188                 public byte     ExpandedKeyMaterialSize
189                 {
190                         get { return this.expandedKeyMaterialSize; }
191                 }
192
193                 public short    EffectiveKeyBits
194                 {
195                         get { return this.effectiveKeyBits; }
196                 }
197                 
198                 public byte IvSize
199                 {
200                         get { return this.ivSize; }
201                 }
202
203                 /*
204                 public byte     BlockSize
205                 {
206                         get { return this.blockSize; }
207                 }
208                 */
209
210                 public Context Context
211                 {
212                         get { return this.context; }
213                         set 
214                         { 
215                                 this.context = value; 
216                         }
217                 }
218
219                 #endregion
220
221                 #region Constructors
222                 
223                 public CipherSuite(
224                         short code, string name, CipherAlgorithmType cipherAlgorithmType, 
225                         HashAlgorithmType hashAlgorithmType, ExchangeAlgorithmType exchangeAlgorithmType,
226                         bool exportable, bool blockMode, byte keyMaterialSize, 
227                         byte expandedKeyMaterialSize, short effectiveKeyBits, 
228                         byte ivSize, byte blockSize)
229                 {
230                         this.code                                       = code;
231                         this.name                                       = name;
232                         this.cipherAlgorithmType        = cipherAlgorithmType;
233                         this.hashAlgorithmType          = hashAlgorithmType;
234                         this.exchangeAlgorithmType      = exchangeAlgorithmType;
235                         this.isExportable                       = exportable;
236                         if (blockMode)
237                         {
238                                 this.cipherMode                 = CipherMode.CBC;
239                         }
240                         this.keyMaterialSize            = keyMaterialSize;
241                         this.expandedKeyMaterialSize= expandedKeyMaterialSize;
242                         this.effectiveKeyBits           = effectiveKeyBits;
243                         this.ivSize                                     = ivSize;
244                         this.blockSize                          = blockSize;
245                         this.keyBlockSize                       = (this.keyMaterialSize + this.HashSize + this.ivSize) << 1;
246                 }
247
248                 #endregion
249
250                 #region Methods
251
252                 internal void Write (byte[] array, int offset, short value)
253                 {
254                         if (offset > array.Length - 2)
255                                 throw new ArgumentException ("offset");
256
257                         array [offset    ] = (byte) (value >> 8);
258                         array [offset + 1] = (byte) value;
259                 }
260
261                 internal void Write (byte[] array, int offset, ulong value)
262                 {
263                         if (offset > array.Length - 8)
264                                 throw new ArgumentException ("offset");
265
266                         array [offset    ] = (byte) (value >> 56);
267                         array [offset + 1] = (byte) (value >> 48);
268                         array [offset + 2] = (byte) (value >> 40);
269                         array [offset + 3] = (byte) (value >> 32);
270                         array [offset + 4] = (byte) (value >> 24);
271                         array [offset + 5] = (byte) (value >> 16);
272                         array [offset + 6] = (byte) (value >> 8);
273                         array [offset + 7] = (byte) value;
274                 }
275
276                 public void InitializeCipher()
277                 {
278                         this.createEncryptionCipher();
279                         this.createDecryptionCipher();
280                 }
281
282                 public byte[] EncryptRecord(byte[] fragment, byte[] mac)
283                 {
284                         // Encryption ( fragment + mac [+ padding + padding_length] )
285                         int length = fragment.Length + mac.Length;
286                         int padlen = 0;
287                         if (this.CipherMode == CipherMode.CBC) {
288                                 // Calculate padding_length
289                                 length++; // keep an extra byte
290                                 padlen = (this.blockSize - length % this.blockSize);
291                                 if (padlen == this.blockSize) {
292                                         padlen = 0;
293                                 }
294                                 length += padlen;
295                         }
296
297                         byte[] plain = new byte [length];
298                         Buffer.BlockCopy (fragment, 0, plain, 0, fragment.Length);
299                         Buffer.BlockCopy (mac, 0, plain, fragment.Length, mac.Length);
300                         if (padlen > 0) {
301                                 int start = fragment.Length + mac.Length;
302                                 for (int i = start; i < (start + padlen + 1); i++) {
303                                         plain[i] = (byte)padlen;
304                                 }
305                         }
306
307                         this.EncryptionCipher.TransformBlock (plain, 0, plain.Length, plain, 0);
308                         return plain;
309                 }
310
311                 public void DecryptRecord(byte[] fragment, out byte[] dcrFragment, out byte[] dcrMAC)
312                 {
313                         int     fragmentSize    = 0;
314                         int paddingLength       = 0;
315
316                         // Decrypt message fragment ( fragment + mac [+ padding + padding_length] )
317                         this.DecryptionCipher.TransformBlock(fragment, 0, fragment.Length, fragment, 0);
318                         // optimization: decrypt "in place", worst case: padding will reduce the size of the data
319                         // this will cut in half the memory allocations (dcrFragment and dcrMAC remains)
320
321                         // Calculate fragment size
322                         if (this.CipherMode == CipherMode.CBC)
323                         {
324                                 // Calculate padding_length
325                                 paddingLength   = fragment[fragment.Length - 1];
326                                 fragmentSize    = (fragment.Length - (paddingLength + 1)) - this.HashSize;
327                         }
328                         else
329                         {
330                                 fragmentSize = fragment.Length - this.HashSize;
331                         }
332
333                         dcrFragment = new byte[fragmentSize];
334                         dcrMAC          = new byte[HashSize];
335
336                         Buffer.BlockCopy(fragment, 0, dcrFragment, 0, dcrFragment.Length);
337                         Buffer.BlockCopy(fragment, dcrFragment.Length, dcrMAC, 0, dcrMAC.Length);
338                 }
339
340                 #endregion
341
342                 #region Abstract Methods
343
344                 public abstract byte[] ComputeClientRecordMAC(ContentType contentType, byte[] fragment);
345
346                 public abstract byte[] ComputeServerRecordMAC(ContentType contentType, byte[] fragment);
347
348                 public abstract void ComputeMasterSecret(byte[] preMasterSecret);
349
350                 public abstract void ComputeKeys();
351
352                 #endregion
353
354                 #region Key Generation Methods
355
356                 public byte[] CreatePremasterSecret()
357                 {
358                         ClientContext   context = (ClientContext)this.context;
359
360                         // Generate random bytes (total size)
361                         byte[] preMasterSecret = this.context.GetSecureRandomBytes (48);
362                         // and replace the first two bytes with the protocol version
363                         // (maximum support version not actual)
364                         preMasterSecret [0] = (byte)(context.ClientHelloProtocol >> 8);
365                         preMasterSecret [1] = (byte)context.ClientHelloProtocol;
366
367                         return preMasterSecret;
368                 }
369
370                 public byte[] PRF(byte[] secret, string label, byte[] data, int length)
371                 {
372                         /* Secret Length calc exmplain from the RFC2246. Section 5
373                          * 
374                          * S1 and S2 are the two halves of the secret and each is the same
375                          * length. S1 is taken from the first half of the secret, S2 from the
376                          * second half. Their length is created by rounding up the length of the
377                          * overall secret divided by two; thus, if the original secret is an odd
378                          * number of bytes long, the last byte of S1 will be the same as the
379                          * first byte of S2.
380                          */
381
382                         // split secret in 2
383                         int secretLen = secret.Length >> 1;
384                         // rounding up
385                         if ((secret.Length & 0x1) == 0x1)
386                                 secretLen++;
387
388                         // Seed
389                         TlsStream seedStream = new TlsStream();
390                         seedStream.Write(Encoding.ASCII.GetBytes(label));
391                         seedStream.Write(data);
392                         byte[] seed = seedStream.ToArray();
393                         seedStream.Reset();
394
395                         // Secret 1
396                         byte[] secret1 = new byte[secretLen];
397                         Buffer.BlockCopy(secret, 0, secret1, 0, secretLen);
398
399                         // Secret2
400                         byte[] secret2 = new byte[secretLen];
401                         Buffer.BlockCopy(secret, (secret.Length - secretLen), secret2, 0, secretLen);
402
403                         // Secret 1 processing
404                         byte[] p_md5 = Expand (MD5.Create (), secret1, seed, length);
405
406                         // Secret 2 processing
407                         byte[] p_sha = Expand (SHA1.Create (), secret2, seed, length);
408
409                         // Perfor XOR of both results
410                         byte[] masterSecret = new byte[length];
411                         for (int i = 0; i < masterSecret.Length; i++)
412                         {
413                                 masterSecret[i] = (byte)(p_md5[i] ^ p_sha[i]);
414                         }
415
416                         return masterSecret;
417                 }
418                 
419                 public byte[] Expand (HashAlgorithm hash, byte[] secret, byte[] seed, int length)
420                 {
421                         int hashLength  = hash.HashSize / 8;
422                         int     iterations      = (int)(length / hashLength);
423                         if ((length % hashLength) > 0)
424                         {
425                                 iterations++;
426                         }
427                         
428                         M.HMAC          hmac    = new M.HMAC (hash, secret);
429                         TlsStream       resMacs = new TlsStream();
430                         
431                         byte[][] hmacs = new byte[iterations + 1][];
432                         hmacs[0] = seed;
433                         for (int i = 1; i <= iterations; i++)
434                         {                               
435                                 TlsStream hcseed = new TlsStream();
436                                 hmac.TransformFinalBlock(hmacs[i-1], 0, hmacs[i-1].Length);
437                                 hmacs[i] = hmac.Hash;
438                                 hcseed.Write(hmacs[i]);
439                                 hcseed.Write(seed);
440                                 hmac.TransformFinalBlock(hcseed.ToArray(), 0, (int)hcseed.Length);
441                                 resMacs.Write(hmac.Hash);
442                                 hcseed.Reset();
443                         }
444
445                         byte[] res = new byte[length];
446                         
447                         Buffer.BlockCopy(resMacs.ToArray(), 0, res, 0, res.Length);
448
449                         resMacs.Reset();
450
451                         return res;
452                 }
453
454                 #endregion
455
456                 #region Private Methods
457
458                 private void createEncryptionCipher()
459                 {
460                         // Create and configure the symmetric algorithm
461                         switch (this.cipherAlgorithmType)
462                         {
463                                 case CipherAlgorithmType.Des:
464                                         this.encryptionAlgorithm = DES.Create();
465                                         break;
466
467                                 case CipherAlgorithmType.Rc2:
468                                         this.encryptionAlgorithm = RC2.Create();
469                                         break;
470
471                                 case CipherAlgorithmType.Rc4:
472                                         this.encryptionAlgorithm = new ARC4Managed();
473                                         break;
474
475                                 case CipherAlgorithmType.TripleDes:
476                                         this.encryptionAlgorithm = TripleDES.Create();
477                                         break;
478
479                                 case CipherAlgorithmType.Rijndael:
480 #if MOBILE || NET_4_0
481                                         // only AES is really used - and we can use CommonCrypto for iOS and OSX this way
482                                         this.encryptionAlgorithm = Aes.Create();
483 #else
484                                         this.encryptionAlgorithm = Rijndael.Create();
485 #endif
486                                         break;
487                         }
488
489                         // If it's a block cipher
490                         if (this.cipherMode == CipherMode.CBC)
491                         {
492                                 // Configure encrypt algorithm
493                                 this.encryptionAlgorithm.Mode           = this.cipherMode;
494                                 this.encryptionAlgorithm.Padding        = PaddingMode.None;
495                                 this.encryptionAlgorithm.KeySize        = this.expandedKeyMaterialSize * 8;
496                                 this.encryptionAlgorithm.BlockSize      = this.blockSize * 8;
497                         }
498
499                         // Set the key and IV for the algorithm
500                         if (this.context is ClientContext)
501                         {
502                                 this.encryptionAlgorithm.Key    = this.context.ClientWriteKey;
503                                 this.encryptionAlgorithm.IV             = this.context.ClientWriteIV;
504                         }
505                         else
506                         {
507                                 this.encryptionAlgorithm.Key    = this.context.ServerWriteKey;
508                                 this.encryptionAlgorithm.IV             = this.context.ServerWriteIV;
509                         }
510                         
511                         // Create encryption cipher
512                         this.encryptionCipher = this.encryptionAlgorithm.CreateEncryptor();
513
514                         // Create the HMAC algorithm
515                         if (this.context is ClientContext)
516                         {
517                                 this.clientHMAC = new M.HMAC(
518                                         CreateHashAlgorithm (),
519                                         this.context.Negotiating.ClientWriteMAC);
520                         }
521                         else
522                         {
523                                 this.serverHMAC = new M.HMAC(
524                                         CreateHashAlgorithm (),
525                                         this.context.Negotiating.ServerWriteMAC);
526                         }
527                 }
528
529                 private void createDecryptionCipher()
530                 {
531                         // Create and configure the symmetric algorithm
532                         switch (this.cipherAlgorithmType)
533                         {
534                                 case CipherAlgorithmType.Des:
535                                         this.decryptionAlgorithm = DES.Create();
536                                         break;
537
538                                 case CipherAlgorithmType.Rc2:
539                                         this.decryptionAlgorithm = RC2.Create();
540                                         break;
541
542                                 case CipherAlgorithmType.Rc4:
543                                         this.decryptionAlgorithm = new ARC4Managed();
544                                         break;
545
546                                 case CipherAlgorithmType.TripleDes:
547                                         this.decryptionAlgorithm = TripleDES.Create();
548                                         break;
549
550                                 case CipherAlgorithmType.Rijndael:
551 #if MOBILE || NET_4_0
552                                         // only AES is really used - and we can use CommonCrypto for iOS and OSX this way
553                                         this.decryptionAlgorithm = Aes.Create();
554 #else
555                                         this.decryptionAlgorithm = Rijndael.Create();
556 #endif
557                                         break;
558                         }
559
560                         // If it's a block cipher
561                         if (this.cipherMode == CipherMode.CBC)
562                         {
563                                 // Configure encrypt algorithm
564                                 this.decryptionAlgorithm.Mode           = this.cipherMode;
565                                 this.decryptionAlgorithm.Padding        = PaddingMode.None;
566                                 this.decryptionAlgorithm.KeySize        = this.expandedKeyMaterialSize * 8;
567                                 this.decryptionAlgorithm.BlockSize      = this.blockSize * 8;
568                         }
569
570                         // Set the key and IV for the algorithm
571                         if (this.context is ClientContext)
572                         {
573                                 this.decryptionAlgorithm.Key    = this.context.ServerWriteKey;
574                                 this.decryptionAlgorithm.IV             = this.context.ServerWriteIV;
575                         }
576                         else
577                         {
578                                 this.decryptionAlgorithm.Key    = this.context.ClientWriteKey;
579                                 this.decryptionAlgorithm.IV             = this.context.ClientWriteIV;
580                         }
581
582                         // Create decryption cipher                     
583                         this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
584
585                         // Create the HMAC
586                         if (this.context is ClientContext)
587                         {
588                                 this.serverHMAC = new M.HMAC(
589                                         CreateHashAlgorithm (),
590                                         this.context.Negotiating.ServerWriteMAC);
591                         }
592                         else
593                         {
594                                 this.clientHMAC = new M.HMAC(
595                                         CreateHashAlgorithm (),
596                                         this.context.Negotiating.ClientWriteMAC);
597                         }
598                 }
599
600                 #endregion
601         }
602 }