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