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(contentType
);
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
.ClientHelloV2
:
119 // Set the last handshake message received as the standard ClientHello
120 this.context
.LastHandshakeMsg
= HandshakeType
.ClientHello
;
124 case ContentType
.Alert
:
125 this.ProcessAlert((AlertLevel
)message
.ReadByte(), (AlertDescription
)message
.ReadByte());
129 case ContentType
.ChangeCipherSpec
:
130 this.ProcessChangeCipherSpec();
133 case ContentType
.ApplicationData
:
136 case ContentType
.Handshake
:
139 this.ProcessHandshakeMessage(message
);
142 // Update handshakes of current messages
143 this.context
.HandshakeMessages
.Write(message
.ToArray());
147 throw new TlsException(
148 AlertDescription
.UnexpectedMessage
,
149 "Unknown record received from server.");
155 private byte[] ReadRecordBuffer(ContentType contentType
)
159 case ContentType
.ClientHelloV2
:
160 return this.ReadClientHelloV2();
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
;
180 byte[] cipherSpecV2
= new byte[cipherSpecLength
];
181 this.innerStream
.Read(cipherSpecV2
, 0, cipherSpecV2
.Length
);
184 byte[] sessionId
= new byte[sessionIdLength
];
185 this.innerStream
.Read(sessionId
, 0, sessionId
.Length
);
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
);
223 private byte[] ReadStandardRecordBuffer()
225 short protocol
= this.ReadShort();
226 short length
= this.ReadShort();
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
);
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
)
262 case AlertLevel
.Fatal
:
263 throw new TlsException(alertLevel
, alertDesc
);
265 case AlertLevel
.Warning
:
269 case AlertDescription
.CloseNotify
:
270 this.context
.ConnectionEnd
= true;
279 #region Send Alert Methods
281 public void SendAlert(AlertDescription description
)
283 this.SendAlert(new Alert(description
));
286 public void SendAlert(
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
);
300 new byte[]{(byte)alert.Level, (byte)alert.Description}
);
302 if (alert
.IsCloseNotify
)
304 this.context
.ConnectionEnd
= true;
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(
355 public byte[] EncodeRecord(
356 ContentType contentType
,
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;
377 if ((count
- position
) > Context
.MAX_FRAGMENT_SIZE
)
379 fragmentLength
= Context
.MAX_FRAGMENT_SIZE
;
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
)
393 fragment
= this.encryptRecordFragment(contentType
, fragment
);
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();
413 #region Cryptography Methods
415 private byte[] encryptRecordFragment(
416 ContentType contentType
,
421 // Calculate message MAC
422 if (this.Context
is ClientContext
)
424 mac
= this.context
.Cipher
.ComputeClientRecordMAC(contentType
, fragment
);
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
++;
451 private TlsStream
decryptRecordFragment(
452 ContentType contentType
,
455 byte[] dcrFragment
= null;
456 byte[] dcrMAC
= null;
457 bool badRecordMac
= false;
461 this.context
.Cipher
.DecryptRecord(fragment
, ref dcrFragment
, ref dcrMAC
);
465 if (this.context
is ServerContext
)
467 this.Context
.RecordProtocol
.SendAlert(AlertDescription
.DecryptionFailed
);
473 // Generate record MAC
476 if (this.Context
is ClientContext
)
478 mac
= this.context
.Cipher
.ComputeServerRecordMAC(contentType
, dcrFragment
);
482 mac
= this.context
.Cipher
.ComputeClientRecordMAC(contentType
, dcrFragment
);
485 DebugHelper
.WriteLine(">>>> Record MAC", mac
);
488 if (mac
.Length
!= dcrMAC
.Length
)
494 for (int i
= 0; i
< mac
.Length
; i
++)
496 if (mac
[i
] != dcrMAC
[i
])
506 throw new TlsException(AlertDescription
.BadRecordMAC
, "Bad record MAC");
509 // Update sequence number
510 this.context
.ReadSequenceNumber
++;
512 return new TlsStream(dcrFragment
);
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();
531 // SSL/TLS cipher spec
533 short code
= codes
.ReadInt16();
534 if ((index
= this.Context
.SupportedCiphers
.IndexOf(code
)) != -1)
536 this.Context
.Cipher
= this.Context
.SupportedCiphers
[index
];
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
);
550 this.Context
.Cipher
= cipher
;
556 if (this.Context
.Cipher
== null)
558 throw new TlsException(AlertDescription
.InsuficientSecurity
, "Insuficient Security");
562 private CipherSuite
MapV2CipherCode(string prefix
, int code
)
569 // TLS_RC4_128_WITH_MD5
570 return this.Context
.SupportedCiphers
[prefix
+ "RSA_WITH_RC4_128_MD5"];
573 // TLS_RC4_128_EXPORT40_WITH_MD5
574 return this.Context
.SupportedCiphers
[prefix
+ "RSA_EXPORT_WITH_RC4_40_MD5"];
577 // TLS_RC2_CBC_128_CBC_WITH_MD5
578 return this.Context
.SupportedCiphers
[prefix
+ "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
581 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
582 return this.Context
.SupportedCiphers
[prefix
+ "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
585 // TLS_IDEA_128_CBC_WITH_MD5
589 // TLS_DES_64_CBC_WITH_MD5
593 // TLS_DES_192_EDE3_CBC_WITH_MD5