Merge pull request #1748 from rolfbjarne/proclib-fix
[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 = CreateHashAlgorithm ();
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 = CreateHashAlgorithm ();
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.IvSize != 0)
194                         {
195                                 this.Context.ClientWriteIV = keyBlock.ReadBytes(this.IvSize);
196                                 this.Context.ServerWriteIV = keyBlock.ReadBytes(this.IvSize);
197                         }
198                         else
199                         {
200                                 this.Context.ClientWriteIV = CipherSuite.EmptyArray;
201                                 this.Context.ServerWriteIV = CipherSuite.EmptyArray;
202                         }
203
204                         DebugHelper.WriteLine(">>>> KeyBlock", keyBlock.ToArray());
205                         DebugHelper.WriteLine(">>>> ClientWriteKey", this.Context.ClientWriteKey);
206                         DebugHelper.WriteLine(">>>> ClientWriteIV", this.Context.ClientWriteIV);
207                         DebugHelper.WriteLine(">>>> ClientWriteMAC", this.Context.Negotiating.ClientWriteMAC);
208                         DebugHelper.WriteLine(">>>> ServerWriteKey", this.Context.ServerWriteKey);
209                         DebugHelper.WriteLine(">>>> ServerWriteIV", this.Context.ServerWriteIV);
210                         DebugHelper.WriteLine(">>>> ServerWriteMAC", this.Context.Negotiating.ServerWriteMAC);
211
212                         ClientSessionCache.SetContextInCache (this.Context);
213                         // Clear no more needed data
214                         keyBlock.Reset();
215                         tmp.Reset();
216                 }
217
218                 #endregion
219
220                 #region Private Methods
221
222                 private byte[] prf(byte[] secret, string label, byte[] random)
223                 {
224                         HashAlgorithm md5 = MD5.Create();
225                         HashAlgorithm sha = SHA1.Create();
226
227                         // Compute SHA hash
228                         TlsStream block = new TlsStream();
229                         block.Write(Encoding.ASCII.GetBytes(label));
230                         block.Write(secret);
231                         block.Write(random);
232                                                 
233                         byte[] shaHash = sha.ComputeHash(block.ToArray(), 0, (int)block.Length);
234
235                         block.Reset();
236
237                         // Compute MD5 hash
238                         block.Write(secret);
239                         block.Write(shaHash);
240
241                         byte[] result = md5.ComputeHash(block.ToArray(), 0, (int)block.Length);
242
243                         // Free resources
244                         block.Reset();
245
246                         return result;
247                 }
248
249                 #endregion
250         }
251 }