(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / RecordProtocol.cs
blobec39ea3eea77169fe8d893baa95d42adfb043bcf
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
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.
25 using System;
26 using System.Collections;
27 using System.IO;
28 using System.Security.Cryptography;
29 using System.Security.Cryptography.X509Certificates;
31 using Mono.Security.Protocol.Tls.Handshake;
33 namespace Mono.Security.Protocol.Tls
35 internal abstract class RecordProtocol
37 #region Fields
39 protected Stream innerStream;
40 protected Context context;
42 #endregion
44 #region Properties
46 public Context Context
48 get { return this.context; }
49 set { this.context = value; }
52 #endregion
54 #region Constructors
56 public RecordProtocol(Stream innerStream, Context context)
58 this.innerStream = innerStream;
59 this.context = context;
60 this.context.RecordProtocol = this;
63 #endregion
65 #region Abstract Methods
67 public abstract void SendRecord(HandshakeType type);
68 protected abstract void ProcessHandshakeMessage(TlsStream handMsg);
69 protected abstract void ProcessChangeCipherSpec();
71 #endregion
73 #region Reveive Record Methods
75 public byte[] ReceiveRecord()
77 if (this.context.ConnectionEnd)
79 throw new TlsException(
80 AlertDescription.InternalError,
81 "The session is finished and it's no longer valid.");
84 // Try to read the Record Content Type
85 int type = this.innerStream.ReadByte();
86 if (type == -1)
88 return null;
91 ContentType contentType = (ContentType)type;
92 byte[] buffer = this.ReadRecordBuffer(contentType);
94 TlsStream message = new TlsStream(buffer);
96 // Decrypt message contents if needed
97 if (contentType == ContentType.Alert && buffer.Length == 2)
100 else
102 if (this.context.IsActual && contentType != ContentType.ChangeCipherSpec)
104 message = this.decryptRecordFragment(contentType, message.ToArray());
106 DebugHelper.WriteLine("Decrypted record data", message.ToArray());
110 // Set last handshake message received to None
111 this.context.LastHandshakeMsg = HandshakeType.None;
113 // Process record
114 byte[] result = message.ToArray();
116 switch (contentType)
118 case ContentType.ClientHelloV2:
119 // Set the last handshake message received as the standard ClientHello
120 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
121 result = null;
122 break;
124 case ContentType.Alert:
125 this.ProcessAlert((AlertLevel)message.ReadByte(), (AlertDescription)message.ReadByte());
126 result = null;
127 break;
129 case ContentType.ChangeCipherSpec:
130 this.ProcessChangeCipherSpec();
131 break;
133 case ContentType.ApplicationData:
134 break;
136 case ContentType.Handshake:
137 while (!message.EOF)
139 this.ProcessHandshakeMessage(message);
142 // Update handshakes of current messages
143 this.context.HandshakeMessages.Write(message.ToArray());
144 break;
146 default:
147 throw new TlsException(
148 AlertDescription.UnexpectedMessage,
149 "Unknown record received from server.");
152 return result;
155 private byte[] ReadRecordBuffer(ContentType contentType)
157 switch (contentType)
159 case ContentType.ClientHelloV2:
160 return this.ReadClientHelloV2();
162 default:
163 if (!Enum.IsDefined(typeof(ContentType), contentType))
165 throw new TlsException(AlertDescription.DecodeError);
167 return this.ReadStandardRecordBuffer();
171 private byte[] ReadClientHelloV2()
173 short protocol = this.ReadShort();
174 short cipherSpecLength = this.ReadShort();
175 short sessionIdLength = this.ReadShort();
176 short challengeLength = this.ReadShort();
177 short length = (challengeLength > 32) ? (short)32 : challengeLength;
179 // Read CipherSpecs
180 byte[] cipherSpecV2 = new byte[cipherSpecLength];
181 this.innerStream.Read(cipherSpecV2, 0, cipherSpecV2.Length);
183 // Read session ID
184 byte[] sessionId = new byte[sessionIdLength];
185 this.innerStream.Read(sessionId, 0, sessionId.Length);
187 // Read challenge ID
188 byte[] challenge = new byte[challengeLength];
189 this.innerStream.Read(challenge, 0, challenge.Length);
191 // Check that the message has a valid protocol version
192 SecurityProtocolType protocolType = this.context.DecodeProtocolCode(protocol);
193 if (protocolType != SecurityProtocolType.Ssl3 && protocolType != SecurityProtocolType.Tls)
195 throw new TlsException(
196 AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
199 if (challengeLength < 16 || cipherSpecLength == 0 || (cipherSpecLength % 3) != 0)
201 throw new TlsException(AlertDescription.DecodeError);
204 // Updated the Session ID
205 if (sessionId.Length > 0)
207 this.context.SessionId = sessionId;
210 // Select the cipher suite collection
211 this.Context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(protocolType);
213 // Select the Cipher suite
214 this.ProcessCipherSpecV2Buffer(protocolType, cipherSpecV2);
216 // Updated the Client Random
217 this.context.ClientRandom = new byte[32];
218 Buffer.BlockCopy(challenge, 0, this.context.ClientRandom, 0, length);
220 return new byte[0];
223 private byte[] ReadStandardRecordBuffer()
225 short protocol = this.ReadShort();
226 short length = this.ReadShort();
228 // Read Record data
229 int received = 0;
230 byte[] buffer = new byte[length];
231 while (received != length)
233 received += this.innerStream.Read(buffer, received, buffer.Length - received);
236 // Check that the message has a valid protocol version
237 if (protocol != this.context.Protocol && this.context.ProtocolNegotiated)
239 throw new TlsException(
240 AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
243 DebugHelper.WriteLine("Record data", buffer);
245 return buffer;
248 private short ReadShort()
250 byte[] b = new byte[2];
251 this.innerStream.Read(b, 0, b.Length);
253 short val = BitConverter.ToInt16(b, 0);
255 return System.Net.IPAddress.HostToNetworkOrder(val);
258 private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
260 switch (alertLevel)
262 case AlertLevel.Fatal:
263 throw new TlsException(alertLevel, alertDesc);
265 case AlertLevel.Warning:
266 default:
267 switch (alertDesc)
269 case AlertDescription.CloseNotify:
270 this.context.ConnectionEnd = true;
271 break;
273 break;
277 #endregion
279 #region Send Alert Methods
281 public void SendAlert(AlertDescription description)
283 this.SendAlert(new Alert(description));
286 public void SendAlert(
287 AlertLevel level,
288 AlertDescription description)
290 this.SendAlert(new Alert(level, description));
293 public void SendAlert(Alert alert)
295 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
297 // Write record
298 this.SendRecord(
299 ContentType.Alert,
300 new byte[]{(byte)alert.Level, (byte)alert.Description});
302 if (alert.IsCloseNotify)
304 this.context.ConnectionEnd = true;
308 #endregion
310 #region Send Record Methods
312 public void SendChangeCipherSpec()
314 DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
316 // Send Change Cipher Spec message as a plain message
317 this.context.IsActual = false;
319 // Send Change Cipher Spec message
320 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
322 // Reset sequence numbers
323 this.context.WriteSequenceNumber = 0;
325 // Make the pending state to be the current state
326 this.context.IsActual = true;
328 // Send Finished message
329 this.SendRecord(HandshakeType.Finished);
332 public void SendRecord(ContentType contentType, byte[] recordData)
334 if (this.context.ConnectionEnd)
336 throw new TlsException(
337 AlertDescription.InternalError,
338 "The session is finished and it's no longer valid.");
341 byte[] record = this.EncodeRecord(contentType, recordData);
343 this.innerStream.Write(record, 0, record.Length);
346 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
348 return this.EncodeRecord(
349 contentType,
350 recordData,
352 recordData.Length);
355 public byte[] EncodeRecord(
356 ContentType contentType,
357 byte[] recordData,
358 int offset,
359 int count)
361 if (this.context.ConnectionEnd)
363 throw new TlsException(
364 AlertDescription.InternalError,
365 "The session is finished and it's no longer valid.");
368 TlsStream record = new TlsStream();
370 int position = offset;
372 while (position < ( offset + count ))
374 short fragmentLength = 0;
375 byte[] fragment;
377 if ((count - position) > Context.MAX_FRAGMENT_SIZE)
379 fragmentLength = Context.MAX_FRAGMENT_SIZE;
381 else
383 fragmentLength = (short)(count - position);
386 // Fill the fragment data
387 fragment = new byte[fragmentLength];
388 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
390 if (this.context.IsActual)
392 // Encrypt fragment
393 fragment = this.encryptRecordFragment(contentType, fragment);
396 // Write tls message
397 record.Write((byte)contentType);
398 record.Write(this.context.Protocol);
399 record.Write((short)fragment.Length);
400 record.Write(fragment);
402 DebugHelper.WriteLine("Record data", fragment);
404 // Update buffer position
405 position += fragmentLength;
408 return record.ToArray();
411 #endregion
413 #region Cryptography Methods
415 private byte[] encryptRecordFragment(
416 ContentType contentType,
417 byte[] fragment)
419 byte[] mac = null;
421 // Calculate message MAC
422 if (this.Context is ClientContext)
424 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
426 else
428 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, fragment);
431 DebugHelper.WriteLine(">>>> Record MAC", mac);
433 // Encrypt the message
434 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
436 // Set new Client Cipher IV
437 if (this.context.Cipher.CipherMode == CipherMode.CBC)
439 byte[] iv = new byte[this.context.Cipher.IvSize];
440 Buffer.BlockCopy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
442 this.context.Cipher.UpdateClientCipherIV(iv);
445 // Update sequence number
446 this.context.WriteSequenceNumber++;
448 return ecr;
451 private TlsStream decryptRecordFragment(
452 ContentType contentType,
453 byte[] fragment)
455 byte[] dcrFragment = null;
456 byte[] dcrMAC = null;
457 bool badRecordMac = false;
461 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
463 catch
465 if (this.context is ServerContext)
467 this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
470 throw;
473 // Generate record MAC
474 byte[] mac = null;
476 if (this.Context is ClientContext)
478 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
480 else
482 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, dcrFragment);
485 DebugHelper.WriteLine(">>>> Record MAC", mac);
487 // Check record MAC
488 if (mac.Length != dcrMAC.Length)
490 badRecordMac = true;
492 else
494 for (int i = 0; i < mac.Length; i++)
496 if (mac[i] != dcrMAC[i])
498 badRecordMac = true;
499 break;
504 if (badRecordMac)
506 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
509 // Update sequence number
510 this.context.ReadSequenceNumber++;
512 return new TlsStream(dcrFragment);
515 #endregion
517 #region CipherSpecV2 processing
519 private void ProcessCipherSpecV2Buffer(SecurityProtocolType protocol, byte[] buffer)
521 TlsStream codes = new TlsStream(buffer);
523 string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
525 while (codes.Position < codes.Length)
527 byte check = codes.ReadByte();
529 if (check == 0)
531 // SSL/TLS cipher spec
532 int index = 0;
533 short code = codes.ReadInt16();
534 if ((index = this.Context.SupportedCiphers.IndexOf(code)) != -1)
536 this.Context.Cipher = this.Context.SupportedCiphers[index];
537 break;
540 else
542 byte[] tmp = new byte[2];
543 codes.Read(tmp, 0, tmp.Length);
545 int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
546 CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
548 if (cipher != null)
550 this.Context.Cipher = cipher;
551 break;
556 if (this.Context.Cipher == null)
558 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
562 private CipherSuite MapV2CipherCode(string prefix, int code)
566 switch (code)
568 case 65664:
569 // TLS_RC4_128_WITH_MD5
570 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
572 case 131200:
573 // TLS_RC4_128_EXPORT40_WITH_MD5
574 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
576 case 196736:
577 // TLS_RC2_CBC_128_CBC_WITH_MD5
578 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
580 case 262272:
581 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
582 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
584 case 327808:
585 // TLS_IDEA_128_CBC_WITH_MD5
586 return null;
588 case 393280:
589 // TLS_DES_64_CBC_WITH_MD5
590 return null;
592 case 458944:
593 // TLS_DES_192_EDE3_CBC_WITH_MD5
594 return null;
596 default:
597 return null;
600 catch
602 return null;
606 #endregion