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()
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();
91 ContentType contentType = (ContentType)type;
92 byte[] buffer = this.ReadRecordBuffer(type);
94 TlsStream message = new TlsStream(buffer);
96 // Decrypt message contents if needed
97 if (contentType == ContentType.Alert && buffer.Length == 2)
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;
114 byte[] result = message.ToArray();
118 case ContentType.Alert:
119 this.ProcessAlert((AlertLevel)message.ReadByte(), (AlertDescription)message.ReadByte());
123 case ContentType.ChangeCipherSpec:
124 this.ProcessChangeCipherSpec();
127 case ContentType.ApplicationData:
130 case ContentType.Handshake:
133 this.ProcessHandshakeMessage(message);
136 // Update handshakes of current messages
137 this.context.HandshakeMessages.Write(message.ToArray());
140 // FIXME / MCS bug - http://bugzilla.ximian.com/show_bug.cgi?id=67711
141 // case (ContentType)0x80:
142 // this.context.HandshakeMessages.Write (result);
146 if (contentType != (ContentType)0x80)
148 throw new TlsException(
149 AlertDescription.UnexpectedMessage,
150 "Unknown record received from server.");
152 this.context.HandshakeMessages.Write (result);
159 private byte[] ReadRecordBuffer(int contentType)
164 return this.ReadClientHelloV2();
167 if (!Enum.IsDefined(typeof(ContentType), (ContentType)contentType))
169 throw new TlsException(AlertDescription.DecodeError);
171 return this.ReadStandardRecordBuffer();
175 private byte[] ReadClientHelloV2()
177 int msgLength = this.innerStream.ReadByte();
178 byte[] message = new byte [msgLength];
179 this.innerStream.Read (message, 0, msgLength);
181 int msgType = message [0];
184 throw new TlsException(AlertDescription.DecodeError);
186 int protocol = (message [1] << 8 | message [2]);
187 int cipherSpecLength = (message [3] << 8 | message [4]);
188 int sessionIdLength = (message [5] << 8 | message [6]);
189 int challengeLength = (message [7] << 8 | message [8]);
190 int length = (challengeLength > 32) ? 32 : challengeLength;
193 byte[] cipherSpecV2 = new byte[cipherSpecLength];
194 Buffer.BlockCopy (message, 9, cipherSpecV2, 0, cipherSpecLength);
197 byte[] sessionId = new byte[sessionIdLength];
198 Buffer.BlockCopy (message, 9 + cipherSpecLength, sessionId, 0, sessionIdLength);
201 byte[] challenge = new byte[challengeLength];
202 Buffer.BlockCopy (message, 9 + cipherSpecLength + sessionIdLength, challenge, 0, challengeLength);
204 if (challengeLength < 16 || cipherSpecLength == 0 || (cipherSpecLength % 3) != 0)
206 throw new TlsException(AlertDescription.DecodeError);
209 // Updated the Session ID
210 if (sessionId.Length > 0)
212 this.context.SessionId = sessionId;
215 // Update the protocol version
216 this.Context.ChangeProtocol((short)protocol);
218 // Select the Cipher suite
219 this.ProcessCipherSpecV2Buffer(this.Context.SecurityProtocol, cipherSpecV2);
221 // Updated the Client Random
\r
222 this.context.ClientRandom = new byte [32]; // Always 32
\r
223 // 1. if challenge is bigger than 32 bytes only use the last 32 bytes
\r
224 // 2. right justify (0) challenge in ClientRandom if less than 32
\r
225 Buffer.BlockCopy (challenge, challenge.Length - length, this.context.ClientRandom, 32 - length, length);
\r
228 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
229 this.context.ProtocolNegotiated = true;
234 private byte[] ReadStandardRecordBuffer()
236 short protocol = this.ReadShort();
237 short length = this.ReadShort();
241 byte[] buffer = new byte[length];
242 while (received != length)
244 received += this.innerStream.Read(buffer, received, buffer.Length - received);
247 // Check that the message has a valid protocol version
248 if (protocol != this.context.Protocol && this.context.ProtocolNegotiated)
250 throw new TlsException(
251 AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
254 DebugHelper.WriteLine("Record data", buffer);
259 private short ReadShort()
261 byte[] b = new byte[2];
262 this.innerStream.Read(b, 0, b.Length);
264 short val = BitConverter.ToInt16(b, 0);
266 return System.Net.IPAddress.HostToNetworkOrder(val);
269 private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
273 case AlertLevel.Fatal:
274 throw new TlsException(alertLevel, alertDesc);
276 case AlertLevel.Warning:
280 case AlertDescription.CloseNotify:
281 this.context.ConnectionEnd = true;
290 #region Send Alert Methods
292 public void SendAlert(AlertDescription description)
294 this.SendAlert(new Alert(description));
297 public void SendAlert(
299 AlertDescription description)
301 this.SendAlert(new Alert(level, description));
304 public void SendAlert(Alert alert)
306 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
311 new byte[]{(byte)alert.Level, (byte)alert.Description});
313 if (alert.IsCloseNotify)
315 this.context.ConnectionEnd = true;
321 #region Send Record Methods
323 public void SendChangeCipherSpec()
325 DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
327 // Send Change Cipher Spec message as a plain message
328 this.context.IsActual = false;
330 // Send Change Cipher Spec message
331 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
333 // Reset sequence numbers
334 this.context.WriteSequenceNumber = 0;
336 // Make the pending state to be the current state
337 this.context.IsActual = true;
339 // Send Finished message
340 this.SendRecord(HandshakeType.Finished);
343 public void SendRecord(ContentType contentType, byte[] recordData)
345 if (this.context.ConnectionEnd)
347 throw new TlsException(
348 AlertDescription.InternalError,
349 "The session is finished and it's no longer valid.");
352 byte[] record = this.EncodeRecord(contentType, recordData);
354 this.innerStream.Write(record, 0, record.Length);
357 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
359 return this.EncodeRecord(
366 public byte[] EncodeRecord(
367 ContentType contentType,
372 if (this.context.ConnectionEnd)
374 throw new TlsException(
375 AlertDescription.InternalError,
376 "The session is finished and it's no longer valid.");
379 TlsStream record = new TlsStream();
381 int position = offset;
383 while (position < ( offset + count ))
385 short fragmentLength = 0;
388 if ((count - position) > Context.MAX_FRAGMENT_SIZE)
390 fragmentLength = Context.MAX_FRAGMENT_SIZE;
394 fragmentLength = (short)(count - position);
397 // Fill the fragment data
398 fragment = new byte[fragmentLength];
399 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
401 if (this.context.IsActual)
404 fragment = this.encryptRecordFragment(contentType, fragment);
408 record.Write((byte)contentType);
409 record.Write(this.context.Protocol);
410 record.Write((short)fragment.Length);
411 record.Write(fragment);
413 DebugHelper.WriteLine("Record data", fragment);
415 // Update buffer position
416 position += fragmentLength;
419 return record.ToArray();
424 #region Cryptography Methods
426 private byte[] encryptRecordFragment(
427 ContentType contentType,
432 // Calculate message MAC
433 if (this.Context is ClientContext)
435 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
439 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, fragment);
442 DebugHelper.WriteLine(">>>> Record MAC", mac);
444 // Encrypt the message
445 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
447 // Set new Client Cipher IV
448 if (this.context.Cipher.CipherMode == CipherMode.CBC)
450 byte[] iv = new byte[this.context.Cipher.IvSize];
451 Buffer.BlockCopy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
453 this.context.Cipher.UpdateClientCipherIV(iv);
456 // Update sequence number
457 this.context.WriteSequenceNumber++;
462 private TlsStream decryptRecordFragment(
463 ContentType contentType,
466 byte[] dcrFragment = null;
467 byte[] dcrMAC = null;
468 bool badRecordMac = false;
472 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
476 if (this.context is ServerContext)
478 this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
484 // Generate record MAC
487 if (this.Context is ClientContext)
489 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
493 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, dcrFragment);
496 DebugHelper.WriteLine(">>>> Record MAC", mac);
499 if (mac.Length != dcrMAC.Length)
505 for (int i = 0; i < mac.Length; i++)
507 if (mac[i] != dcrMAC[i])
517 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
520 // Update sequence number
521 this.context.ReadSequenceNumber++;
523 return new TlsStream(dcrFragment);
528 #region CipherSpecV2 processing
530 private void ProcessCipherSpecV2Buffer(SecurityProtocolType protocol, byte[] buffer)
532 TlsStream codes = new TlsStream(buffer);
534 string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
536 while (codes.Position < codes.Length)
538 byte check = codes.ReadByte();
542 // SSL/TLS cipher spec
544 short code = codes.ReadInt16();
545 if ((index = this.Context.SupportedCiphers.IndexOf(code)) != -1)
547 this.Context.Cipher = this.Context.SupportedCiphers[index];
553 byte[] tmp = new byte[2];
554 codes.Read(tmp, 0, tmp.Length);
556 int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
557 CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
561 this.Context.Cipher = cipher;
567 if (this.Context.Cipher == null)
569 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
573 private CipherSuite MapV2CipherCode(string prefix, int code)
580 // TLS_RC4_128_WITH_MD5
581 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
584 // TLS_RC4_128_EXPORT40_WITH_MD5
585 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
588 // TLS_RC2_CBC_128_CBC_WITH_MD5
589 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
592 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
593 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
596 // TLS_IDEA_128_CBC_WITH_MD5
600 // TLS_DES_64_CBC_WITH_MD5
604 // TLS_DES_192_EDE3_CBC_WITH_MD5