1 /* Transport Security Layer (TLS)
2 * Copyright (c) 2003 Carlos Guzmán Álvarez
4 * Permission is hereby granted, free of charge, to any person
5 * obtaining a copy of this software and associated documentation
6 * files (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
28 using System.Collections;
29 using System.Net.Sockets;
30 using System.Security.Cryptography;
32 using Mono.Security.Protocol.Tls;
33 using Mono.Security.Protocol.Tls.Alerts;
34 using Mono.Security.Protocol.Tls.Handshake;
35 using Mono.Security.Protocol.Tls.Handshake.Client;
37 namespace Mono.Security.Protocol.Tls
39 public sealed class TlsSocket : Socket
43 private TlsSession session;
44 private BufferedStream inputBuffer;
50 internal TlsSession Session
52 get { return this.session; }
55 internal BufferedStream InputBuffer
57 get { return inputBuffer; }
65 AddressFamily addressFamily,
66 SocketType socketType,
67 ProtocolType protocolType
68 ) : base(addressFamily, socketType, protocolType)
70 this.inputBuffer = new BufferedStream(new MemoryStream());
75 AddressFamily addressFamily,
76 SocketType socketType,
77 ProtocolType protocolType
78 ) : this(addressFamily, socketType, protocolType)
80 this.session = session;
85 #region REPLACED_METHODS
87 public new void Close()
91 if (this.session.State != TlsSessionState.Closing &&
92 this.session.State != TlsSessionState.Closed)
98 public new int Receive(byte[] buffer)
100 return this.Receive(buffer, 0, buffer != null ? buffer.Length : 0, SocketFlags.None);
103 public new int Receive(byte[] buffer, SocketFlags socketFlags)
105 return this.Receive(buffer, 0, buffer != null ? buffer.Length : 0, socketFlags);
108 public new int Receive(byte[] buffer, int size, SocketFlags socketFlags)
110 return this.Receive(buffer, 0, size, socketFlags);
113 public new int Receive(byte[] buffer, int offset, int size, SocketFlags socketFlags)
115 if (!session.IsSecure)
117 return base.Receive(buffer, offset, size, socketFlags);
120 // If actual buffer is full readed reset it
121 if (inputBuffer.Position == inputBuffer.Length)
126 // Check if we have space in the middle buffer
127 // if not Read next TLS record and update the inputBuffer
128 while ((inputBuffer.Length - inputBuffer.Position) < size)
130 // Read next record and write it into the inputBuffer
131 long position = inputBuffer.Position;
132 byte[] record = this.receiveRecord();
134 if (record.Length > 0)
136 // Write new data to the inputBuffer
137 inputBuffer.Seek(0, SeekOrigin.End);
138 inputBuffer.Write(record, 0, record.Length);
140 // Restore buffer position
141 inputBuffer.Seek(position, SeekOrigin.Begin);
144 if (base.Available == 0)
150 return inputBuffer.Read(buffer, offset, size);
153 public new int Send(byte[] buffer)
155 return this.Send(buffer, 0, buffer != null ? buffer.Length : 0, SocketFlags.None);
158 public new int Send(byte[] buffer, SocketFlags socketFlags)
160 return this.Send(buffer, 0, buffer != null ? buffer.Length : 0, socketFlags);
163 public new int Send(byte[] buffer, int size, SocketFlags socketFlags)
165 return this.Send(buffer, 0, size, socketFlags);
168 public new int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags)
170 if (!session.IsSecure)
172 return base.Send(buffer, offset, size, socketFlags);
175 // Send the buffer as a TLS record
176 byte[] recordData = new byte[size];
177 System.Array.Copy(buffer, offset, recordData, 0, size);
179 return this.sendRecord(TlsContentType.ApplicationData, recordData);
184 #region TLS_RECORD_METHODS
186 private byte[] receiveRecord()
188 if (session.Context.ConnectionEnd)
190 throw session.CreateException("The session is finished and it's no longer valid.");
193 TlsContentType contentType = (TlsContentType)this.ReadByte();
194 TlsProtocol protocol = (TlsProtocol)this.ReadShort();
195 int length = this.ReadShort();
199 byte[] buffer = new byte[length];
200 while (received != length)
202 received += base.Receive(
203 buffer, received, buffer.Length - received, SocketFlags.None);
206 TlsStream message = new TlsStream(buffer);
208 // Check that the message as a valid protocol version
209 if (protocol != session.Context.Protocol)
211 throw session.CreateException("Invalid protocol version on message received from server");
214 // Decrypt message contents if needed
215 if (contentType == TlsContentType.Alert &&
221 if (session.Context.IsActual &&
222 contentType != TlsContentType.ChangeCipherSpec)
224 message = decryptRecordFragment(
231 byte[] result = message.ToArray();
236 case TlsContentType.Alert:
237 processAlert((TlsAlertLevel)message.ReadByte(),
238 (TlsAlertDescription)message.ReadByte());
241 case TlsContentType.ChangeCipherSpec:
242 // Reset sequence numbers
243 session.Context.ReadSequenceNumber = 0;
246 case TlsContentType.ApplicationData:
249 case TlsContentType.Handshake:
252 processHandshakeMessage(message);
254 // Update handshakes of current messages
255 this.session.Context.HandshakeHashes.Update(message.ToArray());
259 throw session.CreateException("Unknown record received from server.");
267 #region TLS_CRYPTO_METHODS
269 private byte[] encryptRecordFragment(TlsContentType contentType, byte[] fragment)
271 // Calculate message MAC
272 byte[] mac = encodeClientRecordMAC(contentType, fragment);
274 // Encrypt the message
275 byte[] ecr = session.Context.Cipher.EncryptRecord(fragment, mac);
278 if (session.Context.Cipher.CipherMode == CipherMode.CBC)
280 byte[] iv = new byte[session.Context.Cipher.IvSize];
281 System.Array.Copy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
282 session.Context.Cipher.UpdateClientCipherIV(iv);
285 // Update sequence number
286 session.Context.WriteSequenceNumber++;
291 private TlsStream decryptRecordFragment(TlsContentType contentType,
292 TlsProtocol protocol,
295 byte[] dcrFragment = null;
296 byte[] dcrMAC = null;
299 session.Context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
302 if (session.Context.Cipher.CipherMode == CipherMode.CBC)
304 byte[] iv = new byte[session.Context.Cipher.IvSize];
305 System.Array.Copy(fragment, fragment.Length - iv.Length, iv, 0, iv.Length);
306 session.Context.Cipher.UpdateServerCipherIV(iv);
310 byte[] mac = this.encodeServerRecordMAC(contentType, dcrFragment);
312 // Check that the mac is correct
313 if (mac.Length != dcrMAC.Length)
315 throw new TlsException("Invalid MAC received from server.");
317 for (int i = 0; i < mac.Length; i++)
319 if (mac[i] != dcrMAC[i])
321 throw new TlsException("Invalid MAC received from server.");
325 // Update sequence number
326 session.Context.ReadSequenceNumber++;
328 return new TlsStream(dcrFragment);
333 #region TLS_SEND_METHODS
335 internal int SendAlert(TlsAlert alert)
338 int bytesSent = this.sendRecord(TlsContentType.Alert, alert.ToArray());
341 alert.UpdateSession();
343 // Reset message contents
349 private int sendRecord(TlsHandshakeType type)
351 TlsHandshakeMessage msg = createClientHandshakeMessage(type);
354 int bytesSent = this.sendRecord(msg.ContentType, msg.EncodeMessage());
359 // Reset message contents
365 private int sendChangeCipherSpec()
367 // Send Change Cipher Spec message
368 int bytesSent = this.sendRecord(TlsContentType.ChangeCipherSpec, new byte[] {1});
370 // Reset sequence numbers
371 session.Context.WriteSequenceNumber = 0;
373 // Make the pending state to be the current state
374 session.Context.IsActual = true;
376 // Send Finished message
377 bytesSent += this.sendRecord(TlsHandshakeType.Finished);
382 private int sendRecord(TlsContentType contentType, byte[] recordData)
384 if (session.Context.ConnectionEnd)
386 throw session.CreateException("The session is finished and it's no longer valid.");
390 byte[][] fragments = fragmentData(recordData);
391 for (int i = 0; i < fragments.Length; i++)
393 byte[] fragment = fragments[i];
395 if (session.Context.IsActual)
398 fragment = encryptRecordFragment(contentType, fragment);
402 TlsStream record = new TlsStream();
403 record.Write((byte)contentType);
404 record.Write((short)TlsProtocol.Tls1);
405 record.Write((short)fragment.Length);
406 record.Write(fragment);
409 bytesSent += base.Send(record.ToArray());
418 private byte[][] fragmentData(byte[] messageData)
420 ArrayList d = new ArrayList();
424 while (position < messageData.Length)
426 short fragmentLength = 0;
428 if ((messageData.Length - position) > session.MaxFragmentSize)
430 fragmentLength = session.MaxFragmentSize;
434 fragmentLength = (short)(messageData.Length - position);
436 fragmentData = new byte[fragmentLength];
438 System.Array.Copy(messageData, position, fragmentData, 0, fragmentLength);
442 position += fragmentLength;
445 byte[][] result = new byte[d.Count][];
446 for (int i = 0; i < d.Count; i++)
448 result[i] = (byte[])d[i];
456 #region MESSAGE_PROCESSING
458 private void processHandshakeMessage(TlsStream handMsg)
460 TlsHandshakeType handshakeType = (TlsHandshakeType)handMsg.ReadByte();
461 TlsHandshakeMessage message = null;
463 // Read message length
464 int length = handMsg.ReadInt24();
467 byte[] data = new byte[length];
468 handMsg.Read(data, 0, length);
470 // Create and process the server message
471 message = createServerHandshakeMessage(handshakeType, data);
476 message.UpdateSession();
480 private void processAlert(TlsAlertLevel alertLevel,
481 TlsAlertDescription alertDesc)
485 case TlsAlertLevel.Fatal:
486 throw session.CreateException(alertLevel, alertDesc);
488 case TlsAlertLevel.Warning:
492 case TlsAlertDescription.CloseNotify:
493 session.Context.ConnectionEnd = true;
497 session.RaiseWarningAlert(alertLevel, alertDesc);
508 private void resetBuffer()
510 this.inputBuffer.SetLength(0);
511 this.inputBuffer.Position = 0;
514 private byte[] encodeServerRecordMAC(TlsContentType contentType, byte[] fragment)
516 TlsStream data = new TlsStream();
517 byte[] result = null;
519 data.Write(session.Context.ReadSequenceNumber);
520 data.Write((byte)contentType);
521 data.Write((short)TlsProtocol.Tls1);
522 data.Write((short)fragment.Length);
523 data.Write(fragment);
525 result = session.Context.Cipher.ServerHMAC.ComputeHash(data.ToArray());
532 private byte[] encodeClientRecordMAC(TlsContentType contentType, byte[] fragment)
534 TlsStream data = new TlsStream();
535 byte[] result = null;
537 data.Write(session.Context.WriteSequenceNumber);
538 data.Write((byte)contentType);
539 data.Write((short)TlsProtocol.Tls1);
540 data.Write((short)fragment.Length);
541 data.Write(fragment);
543 result = session.Context.Cipher.ClientHMAC.ComputeHash(data.ToArray());
550 private byte ReadByte()
552 byte[] b = new byte[1];
558 private short ReadShort()
560 byte[] b = new byte[2];
563 short val = BitConverter.ToInt16(b, 0);
565 return System.Net.IPAddress.HostToNetworkOrder(val);
570 #region HANDSHAKE_METHODS
575 ClientHello -------->
580 <-------- ServerHelloDone
588 Application Data <-------> Application Data
590 Fig. 1 - Message flow for a full handshake
593 internal void DoHandshake()
595 // Reset isSecure field
596 this.session.IsSecure = false;
599 this.sendRecord(TlsHandshakeType.ClientHello);
601 // Read server response
602 while (!session.HelloDone)
605 this.receiveRecord();
608 // Send client certificate if requested
609 if (session.Context.ServerSettings.CertificateRequest)
611 this.sendRecord(TlsHandshakeType.Certificate);
614 // Send Client Key Exchange
615 this.sendRecord(TlsHandshakeType.ClientKeyExchange);
617 // Now initialize session cipher with the generated keys
618 this.session.Context.Cipher.InitializeCipher();
620 // Send certificate verify if requested
621 if (session.Context.ServerSettings.CertificateRequest)
623 this.sendRecord(TlsHandshakeType.CertificateVerify);
626 // Send Cipher Spec protocol
627 this.sendChangeCipherSpec();
629 // Read Cipher Spec protocol
630 this.receiveRecord();
632 // Read server finished
633 if (!session.HandshakeFinished)
635 this.receiveRecord();
639 this.session.Context.ClearKeyInfo();
642 this.session.IsSecure = true;
645 private TlsHandshakeMessage createClientHandshakeMessage(TlsHandshakeType type)
649 case TlsHandshakeType.ClientHello:
650 return new TlsClientHello(session);
652 case TlsHandshakeType.Certificate:
653 return new TlsClientCertificate(session);
655 case TlsHandshakeType.ClientKeyExchange:
656 return new TlsClientKeyExchange(session);
658 case TlsHandshakeType.CertificateVerify:
659 return new TlsClientCertificateVerify(session);
661 case TlsHandshakeType.Finished:
662 return new TlsClientFinished(session);
665 throw new InvalidOperationException("Unknown client handshake message type: " + type.ToString() );
669 private TlsHandshakeMessage createServerHandshakeMessage(TlsHandshakeType type, byte[] buffer)
673 case TlsHandshakeType.HelloRequest:
674 this.sendRecord(TlsHandshakeType.ClientHello);
677 case TlsHandshakeType.ServerHello:
678 return new TlsServerHello(session, buffer);
680 case TlsHandshakeType.Certificate:
681 return new TlsServerCertificate(session, buffer);
683 case TlsHandshakeType.ServerKeyExchange:
684 return new TlsServerKeyExchange(session, buffer);
686 case TlsHandshakeType.CertificateRequest:
687 return new TlsServerCertificateRequest(session, buffer);
689 case TlsHandshakeType.ServerHelloDone:
690 return new TlsServerHelloDone(session, buffer);
692 case TlsHandshakeType.Finished:
693 return new TlsServerFinished(session, buffer);
696 throw session.CreateException("Unknown server handshake message received ({0})", type.ToString());