2004-03-17 Carlos Guzman Alvarez <carlosga@telefonica.net>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / SslCipherSuite.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 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 class SslCipherSuite : CipherSuite
37         {
38                 #region Fields
39
40                 private byte[] pad1;
41                 private byte[] pad2;
42
43                 #endregion
44
45                 #region Constructors
46                 
47                 public SslCipherSuite(
48                         short code, string name, CipherAlgorithmType cipherAlgorithmType, 
49                         HashAlgorithmType hashAlgorithmType, ExchangeAlgorithmType exchangeAlgorithmType,
50                         bool exportable, bool blockMode, byte keyMaterialSize, 
51                         byte expandedKeyMaterialSize, short effectiveKeyBytes, 
52                         byte ivSize, byte blockSize) :
53                         base(code, name, cipherAlgorithmType, hashAlgorithmType, 
54                         exchangeAlgorithmType, exportable, blockMode, keyMaterialSize, 
55                         expandedKeyMaterialSize, effectiveKeyBytes, ivSize, blockSize)
56
57                 {
58                         int padLength = (hashAlgorithmType == HashAlgorithmType.Md5) ? 48 : 40;
59
60                         // Fill pad arrays
61                         this.pad1 = new byte[padLength];
62                         this.pad2 = new byte[padLength];
63
64                         /* Pad the key for inner and outer digest */
65                         for (int i = 0; i < padLength; ++i) 
66                         {
67                                 pad1[i] = 0x36;
68                                 pad2[i] = 0x5C;
69                         }
70                 }
71
72                 #endregion
73
74                 #region MAC Generation Methods
75
76                 public override byte[] ComputeServerRecordMAC(ContentType contentType, byte[] fragment)
77                 {
78                         HashAlgorithm   hash    = HashAlgorithm.Create(this.HashAlgorithmName);
79                         TlsStream               block   = new TlsStream();
80
81                         block.Write(this.Context.ServerWriteMAC);
82                         block.Write(this.pad1);
83                         if (this.Context is ClientContext)
84                         {
85                                 block.Write(this.Context.ReadSequenceNumber);
86                         }
87                         else
88                         {
89                                 block.Write(this.Context.WriteSequenceNumber);
90                         }
91                         block.Write((byte)contentType);
92                         block.Write((short)fragment.Length);
93                         block.Write(fragment);
94                         
95                         hash.ComputeHash(block.ToArray(), 0, (int)block.Length);
96
97                         byte[] blockHash = hash.Hash;
98
99                         block.Reset();
100
101                         block.Write(this.Context.ServerWriteMAC);
102                         block.Write(this.pad2);
103                         block.Write(blockHash);
104
105                         hash.ComputeHash(block.ToArray(), 0, (int)block.Length);
106
107                         block.Reset();
108
109                         return hash.Hash;
110                 }
111
112                 public override byte[] ComputeClientRecordMAC(ContentType contentType, byte[] fragment)
113                 {
114                         HashAlgorithm   hash    = HashAlgorithm.Create(this.HashAlgorithmName);
115                         TlsStream               block   = new TlsStream();
116
117                         block.Write(this.Context.ClientWriteMAC);
118                         block.Write(this.pad1);
119                         if (this.Context is ClientContext)
120                         {
121                                 block.Write(this.Context.ReadSequenceNumber);
122                         }
123                         else
124                         {
125                                 block.Write(this.Context.WriteSequenceNumber);
126                         }
127                         block.Write((byte)contentType);
128                         block.Write((short)fragment.Length);
129                         block.Write(fragment);
130                         
131                         hash.ComputeHash(block.ToArray(), 0, (int)block.Length);
132
133                         byte[] blockHash = hash.Hash;
134
135                         block.Reset();
136
137                         block.Write(this.Context.ClientWriteMAC);
138                         block.Write(this.pad2);
139                         block.Write(blockHash);
140
141                         hash.ComputeHash(block.ToArray(), 0, (int)block.Length);
142
143                         block.Reset();
144
145                         return hash.Hash;
146                 }
147
148                 #endregion
149
150                 #region Key Generation Methods
151
152                 public override void ComputeMasterSecret(byte[] preMasterSecret)
153                 {
154                         TlsStream masterSecret = new TlsStream();
155
156                         masterSecret.Write(this.prf(preMasterSecret, "A", this.Context.RandomCS));
157                         masterSecret.Write(this.prf(preMasterSecret, "BB", this.Context.RandomCS));
158                         masterSecret.Write(this.prf(preMasterSecret, "CCC", this.Context.RandomCS));
159
160                         this.Context.MasterSecret = masterSecret.ToArray();
161                 }
162
163                 public override void ComputeKeys()
164                 {
165                         // Compute KeyBlock
166                         TlsStream tmp = new TlsStream();
167                         
168                         char    labelChar       = 'A';
169                         int             count           = 1;
170                         while (tmp.Length < this.KeyBlockSize)
171                         {
172                                 string label = String.Empty;
173
174                                 for (int i = 0; i < count; i++)
175                                 {
176                                         label += labelChar.ToString();
177                                 }
178                                                 
179                                 byte[] block = this.prf(this.Context.MasterSecret, label.ToString(), this.Context.RandomSC);
180
181                                 int size = (tmp.Length + block.Length) > this.KeyBlockSize ? (this.KeyBlockSize - (int)tmp.Length) : block.Length;
182                                 
183                                 tmp.Write(block, 0, size);
184
185                                 labelChar++;
186                                 count++;
187                         }
188                         
189                         // Create keyblock
190                         TlsStream keyBlock = new TlsStream(tmp.ToArray());
191
192                         this.Context.ClientWriteMAC = keyBlock.ReadBytes(this.HashSize);
193                         this.Context.ServerWriteMAC = keyBlock.ReadBytes(this.HashSize);
194                         this.Context.ClientWriteKey = keyBlock.ReadBytes(this.KeyMaterialSize);
195                         this.Context.ServerWriteKey = keyBlock.ReadBytes(this.KeyMaterialSize);
196
197                         if (!this.IsExportable)
198                         {
199                                 if (this.IvSize != 0)
200                                 {
201                                         this.Context.ClientWriteIV = keyBlock.ReadBytes(this.IvSize);
202                                         this.Context.ServerWriteIV = keyBlock.ReadBytes(this.IvSize);
203                                 }
204                                 else
205                                 {
206                                         this.Context.ClientWriteIV = CipherSuite.EmptyArray;
207                                         this.Context.ServerWriteIV = CipherSuite.EmptyArray;
208                                 }
209                         }
210                         else
211                         {
212                                 HashAlgorithm md5 = MD5.Create();
213
214                                 // Generate final write keys
215                                 byte[] finalClientWriteKey      = new byte[md5.HashSize];
216                                 md5.TransformBlock(this.Context.ClientWriteKey, 0, this.Context.ClientWriteKey.Length, finalClientWriteKey, 0);
217                                 finalClientWriteKey = md5.TransformFinalBlock(this.Context.RandomCS, 0, this.Context.RandomCS.Length);
218
219                                 byte[] finalServerWriteKey      = new byte[md5.HashSize];
220                                 md5.TransformBlock(this.Context.ServerWriteKey, 0, this.Context.ServerWriteKey.Length, finalServerWriteKey, 0);
221                                 finalClientWriteKey = md5.TransformFinalBlock(this.Context.RandomSC, 0, this.Context.RandomSC.Length);
222                                 
223                                 this.Context.ClientWriteKey     = finalClientWriteKey;
224                                 this.Context.ServerWriteKey     = finalServerWriteKey;
225
226                                 // Generate IV keys
227                                 this.Context.ClientWriteIV = md5.TransformFinalBlock(this.Context.RandomCS, 0, this.Context.RandomCS.Length);
228                                 this.Context.ServerWriteIV = md5.TransformFinalBlock(this.Context.RandomSC, 0, this.Context.RandomSC.Length);
229                         }
230
231                         // Clear no more needed data
232                         keyBlock.Reset();
233                         tmp.Reset();
234                 }
235
236                 #endregion
237
238                 #region Private Methods
239
240                 private byte[] prf(byte[] secret, string label, byte[] random)
241                 {
242                         HashAlgorithm md5 = MD5.Create();
243                         HashAlgorithm sha = SHA1.Create();
244
245                         // Compute SHA hash
246                         TlsStream block = new TlsStream();
247                         block.Write(Encoding.ASCII.GetBytes(label));
248                         block.Write(secret);
249                         block.Write(random);
250                                                 
251                         byte[] shaHash = sha.ComputeHash(block.ToArray(), 0, (int)block.Length);
252
253                         block.Reset();
254
255                         // Compute MD5 hash
256                         block.Write(secret);
257                         block.Write(shaHash);
258
259                         byte[] result = md5.ComputeHash(block.ToArray(), 0, (int)block.Length);
260
261                         // Free resources
262                         block.Reset();
263
264                         return result;
265                 }
266
267                 #endregion
268         }
269 }