TlsAbstractCipherSuite.cs renamed as CipherSuite.cs
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / CipherSuite.cs
1 /* Transport Security Layer (TLS)
2  * Copyright (c) 2003 Carlos Guzmán Álvarez
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 using System.Security.Cryptography.X509Certificates;
30
31 using Mono.Security;
32 using Mono.Security.Cryptography;
33
34 namespace Mono.Security.Protocol.Tls
35 {
36         internal abstract class CipherSuite
37         {
38                 #region FIELDS
39
40                 private short                           code;
41                 private string                          name;
42                 private string                          algName;
43                 private string                          hashName;
44                 private bool                            isExportable;
45                 private CipherMode                      cipherMode;
46                 private byte                            keyMaterialSize;
47                 private byte                            expandedKeyMaterialSize;
48                 private short                           effectiveKeyBits;
49                 private byte                            ivSize;
50                 private byte                            blockSize;
51                 private TlsSessionContext       context;
52                 private SymmetricAlgorithm      encryptionAlgorithm;
53                 private ICryptoTransform        encryptionCipher;
54                 private SymmetricAlgorithm      decryptionAlgorithm;
55                 private ICryptoTransform        decryptionCipher;
56                 private KeyedHashAlgorithm      clientHMAC;
57                 private KeyedHashAlgorithm      serverHMAC;
58                         
59                 #endregion
60
61                 #region PROTECTED_PROPERTIES
62
63                 protected ICryptoTransform EncryptionCipher
64                 {
65                         get { return encryptionCipher; }
66                 }
67
68                 protected ICryptoTransform DecryptionCipher
69                 {
70                         get { return decryptionCipher; }
71                 }
72
73                 protected KeyedHashAlgorithm ClientHMAC
74                 {
75                         get { return clientHMAC; }
76                 }
77                 
78                 protected KeyedHashAlgorithm ServerHMAC
79                 {
80                         get { return serverHMAC; }
81                 }
82
83                 #endregion
84
85                 #region PROPERTIES
86
87                 public short Code
88                 {
89                         get { return code; }
90                 }
91
92                 public string Name
93                 {
94                         get { return name; }
95                 }
96
97                 public bool IsExportable
98                 {
99                         get { return isExportable; }
100                 }
101
102                 public CipherMode CipherMode
103                 {
104                         get { return cipherMode; }
105                 }
106
107                 public int HashSize
108                 {
109                         get { return (int)(hashName == "MD5" ? 16 : 20); }
110                 }
111
112                 public byte     KeyMaterialSize
113                 {
114                         get { return keyMaterialSize; }
115                 }
116
117                 public int KeyBlockSize
118                 {
119                         get 
120                         {
121                                 return keyMaterialSize*2 + HashSize*2 + ivSize*2;
122                         }
123                 }
124
125                 public byte     ExpandedKeyMaterialSize
126                 {
127                         get { return expandedKeyMaterialSize; }
128                 }
129
130                 public byte     EffectiveKeyBits
131                 {
132                         get { return EffectiveKeyBits; }
133                 }
134                 
135                 public byte IvSize
136                 {
137                         get { return ivSize; }
138                 }
139
140                 public byte     BlockSize
141                 {
142                         get { return blockSize; }
143                 }
144
145                 public string HashName
146                 {
147                         get { return hashName; }
148                 }
149
150                 public TlsSessionContext Context
151                 {
152                         get { return context; }
153                         set { context = value; }
154                 }
155
156                 #endregion
157
158                 #region CONSTRUCTORS
159                 
160                 public CipherSuite(short code, string name, string algName, string hashName, bool exportable, bool blockMode, byte keyMaterialSize, byte expandedKeyMaterialSize, short effectiveKeyBytes, byte ivSize, byte blockSize)
161                 {
162                         this.code                                               = code;
163                         this.name                                               = name;
164                         this.algName                                    = algName;
165                         this.hashName                                   = hashName;
166                         this.isExportable                               = exportable;
167                         if (blockMode)
168                         {
169                                 this.cipherMode                         = CipherMode.CBC;
170                         }
171                         this.keyMaterialSize                    = keyMaterialSize;
172                         this.expandedKeyMaterialSize    = expandedKeyMaterialSize;
173                         this.effectiveKeyBits                   = effectiveKeyBits;
174                         this.ivSize                                             = ivSize;
175                         this.blockSize                                  = blockSize;
176                 }
177
178                 #endregion
179
180                 #region METHODS
181
182                 public void InitializeCipher()
183                 {
184                         createEncryptionCipher();
185                         createDecryptionCipher();
186                 }
187
188                 public RSACryptoServiceProvider CreateRSA(X509Certificate certificate)
189                 {
190                         RSAParameters rsaParams = new RSAParameters();
191
192                         // for RSA m_publickey contains 2 ASN.1 integers
193                         // the modulus and the public exponent
194                         ASN1 pubkey     = new ASN1(certificate.GetPublicKey());
195                         ASN1 modulus = pubkey [0];
196                         if ((modulus == null) || (modulus.Tag != 0x02))
197                         {
198                                 return null;
199                         }
200                         ASN1 exponent = pubkey [1];
201                         if (exponent.Tag != 0x02)
202                         {
203                                 return null;
204                         }
205
206                         rsaParams.Modulus       = getUnsignedBigInteger(modulus.Value);
207                         rsaParams.Exponent      = exponent.Value;
208                         
209                         return CreateRSA(rsaParams);
210                 }
211
212                 public RSACryptoServiceProvider CreateRSA(RSAParameters rsaParams)
213                 {                       
214                         // BUG: MS BCL 1.0 can't import a key which 
215                         // isn't the same size as the one present in
216                         // the container.
217                         int keySize = (rsaParams.Modulus.Length << 3);
218                         RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(keySize);
219                         rsa.ImportParameters(rsaParams);
220
221                         return rsa;
222                 }
223
224                 public void UpdateClientCipherIV(byte[] iv)
225                 {
226                         if (cipherMode == CipherMode.CBC)
227                         {
228                                 // Set the new IV
229                                 encryptionAlgorithm.IV  = iv;
230                         
231                                 // Create encryption cipher with the new IV
232                                 encryptionCipher = encryptionAlgorithm.CreateEncryptor();
233                         }
234                 }
235
236                 public void UpdateServerCipherIV(byte[] iv)
237                 {
238                         if (cipherMode == CipherMode.CBC)
239                         {
240                                 // Set the new IV
241                                 decryptionAlgorithm.IV  = iv;
242                         
243                                 // Create encryption cipher with the new IV
244                                 decryptionCipher = decryptionAlgorithm.CreateDecryptor();
245                         }
246                 }
247
248                 public byte[] EncryptRecord(byte[] fragment, byte[] mac)
249                 {
250                         // Encryption ( fragment + mac [+ padding + padding_length] )
251                         MemoryStream ms = new MemoryStream();
252                         CryptoStream cs = new CryptoStream(ms, this.EncryptionCipher, CryptoStreamMode.Write);
253
254                         cs.Write(fragment, 0, fragment.Length);
255                         cs.Write(mac, 0, mac.Length);
256                         if (this.CipherMode == CipherMode.CBC)
257                         {
258                                 // Calculate padding_length
259                                 int fragmentLength      = fragment.Length + mac.Length + 1;
260                                 int paddingLength       = (((fragmentLength/this.BlockSize)*this.BlockSize) + this.BlockSize) - fragmentLength;
261
262                                 // Write padding length byte
263                                 cs.WriteByte((byte)paddingLength);
264                         }
265                         //cs.FlushFinalBlock();
266                         cs.Close();                     
267
268                         return ms.ToArray();
269                 }
270
271                 public void DecryptRecord(byte[] fragment, ref byte[] dcrFragment, ref byte[] dcrMAC)
272                 {
273                         int     fragmentSize    = 0;
274                         int paddingLength       = 0;
275
276                         // Decrypt message fragment ( fragment + mac [+ padding + padding_length] )
277                         byte[] buffer = new byte[fragment.Length];
278                         this.DecryptionCipher.TransformBlock(fragment, 0, fragment.Length, buffer, 0);
279
280                         // Calculate fragment size
281                         if (this.CipherMode == CipherMode.CBC)
282                         {
283                                 // Calculate padding_length
284                                 paddingLength = buffer[buffer.Length - 1];
285                                 for (int i = (buffer.Length - 1); i > (buffer.Length - (paddingLength + 1)); i--)
286                                 {
287                                         if (buffer[i] != paddingLength)
288                                         {
289                                                 paddingLength = 0;
290                                                 break;
291                                         }
292                                 }
293
294                                 fragmentSize = (buffer.Length - (paddingLength + 1)) - HashSize;
295                         }
296                         else
297                         {
298                                 fragmentSize = buffer.Length - HashSize;
299                         }
300
301                         dcrFragment = new byte[fragmentSize];
302                         dcrMAC          = new byte[HashSize];
303
304                         Buffer.BlockCopy(buffer, 0, dcrFragment, 0, dcrFragment.Length);
305                         Buffer.BlockCopy(buffer, dcrFragment.Length, dcrMAC, 0, dcrMAC.Length);
306                 }
307
308                 #endregion
309
310                 #region ABSTRACT_METHODS
311
312                 public abstract byte[] ComputeClientRecordMAC(TlsContentType contentType, byte[] fragment);
313
314                 public abstract byte[] ComputeServerRecordMAC(TlsContentType contentType, byte[] fragment);
315
316                 public abstract void ComputeMasterSecret(byte[] preMasterSecret);
317
318                 public abstract void ComputeKeys();
319
320                 #endregion
321
322                 #region KEY_GENERATION_METODS
323
324                 public byte[] CreatePremasterSecret()
325                 {
326                         TlsStream stream = new TlsStream();
327
328                         // Write protocol version
329                         stream.Write((short)this.Context.Protocol);
330
331                         // Generate random bytes
332                         stream.Write(this.context.GetSecureRandomBytes(46));
333
334                         byte[] preMasterSecret = stream.ToArray();
335
336                         stream.Reset();
337
338                         return preMasterSecret;
339                 }
340
341                 public byte[] PRF(byte[] secret, string label, byte[] data, int length)
342                 {
343                         MD5CryptoServiceProvider        md5     = new MD5CryptoServiceProvider();
344                         SHA1CryptoServiceProvider       sha1 = new SHA1CryptoServiceProvider();
345
346                         int secretLen = secret.Length / 2;
347
348                         // Seed
349                         TlsStream seedStream = new TlsStream();
350                         seedStream.Write(Encoding.ASCII.GetBytes(label));
351                         seedStream.Write(data);
352                         byte[] seed = seedStream.ToArray();
353                         seedStream.Reset();
354
355                         // Secret 1
356                         byte[] secret1 = new byte[secretLen];
357                         System.Array.Copy(secret, 0, secret1, 0, secretLen);
358
359                         // Secret2
360                         byte[] secret2 = new byte[secretLen];
361                         System.Array.Copy(secret, secretLen, secret2, 0, secretLen);
362
363                         // Secret 1 processing
364                         byte[] p_md5 = Expand("MD5", secret1, seed, length);
365
366                         // Secret 2 processing
367                         byte[] p_sha = Expand("SHA1", secret2, seed, length);
368
369                         // Perfor XOR of both results
370                         byte[] masterSecret = new byte[length];
371                         for (int i = 0; i < masterSecret.Length; i++)
372                         {
373                                 masterSecret[i] = (byte)(p_md5[i] ^ p_sha[i]);
374                         }
375
376                         return masterSecret;
377                 }
378                 
379                 public byte[] Expand(string hashName, byte[] secret, byte[] seed, int length)
380                 {
381                         int hashLength  = hashName == "MD5" ? 16 : 20;
382                         int     iterations      = (int)(length / hashLength);
383                         if ((length % hashLength) > 0)
384                         {
385                                 iterations++;
386                         }
387                         
388                         HMAC            hmac    = new HMAC(hashName, secret);
389                         TlsStream       resMacs = new TlsStream();
390                         
391                         byte[][] hmacs = new byte[iterations + 1][];
392                         hmacs[0] = seed;
393                         for (int i = 1; i <= iterations; i++)
394                         {                               
395                                 TlsStream hcseed = new TlsStream();
396                                 hmac.TransformFinalBlock(hmacs[i-1], 0, hmacs[i-1].Length);
397                                 hmacs[i] = hmac.Hash;
398                                 hcseed.Write(hmacs[i]);
399                                 hcseed.Write(seed);
400                                 hmac.TransformFinalBlock(hcseed.ToArray(), 0, (int)hcseed.Length);
401                                 resMacs.Write(hmac.Hash);
402                                 hcseed.Reset();
403                         }
404
405                         byte[] res = new byte[length];
406                         
407                         System.Array.Copy(resMacs.ToArray(), 0, res, 0, res.Length);
408
409                         resMacs.Reset();
410
411                         return res;
412                 }
413
414                 #endregion
415
416                 #region PRIVATE_METHODS
417
418                 // This code is from Mono.Security.X509Certificate class.
419                 private byte[] getUnsignedBigInteger(byte[] integer) 
420                 {
421                         if (integer[0] == 0x00) 
422                         {
423                                 // this first byte is added so we're sure it's an unsigned integer
424                                 // however we can't feed it into RSAParameters or DSAParameters
425                                 int             length   = integer.Length - 1;
426                                 byte[]  uinteger = new byte[length];                            
427                                 Array.Copy(integer, 1, uinteger, 0, length);
428
429                                 return uinteger;
430                         }
431                         else
432                         {
433                                 return integer;
434                         }
435                 }
436
437                 private void createEncryptionCipher()
438                 {
439                         // Create and configure the symmetric algorithm
440                         switch (this.algName)
441                         {
442                                 case "RC4":
443                                         encryptionAlgorithm = new ARC4Managed();
444                                         break;
445
446                                 default:
447                                         encryptionAlgorithm = SymmetricAlgorithm.Create(algName);
448                                         break;
449                         }
450
451                         // If it's a block cipher
452                         if (cipherMode == CipherMode.CBC)
453                         {
454                                 // Configure encrypt algorithm
455                                 encryptionAlgorithm.Mode                = this.cipherMode;
456                                 encryptionAlgorithm.Padding             = PaddingMode.PKCS7;
457                                 encryptionAlgorithm.KeySize             = this.keyMaterialSize * 8;
458                                 encryptionAlgorithm.BlockSize   = this.blockSize * 8;
459                         }
460
461                         // Set the key and IV for the algorithm
462                         encryptionAlgorithm.Key = context.ClientWriteKey;
463                         encryptionAlgorithm.IV  = context.ClientWriteIV;
464                         
465                         // Create encryption cipher
466                         encryptionCipher = encryptionAlgorithm.CreateEncryptor();
467
468                         // Create the HMAC algorithm for the client
469                         clientHMAC = new HMAC(hashName, context.ClientWriteMAC);
470                 }
471
472                 private void createDecryptionCipher()
473                 {
474                         // Create and configure the symmetric algorithm
475                         switch (this.algName)
476                         {
477                                 case "RC4":
478                                         decryptionAlgorithm = new ARC4Managed();
479                                         break;
480
481                                 default:
482                                         decryptionAlgorithm = SymmetricAlgorithm.Create(algName);
483                                         break;
484                         }
485
486                         // If it's a block cipher
487                         if (cipherMode == CipherMode.CBC)
488                         {
489                                 // Configure encrypt algorithm
490                                 decryptionAlgorithm.Mode                = this.cipherMode;
491                                 decryptionAlgorithm.Padding             = PaddingMode.None;
492                                 decryptionAlgorithm.KeySize             = this.keyMaterialSize * 8;
493                                 decryptionAlgorithm.BlockSize   = this.blockSize * 8;
494                         }
495
496                         // Set the key and IV for the algorithm
497                         decryptionAlgorithm.Key = context.ServerWriteKey;
498                         decryptionAlgorithm.IV  = context.ServerWriteIV;
499
500                         // Create decryption cipher                     
501                         decryptionCipher = decryptionAlgorithm.CreateDecryptor();
502
503                         // Create the HMAC algorithm for the server
504                         serverHMAC = new HMAC(hashName, context.ServerWriteMAC);
505                 }
506
507                 #endregion
508         }
509 }