move to from olive to mcs
[mono.git] / mcs / class / System.ServiceModel / 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                 public HashAlgorithmType HashAlgorithmType
119                 {
120                         get { return this.hashAlgorithmType; }
121                 }
122
123                 public int HashSize
124                 {
125                         get 
126                         { 
127                                 switch (this.hashAlgorithmType)
128                                 {
129                                         case HashAlgorithmType.Md5:
130                                                 return 16;
131
132                                         case HashAlgorithmType.Sha1:
133                                                 return 20;
134
135                                         default:
136                                                 return 0;
137                                 }
138                         }       
139                 }
140                 
141                 public ExchangeAlgorithmType ExchangeAlgorithmType
142                 {
143                         get { return this.exchangeAlgorithmType; }
144                 }
145
146                 public CipherMode CipherMode
147                 {
148                         get { return this.cipherMode; }
149                 }
150
151                 public short Code
152                 {
153                         get { return this.code; }
154                 }
155
156                 public string Name
157                 {
158                         get { return this.name; }
159                 }
160
161                 public bool IsExportable
162                 {
163                         get { return this.isExportable; }
164                 }
165
166                 public byte     KeyMaterialSize
167                 {
168                         get { return this.keyMaterialSize; }
169                 }
170
171                 public int KeyBlockSize
172                 {
173                         get { return this.keyBlockSize; }
174                 }
175
176                 public byte     ExpandedKeyMaterialSize
177                 {
178                         get { return this.expandedKeyMaterialSize; }
179                 }
180
181                 public short    EffectiveKeyBits
182                 {
183                         get { return this.effectiveKeyBits; }
184                 }
185                 
186                 public byte IvSize
187                 {
188                         get { return this.ivSize; }
189                 }
190
191                 /*
192                 public byte     BlockSize
193                 {
194                         get { return this.blockSize; }
195                 }
196                 */
197
198                 public Context Context
199                 {
200                         get { return this.context; }
201                         set 
202                         { 
203                                 this.context = value; 
204                         }
205                 }
206
207                 #endregion
208
209                 #region Constructors
210                 
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)
217                 {
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)
225                         {
226                                 this.cipherMode                 = CipherMode.CBC;
227                         }
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;
234                 }
235
236                 #endregion
237
238                 #region Methods
239
240                 internal void Write (byte[] array, int offset, short value)
241                 {
242                         if (offset > array.Length - 2)
243                                 throw new ArgumentException ("offset");
244
245                         array [offset    ] = (byte) (value >> 8);
246                         array [offset + 1] = (byte) value;
247                 }
248
249                 internal void Write (byte[] array, int offset, ulong value)
250                 {
251                         if (offset > array.Length - 8)
252                                 throw new ArgumentException ("offset");
253
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;
262                 }
263
264                 public void InitializeCipher()
265                 {
266                         this.createEncryptionCipher();
267                         this.createDecryptionCipher();
268                 }
269
270                 public byte[] EncryptRecord(byte[] fragment, byte[] mac)
271                 {
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;
281                                 }
282                                 length += padlen;
283                         }
284
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;
292                                 }
293                         }
294
295                         this.EncryptionCipher.TransformBlock (plain, 0, plain.Length, plain, 0);
296                         return plain;
297                 }
298
299                 public void DecryptRecord(byte[] fragment, out byte[] dcrFragment, out byte[] dcrMAC)
300                 {
301                         int     fragmentSize    = 0;
302                         int paddingLength       = 0;
303
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)
308
309                         // Calculate fragment size
310                         if (this.CipherMode == CipherMode.CBC)
311                         {
312                                 // Calculate padding_length
313                                 paddingLength   = fragment[fragment.Length - 1];
314                                 fragmentSize    = (fragment.Length - (paddingLength + 1)) - this.HashSize;
315                         }
316                         else
317                         {
318                                 fragmentSize = fragment.Length - this.HashSize;
319                         }
320
321                         dcrFragment = new byte[fragmentSize];
322                         dcrMAC          = new byte[HashSize];
323
324                         Buffer.BlockCopy(fragment, 0, dcrFragment, 0, dcrFragment.Length);
325                         Buffer.BlockCopy(fragment, dcrFragment.Length, dcrMAC, 0, dcrMAC.Length);
326                 }
327
328                 #endregion
329
330                 #region Abstract Methods
331
332                 public abstract byte[] ComputeClientRecordMAC(ContentType contentType, byte[] fragment);
333
334                 public abstract byte[] ComputeServerRecordMAC(ContentType contentType, byte[] fragment);
335
336                 public abstract void ComputeMasterSecret(byte[] preMasterSecret);
337
338                 public abstract void ComputeKeys();
339
340                 #endregion
341
342                 #region Key Generation Methods
343
344                 public byte[] CreatePremasterSecret()
345                 {
346                         ClientContext   context = (ClientContext)this.context;
347
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;
354
355                         return preMasterSecret;
356                 }
357
358                 public byte[] PRF(byte[] secret, string label, byte[] data, int length)
359                 {
360                         /* Secret Length calc exmplain from the RFC2246. Section 5
361                          * 
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.
368                          */
369
370                         // split secret in 2
371                         int secretLen = secret.Length >> 1;
372                         // rounding up
373                         if ((secret.Length & 0x1) == 0x1)
374                                 secretLen++;
375
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();
382
383                         // Secret 1
384                         byte[] secret1 = new byte[secretLen];
385                         Buffer.BlockCopy(secret, 0, secret1, 0, secretLen);
386
387                         // Secret2
388                         byte[] secret2 = new byte[secretLen];
389                         Buffer.BlockCopy(secret, (secret.Length - secretLen), secret2, 0, secretLen);
390
391                         // Secret 1 processing
392                         byte[] p_md5 = Expand("MD5", secret1, seed, length);
393
394                         // Secret 2 processing
395                         byte[] p_sha = Expand("SHA1", secret2, seed, length);
396
397                         // Perfor XOR of both results
398                         byte[] masterSecret = new byte[length];
399                         for (int i = 0; i < masterSecret.Length; i++)
400                         {
401                                 masterSecret[i] = (byte)(p_md5[i] ^ p_sha[i]);
402                         }
403
404                         return masterSecret;
405                 }
406                 
407                 public byte[] Expand(string hashName, byte[] secret, byte[] seed, int length)
408                 {
409                         int hashLength  = hashName == "MD5" ? 16 : 20;
410                         int     iterations      = (int)(length / hashLength);
411                         if ((length % hashLength) > 0)
412                         {
413                                 iterations++;
414                         }
415                         
416                         M.HMAC          hmac    = new M.HMAC(hashName, secret);
417                         TlsStream       resMacs = new TlsStream();
418                         
419                         byte[][] hmacs = new byte[iterations + 1][];
420                         hmacs[0] = seed;
421                         for (int i = 1; i <= iterations; i++)
422                         {                               
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();
431                         }
432
433                         byte[] res = new byte[length];
434                         
435                         Buffer.BlockCopy(resMacs.ToArray(), 0, res, 0, res.Length);
436
437                         resMacs.Reset();
438
439                         return res;
440                 }
441
442                 #endregion
443
444                 #region Private Methods
445
446                 private void createEncryptionCipher()
447                 {
448                         // Create and configure the symmetric algorithm
449                         switch (this.cipherAlgorithmType)
450                         {
451                                 case CipherAlgorithmType.Des:
452                                         this.encryptionAlgorithm = DES.Create();
453                                         break;
454
455                                 case CipherAlgorithmType.Rc2:
456                                         this.encryptionAlgorithm = RC2.Create();
457                                         break;
458
459                                 case CipherAlgorithmType.Rc4:
460                                         this.encryptionAlgorithm = new ARC4Managed();
461                                         break;
462
463                                 case CipherAlgorithmType.TripleDes:
464                                         this.encryptionAlgorithm = TripleDES.Create();
465                                         break;
466
467                                 case CipherAlgorithmType.Rijndael:
468                                         this.encryptionAlgorithm = Rijndael.Create();
469                                         break;
470                         }
471
472                         // If it's a block cipher
473                         if (this.cipherMode == CipherMode.CBC)
474                         {
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;
480                         }
481
482                         // Set the key and IV for the algorithm
483                         if (this.context is ClientContext)
484                         {
485                                 this.encryptionAlgorithm.Key    = this.context.ClientWriteKey;
486                                 this.encryptionAlgorithm.IV             = this.context.ClientWriteIV;
487                         }
488                         else
489                         {
490                                 this.encryptionAlgorithm.Key    = this.context.ServerWriteKey;
491                                 this.encryptionAlgorithm.IV             = this.context.ServerWriteIV;
492                         }
493                         
494                         // Create encryption cipher
495                         this.encryptionCipher = this.encryptionAlgorithm.CreateEncryptor();
496
497                         // Create the HMAC algorithm
498                         if (this.context is ClientContext)
499                         {
500                                 this.clientHMAC = new M.HMAC(
501                                         this.HashAlgorithmName,
502                                         this.context.Negotiating.ClientWriteMAC);
503                         }
504                         else
505                         {
506                                 this.serverHMAC = new M.HMAC(
507                                         this.HashAlgorithmName,
508                                         this.context.Negotiating.ServerWriteMAC);
509                         }
510                 }
511
512                 private void createDecryptionCipher()
513                 {
514                         // Create and configure the symmetric algorithm
515                         switch (this.cipherAlgorithmType)
516                         {
517                                 case CipherAlgorithmType.Des:
518                                         this.decryptionAlgorithm = DES.Create();
519                                         break;
520
521                                 case CipherAlgorithmType.Rc2:
522                                         this.decryptionAlgorithm = RC2.Create();
523                                         break;
524
525                                 case CipherAlgorithmType.Rc4:
526                                         this.decryptionAlgorithm = new ARC4Managed();
527                                         break;
528
529                                 case CipherAlgorithmType.TripleDes:
530                                         this.decryptionAlgorithm = TripleDES.Create();
531                                         break;
532
533                                 case CipherAlgorithmType.Rijndael:
534                                         this.decryptionAlgorithm = Rijndael.Create();
535                                         break;
536                         }
537
538                         // If it's a block cipher
539                         if (this.cipherMode == CipherMode.CBC)
540                         {
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;
546                         }
547
548                         // Set the key and IV for the algorithm
549                         if (this.context is ClientContext)
550                         {
551                                 this.decryptionAlgorithm.Key    = this.context.ServerWriteKey;
552                                 this.decryptionAlgorithm.IV             = this.context.ServerWriteIV;
553                         }
554                         else
555                         {
556                                 this.decryptionAlgorithm.Key    = this.context.ClientWriteKey;
557                                 this.decryptionAlgorithm.IV             = this.context.ClientWriteIV;
558                         }
559
560                         // Create decryption cipher                     
561                         this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
562
563                         // Create the HMAC
564                         if (this.context is ClientContext)
565                         {
566                                 this.serverHMAC = new M.HMAC(
567                                         this.HashAlgorithmName,
568                                         this.context.Negotiating.ServerWriteMAC);
569                         }
570                         else
571                         {
572                                 this.clientHMAC = new M.HMAC(
573                                         this.HashAlgorithmName,
574                                         this.context.Negotiating.ClientWriteMAC);
575                         }
576                 }
577
578                 #endregion
579         }
580 }