2004-02-17 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[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  * 
4  * Permission is hereby granted, free of charge, to any person 
5  * obtaining a copy of this software and associated documentation 
6  * files (the "Software"), to deal in the Software without restriction, 
7  * including without limitation the rights to use, copy, modify, merge, 
8  * publish, distribute, sublicense, and/or sell copies of the Software, 
9  * and to permit persons to whom the Software is furnished to do so, 
10  * subject to the following conditions:
11  * 
12  * The above copyright notice and this permission notice shall be included 
13  * in all copies or substantial portions of the Software.
14  * 
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
17  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
19  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
20  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
22  * 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 Mono.Security.X509;
33 using M = Mono.Security.Cryptography;
34
35 namespace Mono.Security.Protocol.Tls
36 {
37         internal abstract class CipherSuite
38         {
39                 #region Fields
40
41                 private short                                   code;
42                 private string                                  name;
43                 private CipherAlgorithmType             cipherAlgorithmType;
44                 private HashAlgorithmType               hashAlgorithmType;
45                 private ExchangeAlgorithmType   exchangeAlgorithmType;
46                 private bool                                    isExportable;
47                 private CipherMode                              cipherMode;
48                 private byte                                    keyMaterialSize;
49                 private int                                             keyBlockSize;
50                 private byte                                    expandedKeyMaterialSize;
51                 private short                                   effectiveKeyBits;
52                 private byte                                    ivSize;
53                 private byte                                    blockSize;
54                 private TlsContext              context;
55                 private SymmetricAlgorithm              encryptionAlgorithm;
56                 private ICryptoTransform                encryptionCipher;
57                 private SymmetricAlgorithm              decryptionAlgorithm;
58                 private ICryptoTransform                decryptionCipher;
59                 private KeyedHashAlgorithm              clientHMAC;
60                 private KeyedHashAlgorithm              serverHMAC;
61                         
62                 #endregion
63
64                 #region Protected Properties
65
66                 protected ICryptoTransform EncryptionCipher
67                 {
68                         get { return this.encryptionCipher; }
69                 }
70
71                 protected ICryptoTransform DecryptionCipher
72                 {
73                         get { return this.decryptionCipher; }
74                 }
75
76                 protected KeyedHashAlgorithm ClientHMAC
77                 {
78                         get { return this.clientHMAC; }
79                 }
80                 
81                 protected KeyedHashAlgorithm ServerHMAC
82                 {
83                         get { return this.serverHMAC; }
84                 }
85
86                 #endregion
87
88                 #region Properties
89
90                 public CipherAlgorithmType CipherAlgorithmType
91                 {
92                         get { return this.cipherAlgorithmType; }
93                 }
94
95                 public string HashAlgorithmName
96                 {
97                         get 
98                         {  
99                                 switch (this.hashAlgorithmType)
100                                 {
101                                         case HashAlgorithmType.Md5:
102                                                 return "MD5";
103
104                                         case HashAlgorithmType.Sha1:
105                                                 return "SHA1";
106
107                                         default:
108                                                 return "None";
109                                 }
110                         }
111                 }
112
113                 public HashAlgorithmType HashAlgorithmType
114                 {
115                         get { return this.hashAlgorithmType; }
116                 }
117
118                 public int HashSize
119                 {
120                         get 
121                         { 
122                                 switch (this.hashAlgorithmType)
123                                 {
124                                         case HashAlgorithmType.Md5:
125                                                 return 16;
126
127                                         case HashAlgorithmType.Sha1:
128                                                 return 20;
129
130                                         default:
131                                                 return 0;
132                                 }
133                         }       
134                 }
135                 
136                 public ExchangeAlgorithmType ExchangeAlgorithmType
137                 {
138                         get { return this.exchangeAlgorithmType; }
139                 }
140
141                 public CipherMode CipherMode
142                 {
143                         get { return this.cipherMode; }
144                 }
145
146                 public short Code
147                 {
148                         get { return this.code; }
149                 }
150
151                 public string Name
152                 {
153                         get { return this.name; }
154                 }
155
156                 public bool IsExportable
157                 {
158                         get { return this.isExportable; }
159                 }
160
161                 public byte     KeyMaterialSize
162                 {
163                         get { return this.keyMaterialSize; }
164                 }
165
166                 public int KeyBlockSize
167                 {
168                         get { return this.keyBlockSize; }
169                 }
170
171                 public byte     ExpandedKeyMaterialSize
172                 {
173                         get { return this.expandedKeyMaterialSize; }
174                 }
175
176                 public byte     EffectiveKeyBits
177                 {
178                         get { return this.EffectiveKeyBits; }
179                 }
180                 
181                 public byte IvSize
182                 {
183                         get { return this.ivSize; }
184                 }
185
186                 public byte     BlockSize
187                 {
188                         get { return this.blockSize; }
189                 }
190
191                 public TlsContext Context
192                 {
193                         get { return this.context; }
194                         set { this.context = value; }
195                 }
196
197                 #endregion
198
199                 #region Constructors
200                 
201                 public CipherSuite(
202                         short code, string name, CipherAlgorithmType cipherAlgorithmType, 
203                         HashAlgorithmType hashAlgorithmType, ExchangeAlgorithmType exchangeAlgorithmType,
204                         bool exportable, bool blockMode, byte keyMaterialSize, 
205                         byte expandedKeyMaterialSize, short effectiveKeyBytes, 
206                         byte ivSize, byte blockSize)
207                 {
208                         this.code                                       = code;
209                         this.name                                       = name;
210                         this.cipherAlgorithmType        = cipherAlgorithmType;
211                         this.hashAlgorithmType          = hashAlgorithmType;
212                         this.exchangeAlgorithmType      = exchangeAlgorithmType;
213                         this.isExportable                       = exportable;
214                         if (blockMode)
215                         {
216                                 this.cipherMode                 = CipherMode.CBC;
217                         }
218                         this.keyMaterialSize            = keyMaterialSize;
219                         this.expandedKeyMaterialSize= expandedKeyMaterialSize;
220                         this.effectiveKeyBits           = effectiveKeyBits;
221                         this.ivSize                                     = ivSize;
222                         this.blockSize                          = blockSize;
223                         this.keyBlockSize                       = this.keyMaterialSize*2 + this.HashSize*2 + this.ivSize*2;
224                 }
225
226                 #endregion
227
228                 #region Methods
229
230                 public void InitializeCipher()
231                 {
232                         this.createEncryptionCipher();
233                         this.createDecryptionCipher();
234                 }
235
236                 public RSA CertificateRSA()
237                 {
238                         RSA rsaCert = this.Context.ServerSettings.Certificates[0].RSA;
239                         RSA rsa         = new RSAManaged(rsaCert.KeySize);
240
241                         rsa.ImportParameters(rsaCert.ExportParameters(false));
242
243                         return rsa;
244                 }
245
246                 public void UpdateClientCipherIV(byte[] iv)
247                 {
248                         if (this.cipherMode == CipherMode.CBC)
249                         {
250                                 // Set the new IV
251                                 this.encryptionAlgorithm.IV     = iv;
252                         
253                                 // Create encryption cipher with the new IV
254                                 this.encryptionCipher = this.encryptionAlgorithm.CreateEncryptor();
255                         }
256                 }
257
258                 public void UpdateServerCipherIV(byte[] iv)
259                 {
260                         if (this.cipherMode == CipherMode.CBC)
261                         {
262                                 // Set the new IV
263                                 this.decryptionAlgorithm.IV     = iv;
264                         
265                                 // Create encryption cipher with the new IV
266                                 this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
267                         }
268                 }
269
270                 public byte[] EncryptRecord(byte[] fragment, byte[] mac)
271                 {
272                         // Encryption ( fragment + mac [+ padding + padding_length] )
273                         MemoryStream ms = new MemoryStream();
274                         CryptoStream cs = new CryptoStream(ms, this.EncryptionCipher, CryptoStreamMode.Write);
275
276                         cs.Write(fragment, 0, fragment.Length);
277                         cs.Write(mac, 0, mac.Length);
278                         if (this.CipherMode == CipherMode.CBC)
279                         {
280                                 // Calculate padding_length
281                                 byte fragmentLength     = (byte)(fragment.Length + mac.Length + 1);
282                                 byte paddingLength      = (byte)(this.blockSize - fragmentLength % this.blockSize);
283                                 if (paddingLength == this.blockSize)
284                                 {
285                                         paddingLength = 0;
286                                 }
287
288                                 // Write padding length byte
289                                 byte[] padding = new byte[(paddingLength + 1)];                         
290                                 for (int i = 0; i < (paddingLength + 1); i++)
291                                 {
292                                         padding[i] = paddingLength;
293                                 }
294
295                                 cs.Write(padding, 0, padding.Length);
296                         }
297                         cs.FlushFinalBlock();
298                         cs.Close();
299
300                         return ms.ToArray();
301                 }
302
303                 public void DecryptRecord(byte[] fragment, ref byte[] dcrFragment, ref byte[] dcrMAC)
304                 {
305                         int     fragmentSize    = 0;
306                         int paddingLength       = 0;
307
308                         // Decrypt message fragment ( fragment + mac [+ padding + padding_length] )
309                         byte[] buffer = new byte[fragment.Length];
310                         this.DecryptionCipher.TransformBlock(fragment, 0, fragment.Length, buffer, 0);
311
312                         // Calculate fragment size
313                         if (this.CipherMode == CipherMode.CBC)
314                         {
315                                 // Calculate padding_length
316                                 paddingLength   = buffer[buffer.Length - 1];
317                                 fragmentSize    = (buffer.Length - (paddingLength + 1)) - this.HashSize;
318                         }
319                         else
320                         {
321                                 fragmentSize = buffer.Length - this.HashSize;
322                         }
323
324                         dcrFragment = new byte[fragmentSize];
325                         dcrMAC          = new byte[HashSize];
326
327                         Buffer.BlockCopy(buffer, 0, dcrFragment, 0, dcrFragment.Length);
328                         Buffer.BlockCopy(buffer, dcrFragment.Length, dcrMAC, 0, dcrMAC.Length);
329                 }
330
331                 #endregion
332
333                 #region Abstract Methods
334
335                 public abstract byte[] ComputeClientRecordMAC(TlsContentType contentType, byte[] fragment);
336
337                 public abstract byte[] ComputeServerRecordMAC(TlsContentType contentType, byte[] fragment);
338
339                 public abstract void ComputeMasterSecret(byte[] preMasterSecret);
340
341                 public abstract void ComputeKeys();
342
343                 #endregion
344
345                 #region Key Generation Methods
346
347                 public byte[] CreatePremasterSecret()
348                 {
349                         TlsStream stream = new TlsStream();
350
351                         // Write protocol version
352                         stream.Write((short)this.Context.Protocol);
353
354                         // Generate random bytes
355                         stream.Write(this.context.GetSecureRandomBytes(46));
356
357                         byte[] preMasterSecret = stream.ToArray();
358
359                         stream.Reset();
360
361                         return preMasterSecret;
362                 }
363
364                 public byte[] PRF(byte[] secret, string label, byte[] data, int length)
365                 {
366                         HashAlgorithm md5       = MD5.Create();
367                         HashAlgorithm sha1      = SHA1.Create();
368
369                         int secretLen = secret.Length / 2;
370
371                         // Seed
372                         TlsStream seedStream = new TlsStream();
373                         seedStream.Write(Encoding.ASCII.GetBytes(label));
374                         seedStream.Write(data);
375                         byte[] seed = seedStream.ToArray();
376                         seedStream.Reset();
377
378                         // Secret 1
379                         byte[] secret1 = new byte[secretLen];
380                         System.Array.Copy(secret, 0, secret1, 0, secretLen);
381
382                         // Secret2
383                         byte[] secret2 = new byte[secretLen];
384                         System.Array.Copy(secret, secretLen, secret2, 0, secretLen);
385
386                         // Secret 1 processing
387                         byte[] p_md5 = Expand("MD5", secret1, seed, length);
388
389                         // Secret 2 processing
390                         byte[] p_sha = Expand("SHA1", secret2, seed, length);
391
392                         // Perfor XOR of both results
393                         byte[] masterSecret = new byte[length];
394                         for (int i = 0; i < masterSecret.Length; i++)
395                         {
396                                 masterSecret[i] = (byte)(p_md5[i] ^ p_sha[i]);
397                         }
398
399                         return masterSecret;
400                 }
401                 
402                 public byte[] Expand(string hashName, byte[] secret, byte[] seed, int length)
403                 {
404                         int hashLength  = hashName == "MD5" ? 16 : 20;
405                         int     iterations      = (int)(length / hashLength);
406                         if ((length % hashLength) > 0)
407                         {
408                                 iterations++;
409                         }
410                         
411                         M.HMAC          hmac    = new M.HMAC(hashName, secret);
412                         TlsStream       resMacs = new TlsStream();
413                         
414                         byte[][] hmacs = new byte[iterations + 1][];
415                         hmacs[0] = seed;
416                         for (int i = 1; i <= iterations; i++)
417                         {                               
418                                 TlsStream hcseed = new TlsStream();
419                                 hmac.TransformFinalBlock(hmacs[i-1], 0, hmacs[i-1].Length);
420                                 hmacs[i] = hmac.Hash;
421                                 hcseed.Write(hmacs[i]);
422                                 hcseed.Write(seed);
423                                 hmac.TransformFinalBlock(hcseed.ToArray(), 0, (int)hcseed.Length);
424                                 resMacs.Write(hmac.Hash);
425                                 hcseed.Reset();
426                         }
427
428                         byte[] res = new byte[length];
429                         
430                         System.Array.Copy(resMacs.ToArray(), 0, res, 0, res.Length);
431
432                         resMacs.Reset();
433
434                         return res;
435                 }
436
437                 #endregion
438
439                 #region Private Methods
440
441                 private void createEncryptionCipher()
442                 {
443                         // Create and configure the symmetric algorithm
444                         switch (this.cipherAlgorithmType)
445                         {
446                                 case CipherAlgorithmType.Des:
447                                         this.encryptionAlgorithm = DES.Create();
448                                         break;
449
450                                 case CipherAlgorithmType.Rc2:
451                                         this.encryptionAlgorithm = RC2.Create();
452                                         break;
453
454                                 case CipherAlgorithmType.Rc4:
455                                         this.encryptionAlgorithm = new ARC4Managed();
456                                         break;
457
458                                 case CipherAlgorithmType.TripleDes:
459                                         this.encryptionAlgorithm = TripleDES.Create();
460                                         break;
461
462                                 case CipherAlgorithmType.Rijndael:
463                                         this.encryptionAlgorithm = Rijndael.Create();
464                                         break;
465                         }
466
467                         // If it's a block cipher
468                         if (this.cipherMode == CipherMode.CBC)
469                         {
470                                 // Configure encrypt algorithm
471                                 this.encryptionAlgorithm.Mode           = this.cipherMode;
472                                 this.encryptionAlgorithm.Padding        = PaddingMode.None;
473                                 this.encryptionAlgorithm.KeySize        = this.keyMaterialSize * 8;
474                                 this.encryptionAlgorithm.BlockSize      = this.blockSize * 8;
475                         }
476
477                         // Set the key and IV for the algorithm
478                         this.encryptionAlgorithm.Key    = this.context.ClientWriteKey;
479                         this.encryptionAlgorithm.IV             = this.context.ClientWriteIV;
480                         
481                         // Create encryption cipher
482                         this.encryptionCipher = this.encryptionAlgorithm.CreateEncryptor();
483
484                         // Create the HMAC algorithm for the client
485                         this.clientHMAC = new M.HMAC(
486                                 this.HashAlgorithmName,
487                                 this.context.ClientWriteMAC);
488                 }
489
490                 private void createDecryptionCipher()
491                 {
492                         // Create and configure the symmetric algorithm
493                         switch (this.cipherAlgorithmType)
494                         {
495                                 case CipherAlgorithmType.Des:
496                                         this.decryptionAlgorithm = DES.Create();
497                                         break;
498
499                                 case CipherAlgorithmType.Rc2:
500                                         this.decryptionAlgorithm = RC2.Create();
501                                         break;
502
503                                 case CipherAlgorithmType.Rc4:
504                                         this.decryptionAlgorithm = new ARC4Managed();
505                                         break;
506
507                                 case CipherAlgorithmType.TripleDes:
508                                         this.decryptionAlgorithm = TripleDES.Create();
509                                         break;
510
511                                 case CipherAlgorithmType.Rijndael:
512                                         this.decryptionAlgorithm = Rijndael.Create();
513                                         break;
514                         }
515
516                         // If it's a block cipher
517                         if (this.cipherMode == CipherMode.CBC)
518                         {
519                                 // Configure encrypt algorithm
520                                 this.decryptionAlgorithm.Mode           = this.cipherMode;
521                                 this.decryptionAlgorithm.Padding        = PaddingMode.None;
522                                 this.decryptionAlgorithm.KeySize        = this.keyMaterialSize * 8;
523                                 this.decryptionAlgorithm.BlockSize      = this.blockSize * 8;
524                         }
525
526                         // Set the key and IV for the algorithm
527                         this.decryptionAlgorithm.Key    = this.context.ServerWriteKey;
528                         this.decryptionAlgorithm.IV             = this.context.ServerWriteIV;
529
530                         // Create decryption cipher                     
531                         this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
532
533                         // Create the HMAC algorithm for the server
534                         this.serverHMAC = new M.HMAC(
535                                 this.HashAlgorithmName,
536                                 this.context.ServerWriteMAC);
537                 }
538
539                 #endregion
540         }
541 }