1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
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:
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
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.
26 using System.Collections;
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
39 protected Stream innerStream;
40 protected Context context;
46 public Context Context
48 get { return this.context; }
49 set { this.context = value; }
56 public RecordProtocol(Stream innerStream, Context context)
58 this.innerStream = innerStream;
59 this.context = context;
60 this.context.RecordProtocol = this;
65 #region Abstract Methods
67 public abstract void SendRecord(HandshakeType type);
68 protected abstract void ProcessHandshakeMessage(TlsStream handMsg);
69 protected abstract void ProcessChangeCipherSpec();
73 #region Reveive Record Methods
75 public byte[] ReceiveRecord(Stream record)
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
\r
85 int type = record.ReadByte ();
91 // Set last handshake message received to None
92 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
94 ContentType contentType = (ContentType)type;
95 byte[] buffer = this.ReadRecordBuffer(type, record);
\r
98 // record incomplete (at the moment)
\r
102 // Decrypt message contents if needed
103 if (contentType == ContentType.Alert && buffer.Length == 2)
108 if (this.context.IsActual && contentType != ContentType.ChangeCipherSpec)
110 buffer = this.decryptRecordFragment(contentType, buffer);
\r
111 DebugHelper.WriteLine("Decrypted record data", buffer);
118 case ContentType.Alert:
119 this.ProcessAlert((AlertLevel)buffer [0], (AlertDescription)buffer [1]);
\r
122 // don't reprocess that memory block
\r
123 record.SetLength (0);
128 case ContentType.ChangeCipherSpec:
129 this.ProcessChangeCipherSpec();
132 case ContentType.ApplicationData:
135 case ContentType.Handshake:
\r
136 TlsStream message = new TlsStream (buffer);
139 this.ProcessHandshakeMessage(message);
142 // Update handshakes of current messages
143 this.context.HandshakeMessages.Write(buffer);
146 // FIXME / MCS bug - http://bugzilla.ximian.com/show_bug.cgi?id=67711
147 // case (ContentType)0x80:
148 // this.context.HandshakeMessages.Write (result);
152 if (contentType != (ContentType)0x80)
154 throw new TlsException(
155 AlertDescription.UnexpectedMessage,
156 "Unknown record received from server.");
158 this.context.HandshakeMessages.Write (buffer);
165 private byte[] ReadRecordBuffer (int contentType, Stream record)
170 return this.ReadClientHelloV2(record);
173 if (!Enum.IsDefined(typeof(ContentType), (ContentType)contentType))
175 throw new TlsException(AlertDescription.DecodeError);
177 return this.ReadStandardRecordBuffer(record);
181 private byte[] ReadClientHelloV2 (Stream record)
183 int msgLength = record.ReadByte ();
\r
184 // process further only if the whole record is available
\r
185 if (record.CanSeek && (msgLength + 1 > record.Length))
190 byte[] message = new byte[msgLength];
\r
191 record.Read (message, 0, msgLength);
193 int msgType = message [0];
196 throw new TlsException(AlertDescription.DecodeError);
198 int protocol = (message [1] << 8 | message [2]);
199 int cipherSpecLength = (message [3] << 8 | message [4]);
200 int sessionIdLength = (message [5] << 8 | message [6]);
201 int challengeLength = (message [7] << 8 | message [8]);
202 int length = (challengeLength > 32) ? 32 : challengeLength;
205 byte[] cipherSpecV2 = new byte[cipherSpecLength];
206 Buffer.BlockCopy (message, 9, cipherSpecV2, 0, cipherSpecLength);
209 byte[] sessionId = new byte[sessionIdLength];
210 Buffer.BlockCopy (message, 9 + cipherSpecLength, sessionId, 0, sessionIdLength);
213 byte[] challenge = new byte[challengeLength];
214 Buffer.BlockCopy (message, 9 + cipherSpecLength + sessionIdLength, challenge, 0, challengeLength);
216 if (challengeLength < 16 || cipherSpecLength == 0 || (cipherSpecLength % 3) != 0)
218 throw new TlsException(AlertDescription.DecodeError);
221 // Updated the Session ID
222 if (sessionId.Length > 0)
224 this.context.SessionId = sessionId;
227 // Update the protocol version
228 this.Context.ChangeProtocol((short)protocol);
230 // Select the Cipher suite
231 this.ProcessCipherSpecV2Buffer(this.Context.SecurityProtocol, cipherSpecV2);
233 // Updated the Client Random
\r
234 this.context.ClientRandom = new byte [32]; // Always 32
\r
235 // 1. if challenge is bigger than 32 bytes only use the last 32 bytes
\r
236 // 2. right justify (0) challenge in ClientRandom if less than 32
\r
237 Buffer.BlockCopy (challenge, challenge.Length - length, this.context.ClientRandom, 32 - length, length);
\r
240 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
241 this.context.ProtocolNegotiated = true;
246 private byte[] ReadStandardRecordBuffer (Stream record)
248 short protocol = this.ReadShort(record);
249 short length = this.ReadShort(record);
251 // process further only if the whole record is available
\r
252 // note: the first 5 bytes aren't part of the length
\r
253 if (record.CanSeek && (length + 5 > record.Length))
260 byte[] buffer = new byte[length];
261 while (received != length)
263 received += record.Read(buffer, received, buffer.Length - received);
266 // Check that the message has a valid protocol version
267 if (protocol != this.context.Protocol && this.context.ProtocolNegotiated)
269 throw new TlsException(
270 AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
273 DebugHelper.WriteLine("Record data", buffer);
278 private short ReadShort(Stream record)
280 byte[] b = new byte[2];
281 record.Read(b, 0, b.Length);
283 short val = BitConverter.ToInt16(b, 0);
285 return System.Net.IPAddress.HostToNetworkOrder(val);
288 private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
292 case AlertLevel.Fatal:
293 throw new TlsException(alertLevel, alertDesc);
295 case AlertLevel.Warning:
299 case AlertDescription.CloseNotify:
300 this.context.ConnectionEnd = true;
309 #region Send Alert Methods
311 public void SendAlert(AlertDescription description)
313 this.SendAlert(new Alert(description));
316 public void SendAlert(
318 AlertDescription description)
320 this.SendAlert(new Alert(level, description));
323 public void SendAlert(Alert alert)
325 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
330 new byte[]{(byte)alert.Level, (byte)alert.Description});
332 if (alert.IsCloseNotify)
334 this.context.ConnectionEnd = true;
340 #region Send Record Methods
342 public void SendChangeCipherSpec()
344 DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
346 // Send Change Cipher Spec message as a plain message
347 this.context.IsActual = false;
349 // Send Change Cipher Spec message
350 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
352 // Reset sequence numbers
353 this.context.WriteSequenceNumber = 0;
355 // Make the pending state to be the current state
356 this.context.IsActual = true;
358 // Send Finished message
359 this.SendRecord(HandshakeType.Finished);
362 public void SendRecord(ContentType contentType, byte[] recordData)
364 if (this.context.ConnectionEnd)
366 throw new TlsException(
367 AlertDescription.InternalError,
368 "The session is finished and it's no longer valid.");
371 byte[] record = this.EncodeRecord(contentType, recordData);
373 this.innerStream.Write(record, 0, record.Length);
376 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
378 return this.EncodeRecord(
385 public byte[] EncodeRecord(
386 ContentType contentType,
391 if (this.context.ConnectionEnd)
393 throw new TlsException(
394 AlertDescription.InternalError,
395 "The session is finished and it's no longer valid.");
398 TlsStream record = new TlsStream();
400 int position = offset;
402 while (position < ( offset + count ))
404 short fragmentLength = 0;
407 if ((count - position) > Context.MAX_FRAGMENT_SIZE)
409 fragmentLength = Context.MAX_FRAGMENT_SIZE;
413 fragmentLength = (short)(count - position);
416 // Fill the fragment data
417 fragment = new byte[fragmentLength];
418 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
420 if (this.context.IsActual)
423 fragment = this.encryptRecordFragment(contentType, fragment);
427 record.Write((byte)contentType);
428 record.Write(this.context.Protocol);
429 record.Write((short)fragment.Length);
430 record.Write(fragment);
432 DebugHelper.WriteLine("Record data", fragment);
434 // Update buffer position
435 position += fragmentLength;
438 return record.ToArray();
443 #region Cryptography Methods
445 private byte[] encryptRecordFragment(
446 ContentType contentType,
451 // Calculate message MAC
452 if (this.Context is ClientContext)
454 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
458 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, fragment);
461 DebugHelper.WriteLine(">>>> Record MAC", mac);
463 // Encrypt the message
464 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
466 // Set new Client Cipher IV
467 if (this.context.Cipher.CipherMode == CipherMode.CBC)
469 byte[] iv = new byte[this.context.Cipher.IvSize];
470 Buffer.BlockCopy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
472 this.context.Cipher.UpdateClientCipherIV(iv);
475 // Update sequence number
476 this.context.WriteSequenceNumber++;
481 private byte[] decryptRecordFragment(
482 ContentType contentType,
485 byte[] dcrFragment = null;
486 byte[] dcrMAC = null;
487 bool badRecordMac = false;
491 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
495 if (this.context is ServerContext)
497 this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
503 // Generate record MAC
506 if (this.Context is ClientContext)
508 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
512 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, dcrFragment);
515 DebugHelper.WriteLine(">>>> Record MAC", mac);
518 if (mac.Length != dcrMAC.Length)
524 for (int i = 0; i < mac.Length; i++)
526 if (mac[i] != dcrMAC[i])
536 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
539 // Update sequence number
540 this.context.ReadSequenceNumber++;
547 #region CipherSpecV2 processing
549 private void ProcessCipherSpecV2Buffer(SecurityProtocolType protocol, byte[] buffer)
551 TlsStream codes = new TlsStream(buffer);
553 string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
555 while (codes.Position < codes.Length)
557 byte check = codes.ReadByte();
561 // SSL/TLS cipher spec
562 short code = codes.ReadInt16();
563 int index = this.Context.SupportedCiphers.IndexOf(code);
566 this.Context.Cipher = this.Context.SupportedCiphers[index];
572 byte[] tmp = new byte[2];
573 codes.Read(tmp, 0, tmp.Length);
575 int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
576 CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
580 this.Context.Cipher = cipher;
586 if (this.Context.Cipher == null)
588 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
592 private CipherSuite MapV2CipherCode(string prefix, int code)
599 // TLS_RC4_128_WITH_MD5
600 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
603 // TLS_RC4_128_EXPORT40_WITH_MD5
604 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
607 // TLS_RC2_CBC_128_CBC_WITH_MD5
608 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
611 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
612 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
615 // TLS_IDEA_128_CBC_WITH_MD5
619 // TLS_DES_64_CBC_WITH_MD5
623 // TLS_DES_192_EDE3_CBC_WITH_MD5