5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2007 Novell, Inc. http://www.novell.com
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System
.Collections
.Generic
;
31 using System
.Security
.Cryptography
;
32 using System
.Security
.Cryptography
.X509Certificates
;
34 using Mono
.Security
.Protocol
.Tls
;
35 using Mono
.Security
.Protocol
.Tls
.Handshake
;
36 using Mono
.Security
.Protocol
.Tls
.Handshake
.Client
;
38 namespace System
.ServiceModel
.Security
.Tokens
40 internal abstract class TlsSession
42 protected abstract Context Context { get; }
44 protected abstract RecordProtocol Protocol { get; }
46 public byte [] MasterSecret
{
47 get { return Context.MasterSecret; }
50 public byte [] CreateHash (byte [] key
, byte [] seedSrc
, string label
)
52 byte [] labelBytes
= Encoding
.UTF8
.GetBytes (label
);
53 byte [] seed
= new byte [seedSrc
.Length
+ labelBytes
.Length
];
54 Array
.Copy (seedSrc
, seed
, seedSrc
.Length
);
55 Array
.Copy (labelBytes
, 0, seed
, seedSrc
.Length
, labelBytes
.Length
);
56 return Context
.Current
.Cipher
.Expand ("SHA1", key
, seed
, 256 / 8);
59 public byte [] CreateHashAlt (byte [] key
, byte [] seed
, string label
)
61 return Context
.Current
.Cipher
.PRF (key
, label
, seed
, 256 / 8);
64 protected void WriteHandshake (MemoryStream ms
)
66 Context
.SupportedCiphers
= CipherSuiteFactory
.GetSupportedCiphers (SecurityProtocolType
.Tls
);
67 ms
.WriteByte (0x16); // Handshake
68 ms
.WriteByte (3); // version-major
69 ms
.WriteByte (1); // version-minor
72 protected void WriteChangeCipherSpec (MemoryStream ms
)
74 ms
.WriteByte (0x14); // Handshake
75 ms
.WriteByte (3); // version-major
76 ms
.WriteByte (1); // version-minor
77 ms
.WriteByte (0); // size-upper
78 ms
.WriteByte (1); // size-lower
79 ms
.WriteByte (1); // ChangeCipherSpec content (1 byte)
82 protected void ReadHandshake (MemoryStream ms
)
84 if (ms
.ReadByte () != 0x16)
85 throw new Exception ("INTERNAL ERROR: handshake is expected");
86 Context
.ChangeProtocol ((short) (ms
.ReadByte () * 0x100 + ms
.ReadByte ()));
89 protected void ReadChangeCipherSpec (MemoryStream ms
)
91 if (ms
.ReadByte () != 0x14)
92 throw new Exception ("INTERNAL ERROR: ChangeCipherSpec is expected");
93 Context
.ChangeProtocol ((short) (ms
.ReadByte () * 0x100 + ms
.ReadByte ()));
94 if (ms
.ReadByte () * 0x100 + ms
.ReadByte () != 1)
95 throw new Exception ("INTERNAL ERROR: unexpected ChangeCipherSpec length");
96 ms
.ReadByte (); // ChangeCipherSpec content (1 byte) ... anything is OK?
99 protected byte [] ReadNextOperation (MemoryStream ms
, HandshakeType expected
)
101 if (ms
.ReadByte () != (int) expected
)
102 throw new Exception ("INTERNAL ERROR: unexpected server response");
103 int size
= ms
.ReadByte () * 0x10000 + ms
.ReadByte () * 0x100 + ms
.ReadByte ();
104 // FIXME: use correct valid input range
106 throw new Exception ("rejected massive input size.");
107 byte [] bytes
= new byte [size
];
108 ms
.Read (bytes
, 0, size
);
112 protected void WriteOperations (MemoryStream ms
, params HandshakeMessage
[] msgs
)
114 List
<byte []> rawbufs
= new List
<byte []> ();
116 for (int i
= 0; i
< msgs
.Length
; i
++) {
117 HandshakeMessage msg
= msgs
[i
];
119 rawbufs
.Add (msg
.EncodeMessage ());
120 total
+= rawbufs
[i
].Length
;
123 // FIXME: split packets when the size exceeded 0x10000 (or so)
124 ms
.WriteByte ((byte) (total
/ 0x100));
125 ms
.WriteByte ((byte) (total
% 0x100));
126 foreach (byte [] bytes
in rawbufs
)
127 ms
.Write (bytes
, 0, bytes
.Length
);
130 protected void VerifyEndOfTransmit (MemoryStream ms
)
132 if (ms
.Position
== ms
.Length
)
136 byte [] bytes = new byte [ms.Length - ms.Position];
137 ms.Read (bytes, 0, bytes.Length);
138 foreach (byte b in bytes)
139 Console.Write ("{0:X02} ", b);
140 Console.WriteLine (" - total {0} bytes remained.", bytes.Length);
143 throw new Exception ("INTERNAL ERROR: unexpected server response");
147 internal class TlsClientSession
: TlsSession
153 public TlsClientSession (string host
, X509Certificate2 clientCert
)
155 stream
= new MemoryStream ();
156 if (clientCert
== null)
157 ssl
= new SslClientStream (stream
, host
, true, SecurityProtocolType
.Tls
);
159 ssl
= new SslClientStream (stream
, host
, true, SecurityProtocolType
.Tls
, new X509CertificateCollection (new X509Certificate
[] {clientCert}
));
161 ssl
.ClientCertSelection
+= delegate (
162 X509CertificateCollection clientCertificates
,
163 X509Certificate serverCertificate
,
165 X509CertificateCollection serverRequestedCertificates
) {
166 return clientCertificates
[0];
171 protected override Context Context
{
172 get { return ssl.context; }
175 protected override RecordProtocol Protocol
{
176 get { return ssl.protocol; }
179 public byte [] ProcessClientHello ()
181 Context
.SupportedCiphers
= CipherSuiteFactory
.GetSupportedCiphers (Context
.SecurityProtocol
);
182 Context
.HandshakeState
= HandshakeState
.Started
;
183 Protocol
.SendRecord (HandshakeType
.ClientHello
);
185 return stream
.ToArray ();
188 // ServerHello, ServerCertificate and ServerHelloDone
189 public void ProcessServerHello (byte [] raw
)
191 stream
.SetLength (0);
192 stream
.Write (raw
, 0, raw
.Length
);
193 stream
.Seek (0, SeekOrigin
.Begin
);
195 Protocol
.ReceiveRecord (stream
); // ServerHello
196 Protocol
.ReceiveRecord (stream
); // ServerCertificate
198 Protocol
.ReceiveRecord (stream
); // CertificateRequest
199 Protocol
.ReceiveRecord (stream
); // ServerHelloDone
200 if (stream
.Position
!= stream
.Length
)
201 throw new SecurityNegotiationException (String
.Format ("Unexpected SSL negotiation binary: {0} bytes of excess in {1} bytes of the octets", stream
.Length
- stream
.Position
, stream
.Length
));
204 public byte [] ProcessClientKeyExchange ()
206 stream
.SetLength (0);
208 Protocol
.SendRecord (HandshakeType
.Certificate
);
209 Protocol
.SendRecord (HandshakeType
.ClientKeyExchange
);
210 Context
.Negotiating
.Cipher
.ComputeKeys ();
211 Context
.Negotiating
.Cipher
.InitializeCipher ();
212 Protocol
.SendChangeCipherSpec ();
213 Context
.SupportedCiphers
= CipherSuiteFactory
.GetSupportedCiphers (SecurityProtocolType
.Tls
);
214 Protocol
.SendRecord (HandshakeType
.Finished
);
216 return stream
.ToArray ();
219 public void ProcessServerFinished (byte [] raw
)
221 stream
.SetLength (0);
222 stream
.Write (raw
, 0, raw
.Length
);
223 stream
.Seek (0, SeekOrigin
.Begin
);
225 Protocol
.ReceiveRecord (stream
); // ChangeCipherSpec
226 Protocol
.ReceiveRecord (stream
); // ServerFinished
229 public byte [] ProcessApplicationData (byte [] raw
)
231 stream
.SetLength (0);
232 stream
.Write (raw
, 0, raw
.Length
);
233 stream
.Seek (0, SeekOrigin
.Begin
);
234 return Protocol
.ReceiveRecord (stream
); // ApplicationData