2006-04-21 Sebastien Pouliot <sebastien@ximian.com>
[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 //
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 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                                 this.pad1[i] = 0x36;
68                                 this.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.WriteSequenceNumber);
122                         }
123                         else
124                         {
125                                 block.Write(this.Context.ReadSequenceNumber);
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                         DebugHelper.WriteLine(">>>> MasterSecret", this.Context.MasterSecret);
163                 }
164
165                 public override void ComputeKeys()
166                 {
167                         // Compute KeyBlock
168                         TlsStream tmp = new TlsStream();
169                         
170                         char    labelChar       = 'A';
171                         int             count           = 1;
172
173                         while (tmp.Length < this.KeyBlockSize)
174                         {
175                                 string label = String.Empty;
176
177                                 for (int i = 0; i < count; i++)
178                                 {
179                                         label += labelChar.ToString();
180                                 }
181                                                 
182                                 byte[] block = this.prf(this.Context.MasterSecret, label.ToString(), this.Context.RandomSC);
183
184                                 int size = (tmp.Length + block.Length) > this.KeyBlockSize ? (this.KeyBlockSize - (int)tmp.Length) : block.Length;
185                                 
186                                 tmp.Write(block, 0, size);
187
188                                 labelChar++;
189                                 count++;
190                         }
191                         
192                         // Create keyblock
193                         TlsStream keyBlock = new TlsStream(tmp.ToArray());                      
194                         
195                         this.Context.ClientWriteMAC = keyBlock.ReadBytes(this.HashSize);
196                         this.Context.ServerWriteMAC = keyBlock.ReadBytes(this.HashSize);
197                         this.Context.ClientWriteKey = keyBlock.ReadBytes(this.KeyMaterialSize);
198                         this.Context.ServerWriteKey = keyBlock.ReadBytes(this.KeyMaterialSize);
199
200                         if (!this.IsExportable)
201                         {
202                                 if (this.IvSize != 0)
203                                 {
204                                         this.Context.ClientWriteIV = keyBlock.ReadBytes(this.IvSize);
205                                         this.Context.ServerWriteIV = keyBlock.ReadBytes(this.IvSize);
206                                 }
207                                 else
208                                 {
209                                         this.Context.ClientWriteIV = CipherSuite.EmptyArray;
210                                         this.Context.ServerWriteIV = CipherSuite.EmptyArray;
211                                 }
212                         }
213                         else
214                         {
215                                 HashAlgorithm md5 = MD5.Create();
216
217                                 int keySize = (md5.HashSize >> 3); //in bytes not bits
218                                 byte[] temp = new byte [keySize];
219
220                                 // Generate final write keys
221                                 md5.TransformBlock(this.Context.ClientWriteKey, 0, this.Context.ClientWriteKey.Length, temp, 0);
222                                 md5.TransformFinalBlock(this.Context.RandomCS, 0, this.Context.RandomCS.Length);
223                                 byte[] finalClientWriteKey = new byte[this.ExpandedKeyMaterialSize];
224                                 Buffer.BlockCopy(md5.Hash, 0, finalClientWriteKey, 0, this.ExpandedKeyMaterialSize);
225
226                                 md5.Initialize();
227                                 md5.TransformBlock(this.Context.ServerWriteKey, 0, this.Context.ServerWriteKey.Length, temp, 0);
228                                 md5.TransformFinalBlock(this.Context.RandomSC, 0, this.Context.RandomSC.Length);
229                                 byte[] finalServerWriteKey = new byte[this.ExpandedKeyMaterialSize];
230                                 Buffer.BlockCopy(md5.Hash, 0, finalServerWriteKey, 0, this.ExpandedKeyMaterialSize);
231                                 
232                                 this.Context.ClientWriteKey = finalClientWriteKey;
233                                 this.Context.ServerWriteKey = finalServerWriteKey;
234
235                                 // Generate IV keys
236                                 if (this.IvSize > 0) 
237                                 {
238                                         md5.Initialize();
239                                         temp = md5.ComputeHash(this.Context.RandomCS, 0, this.Context.RandomCS.Length);
240                                         this.Context.ClientWriteIV = new byte[this.IvSize];
241                                         Buffer.BlockCopy(temp, 0, this.Context.ClientWriteIV, 0, this.IvSize);
242
243                                         md5.Initialize();
244                                         temp = md5.ComputeHash(this.Context.RandomSC, 0, this.Context.RandomSC.Length);
245                                         this.Context.ServerWriteIV = new byte[this.IvSize];
246                                         Buffer.BlockCopy(temp, 0, this.Context.ServerWriteIV, 0, this.IvSize);
247                                 }
248                                 else 
249                                 {
250                                         this.Context.ClientWriteIV = CipherSuite.EmptyArray;
251                                         this.Context.ServerWriteIV = CipherSuite.EmptyArray;
252                                 }
253                         }
254
255                         DebugHelper.WriteLine(">>>> KeyBlock", keyBlock.ToArray());
256                         DebugHelper.WriteLine(">>>> ClientWriteKey", this.Context.ClientWriteKey);
257                         DebugHelper.WriteLine(">>>> ClientWriteIV", this.Context.ClientWriteIV);
258                         DebugHelper.WriteLine(">>>> ClientWriteMAC", this.Context.ClientWriteMAC);
259                         DebugHelper.WriteLine(">>>> ServerWriteKey", this.Context.ServerWriteKey);
260                         DebugHelper.WriteLine(">>>> ServerWriteIV", this.Context.ServerWriteIV);
261                         DebugHelper.WriteLine(">>>> ServerWriteMAC", this.Context.ServerWriteMAC);
262
263                         ClientSessionCache.SetContextInCache (this.Context);
264                         // Clear no more needed data
265                         keyBlock.Reset();
266                         tmp.Reset();
267                 }
268
269                 #endregion
270
271                 #region Private Methods
272
273                 private byte[] prf(byte[] secret, string label, byte[] random)
274                 {
275                         HashAlgorithm md5 = MD5.Create();
276                         HashAlgorithm sha = SHA1.Create();
277
278                         // Compute SHA hash
279                         TlsStream block = new TlsStream();
280                         block.Write(Encoding.ASCII.GetBytes(label));
281                         block.Write(secret);
282                         block.Write(random);
283                                                 
284                         byte[] shaHash = sha.ComputeHash(block.ToArray(), 0, (int)block.Length);
285
286                         block.Reset();
287
288                         // Compute MD5 hash
289                         block.Write(secret);
290                         block.Write(shaHash);
291
292                         byte[] result = md5.ComputeHash(block.ToArray(), 0, (int)block.Length);
293
294                         // Free resources
295                         block.Reset();
296
297                         return result;
298                 }
299
300                 #endregion
301         }
302 }