2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included 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 OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 using System.Collections.Generic;
35 * Class for a SSL client connection.
37 * An instance is created over a specified transport stream. SSL session
38 * parameters from a previous connection are optionally specified, to
39 * attempt session resumption. The instance handles the connection but
40 * cannot be "revived" after the connection was closed (the session
41 * parameters, though, can be extracted and used with another instance).
44 public class SSLClient : SSLEngine {
47 * Create the client over the provided stream. No attempt at
48 * session resumption will be made.
50 public SSLClient(Stream sub) : this(sub, null)
55 * Create the client over the provided stream. If the
56 * 'sessionParameters' are not null, then the client will try to
57 * resume that session. Note that session parameters may include
58 * a "target server name", in which case the ServerName
59 * property will be set to that name, which will be included
60 * in the ClientHello as the Server Name Indication extension.
62 public SSLClient(Stream sub, SSLSessionParameters sessionParameters)
65 sentExtensions = new List<int>();
66 resumeParams = sessionParameters;
67 if (resumeParams != null) {
68 string name = resumeParams.ServerName;
76 * Validator for the server certificate. This callback is
77 * responsible for obtaining the server's public key and making
78 * sure it is the right one. A normal, standard-compliant
79 * implementation should do the following:
81 * -- Validate the certificate as X.509 mandates (building
82 * a path to a trust anchor, and verifying all signatures, names
83 * and appropriate certificate extensions; also obtaining
84 * proper CRL or OCSP response for a fresh revocation status).
86 * -- Check that the intended server name (provided in the
87 * 'serverName' parameter) matches that which is found in the
88 * certificate (see RFC 2818 section 3.1 for details; also
89 * consider RFC 6125 section 6.4).
91 * -- Return the public key found in the server's certificate,
92 * along with its allowed usages (which may depend on the
93 * KeyUsage extensions found in the certificate).
95 * The certificate chain, as received from the server, is
96 * provided as parameter; it is non-empty (it contains at least
97 * one certificate). The server's certificate is the first one
100 * The 'serverName' parameter is the intended server name, to
101 * match against the names found in the certificate. If it is
102 * null, then no matching is expected (this correspond to the
103 * ServerName property in this SSLClient instance).
105 * The 'usage' variable shall be set to a value that qualifies
106 * whether the key may be used for encryption and/or signatures.
108 public delegate IPublicKey CertValidator(
109 byte[][] chain, string serverName, out KeyUsage usage);
110 public CertValidator ServerCertValidator {
115 * A simple INSECURE certificate "validator" that does not validate
116 * anything: the public key is extracted and returned, with no
117 * other checks. THIS IS FOR TESTS ONLY. Using this validator
118 * basically voids all security properties of SSL.
120 public static IPublicKey InsecureCertValidator(
121 byte[][] chain, string serverName, out KeyUsage usage)
123 usage = KeyUsage.EncryptAndSign;
124 return SSL.GetKeyFromCert(chain[0]);
127 List<int> sentExtensions;
128 SSLSessionParameters resumeParams;
130 internal override bool IsClient {
136 internal override bool DoHandshake()
138 CheckConfigHashAndSign();
141 MakeRandom(clientRandom);
147 if (!ParseServerHello(out resume)) {
153 * Abbreviated handshake.
155 ParseCCSAndFinished();
156 SendCCSAndFinished();
164 IPublicKey pkey = ParseCertificate(out usage);
168 if (SSL.IsECDHE_RSA(CipherSuite)) {
169 if (!(pkey is RSAPublicKey)) {
170 throw new SSLException(
171 "ECDHE_RSA needs a RSA public key");
173 if (usage != KeyUsage.SignOnly
174 && usage != KeyUsage.EncryptAndSign)
176 throw new SSLException("Server public key"
177 + " unfit for signatures");
179 serverPoint = ParseServerKeyExchange(out curve, pkey);
180 } else if (SSL.IsECDHE_ECDSA(CipherSuite)) {
181 if (!(pkey is ECPublicKey)) {
182 throw new SSLException(
183 "ECDHE_ECDSA needs an EC public key");
185 if (usage != KeyUsage.SignOnly
186 && usage != KeyUsage.EncryptAndSign)
188 throw new SSLException("Server public key"
189 + " unfit for signatures");
191 serverPoint = ParseServerKeyExchange(out curve, pkey);
197 bool reqClientCert = false;
199 byte[] msg = ReadHandshakeMessage(out mt);
200 if (mt == SSL.CERTIFICATE_REQUEST) {
202 * FIXME: parse message and select a client
205 reqClientCert = true;
206 msg = ReadHandshakeMessage(out mt);
208 if (mt != SSL.SERVER_HELLO_DONE) {
209 throw new SSLException(string.Format("Unexpected"
210 + " handshake message {0}"
211 + " (expected: ServerHelloDone)", mt));
213 if (msg.Length != 0) {
214 throw new SSLException(
215 "Invalid ServerHelloDone (not empty)");
220 * FIXME: right now, we send an empty Certificate
221 * message if the server asks for a client
222 * certificate; i.e., we claim we have none.
225 StartHandshakeMessage(SSL.CERTIFICATE);
226 EndHandshakeMessage(ms);
229 if (SSL.IsRSA(CipherSuite)) {
230 if (!(pkey is RSAPublicKey)) {
231 throw new SSLException(
232 "Server public key is not RSA");
234 if (usage != KeyUsage.EncryptOnly
235 && usage != KeyUsage.EncryptAndSign)
237 throw new SSLException("Server public key is"
238 + " not allowed for encryption");
240 SendClientKeyExchangeRSA(pkey as RSAPublicKey);
241 } else if (SSL.IsECDH(CipherSuite)) {
242 if (!(pkey is ECPublicKey)) {
243 throw new SSLException(
244 "Server public key is not EC");
246 if (usage != KeyUsage.EncryptOnly
247 && usage != KeyUsage.EncryptAndSign)
249 throw new SSLException("Server public key is"
250 + " not allowed for key exchange");
252 SendClientKeyExchangeECDH(pkey as ECPublicKey);
253 } else if (serverPoint != null) {
254 SendClientKeyExchangeECDH(curve, serverPoint);
257 * TODO: Maybe support DHE cipher suites?
259 throw new Exception("NYI");
263 * FIXME: when client certificates are supported, we
264 * will need to send a CertificateVerify message here.
267 SendCCSAndFinished();
270 ParseCCSAndFinished();
277 void SendClientHello()
279 MemoryStream ms = StartHandshakeMessage(SSL.CLIENT_HELLO);
281 // Maximum supported protocol version.
282 IO.Write16(ms, VersionMax);
285 ms.Write(clientRandom, 0, clientRandom.Length);
288 if (resumeParams != null) {
289 byte[] id = resumeParams.SessionID;
290 ms.WriteByte((byte)id.Length);
291 ms.Write(id, 0, id.Length);
296 // List of supported cipher suites.
297 int csLen = SupportedCipherSuites.Length << 1;
298 int extraCS = GetQuirkInt("sendExtraCipherSuite", -1);
302 IO.Write16(ms, csLen);
303 foreach (int cs in SupportedCipherSuites) {
307 IO.Write16(ms, extraCS);
310 // List of supported compression algorithms.
315 sentExtensions.Clear();
316 MemoryStream chExt = new MemoryStream();
318 // Server Name Indication
319 if (ServerName != null && ServerName.Length > 0) {
320 byte[] encName = Encoding.UTF8.GetBytes(ServerName);
321 int elen = encName.Length;
323 throw new SSLException("Oversized server name");
325 sentExtensions.Add(0x0000);
326 IO.Write16(chExt, 0x0000); // extension type
327 IO.Write16(chExt, elen + 5); // extension length
328 IO.Write16(chExt, elen + 3); // name list length
329 chExt.WriteByte(0x00); // name type
330 IO.Write16(chExt, elen); // name length
331 chExt.Write(encName, 0, elen);
334 // Supported Curves and Supported Point Formats
335 if (SupportedCurves != null && SupportedCurves.Length > 0) {
336 int len = SupportedCurves.Length;
337 sentExtensions.Add(0x000A);
338 IO.Write16(chExt, 0x000A);
339 IO.Write16(chExt, (len << 1) + 2);
340 IO.Write16(chExt, len << 1);
341 foreach (int cc in SupportedCurves) {
342 IO.Write16(chExt, cc);
345 sentExtensions.Add(0x000B);
346 IO.Write16(chExt, 0x000B);
347 IO.Write16(chExt, 2);
349 chExt.WriteByte(0x00);
352 // Supported Signatures
353 if (VersionMax >= SSL.TLS12 && SupportedHashAndSign != null
354 && SupportedHashAndSign.Length > 0)
356 sentExtensions.Add(0x000D);
357 IO.Write16(chExt, 0x000D);
358 int num = SupportedHashAndSign.Length;
359 IO.Write16(chExt, 2 + (num << 1));
360 IO.Write16(chExt, num << 1);
361 foreach (int hs in SupportedHashAndSign) {
362 IO.Write16(chExt, hs);
366 // Secure renegotiation
367 if (!GetQuirkBool("noSecureReneg")) {
368 sentExtensions.Add(0xFF01);
369 IO.Write16(chExt, 0xFF01);
371 if (renegSupport > 0) {
372 exv = savedClientFinished;
377 if (GetQuirkBool("forceEmptySecureReneg")) {
379 } else if (GetQuirkBool("forceNonEmptySecureReneg")) {
381 } else if (GetQuirkBool("alterNonEmptySecureReneg")) {
382 if (exv.Length > 0) {
383 exv[exv.Length - 1] ^= 0x01;
385 } else if (GetQuirkBool("oversizedSecureReneg")) {
389 IO.Write16(chExt, exv.Length + 1);
390 chExt.WriteByte((byte)exv.Length);
391 chExt.Write(exv, 0, exv.Length);
394 // Extra extension with random contents.
395 int extraExt = GetQuirkInt("sendExtraExtension", -1);
397 byte[] exv = new byte[extraExt >> 16];
399 IO.Write16(chExt, extraExt & 0xFFFF);
400 IO.Write16(chExt, exv.Length);
401 chExt.Write(exv, 0, exv.Length);
404 // Max Fragment Length
408 byte[] encExt = chExt.ToArray();
409 if (encExt.Length > 0) {
410 if (encExt.Length > 65535) {
411 throw new SSLException("Oversized extensions");
413 IO.Write16(ms, encExt.Length);
414 ms.Write(encExt, 0, encExt.Length);
417 EndHandshakeMessage(ms);
420 bool ParseServerHello(out bool resume)
425 byte[] msg = ReadHandshakeMessage(out mt, FirstHandshakeDone);
428 * Server denies attempt explicitly.
432 if (mt != SSL.SERVER_HELLO) {
433 throw new SSLException(string.Format("Unexpected"
434 + " handshake message {0} (expecting a"
435 + " ServerHello)", mt));
438 if (msg.Length < 38) {
439 throw new SSLException("Truncated ServerHello");
441 Version = IO.Dec16be(msg, 0);
442 if (Version < VersionMin || Version > VersionMax) {
443 throw new SSLException(string.Format(
444 "Unsupported version: 0x{0:X4}", Version));
446 Array.Copy(msg, 2, serverRandom, 0, 32);
449 throw new SSLException("Invalid session ID length");
451 if (idLen + 38 > msg.Length) {
452 throw new SSLException("Truncated ServerHello");
454 sessionID = new byte[idLen];
455 Array.Copy(msg, 35, sessionID, 0, idLen);
456 int off = 35 + idLen;
459 * Cipher suite. It must be one of the suites we sent.
461 CipherSuite = IO.Dec16be(msg, off);
464 foreach (int cs in SupportedCipherSuites) {
465 if (cs == SSL.FALLBACK_SCSV
466 || cs == SSL.EMPTY_RENEGOTIATION_INFO_SCSV)
470 if (cs == CipherSuite) {
476 throw new SSLException(string.Format(
477 "Server selected cipher suite 0x{0:X4}"
478 + " which we did not advertise", CipherSuite));
482 * Compression. Must be 0, since we do not support it.
484 int comp = msg[off ++];
486 throw new SSLException(string.Format(
487 "Server selected compression {0}"
488 + " which we did not advertise", comp));
492 * Extensions. Each extension from the server should
493 * correspond to an extension sent in the ClientHello.
495 bool secReneg = false;
496 if (msg.Length > off) {
497 if (msg.Length == off + 1) {
498 throw new SSLException("Truncated ServerHello");
500 int tlen = IO.Dec16be(msg, off);
502 if (tlen != msg.Length - off) {
503 throw new SSLException(
504 "Invalid extension list length");
506 while (off < msg.Length) {
507 if ((off + 4) > msg.Length) {
508 throw new SSLException(
509 "Truncated extention");
511 int etype = IO.Dec16be(msg, off);
512 int elen = IO.Dec16be(msg, off + 2);
514 if (elen > msg.Length - off) {
515 throw new SSLException(
516 "Truncated extention");
518 if (!sentExtensions.Contains(etype)) {
519 throw new SSLException(string.Format(
520 "Server send unadvertised"
521 + " extenstion 0x{0:X4}",
526 * We have some processing to do on some
527 * specific server-side extensions.
533 ParseExtSecureReneg(msg, off, elen);
542 * Renegotiation support: if we did not get the extension
543 * from the server, then secure renegotiation is
544 * definitely not supported. If it _was_ known as
545 * being supported (from a previous handshake) then this
549 if (renegSupport > 0) {
550 throw new SSLException("Missing Secure"
551 + " Renegotiation extension");
557 * Check whether this is a session resumption: a session
558 * is resumed if we sent a non-empty session ID, and the
559 * ServerHello contained the same session ID.
561 * In case of resumption, the ServerHello must use the
562 * same version and cipher suite than in the saved
565 if (resumeParams != null) {
566 byte[] id = resumeParams.SessionID;
567 if (id.Length > 0 && IO.Eq(id, sessionID)) {
568 if (Version != resumeParams.Version) {
569 throw new SSLException(
570 "Resume version mismatch");
572 if (CipherSuite != resumeParams.CipherSuite) {
573 throw new SSLException(
574 "Resume cipher suite mismatch");
576 SetMasterSecret(resumeParams.MasterSecret);
584 void ParseExtSecureReneg(byte[] buf, int off, int len)
586 if (len < 1 || len != 1 + buf[off]) {
587 throw new SSLException(
588 "Invalid Secure Renegotiation extension");
593 if (renegSupport == 0) {
595 * Initial handshake: extension MUST be empty.
598 throw new SSLException(
599 "Non-empty Secure Renegotation"
600 + " on initial handshake");
605 * Renegotiation: extension MUST contain the
606 * concatenation of the saved client and
607 * server Finished messages (in that order).
610 throw new SSLException(
611 "Wrong Secure Renegotiation value");
614 for (int i = 0; i < 12; i ++) {
615 z |= savedClientFinished[i] ^ buf[off + i];
616 z |= savedServerFinished[i] ^ buf[off + 12 + i];
619 throw new SSLException(
620 "Wrong Secure Renegotiation value");
625 IPublicKey ParseCertificate(out KeyUsage usage)
627 byte[] msg = ReadHandshakeMessageExpected(SSL.CERTIFICATE);
628 if (msg.Length < 3) {
629 throw new SSLException("Invalid Certificate message");
631 int tlen = IO.Dec24be(msg, 0);
633 if (tlen != msg.Length - off) {
634 throw new SSLException("Invalid Certificate message");
636 List<byte[]> certs = new List<byte[]>();
637 while (off < msg.Length) {
638 if (msg.Length - off < 3) {
639 throw new SSLException(
640 "Invalid Certificate message");
642 int clen = IO.Dec24be(msg, off);
644 if (clen > msg.Length - off) {
645 throw new SSLException(
646 "Invalid Certificate message");
648 byte[] ec = new byte[clen];
649 Array.Copy(msg, off, ec, 0, clen);
654 return ServerCertValidator(
655 certs.ToArray(), ServerName, out usage);
658 byte[] ParseServerKeyExchange(out ECCurve curve, IPublicKey pkey)
660 byte[] msg = ReadHandshakeMessageExpected(
661 SSL.SERVER_KEY_EXCHANGE);
662 if (msg.Length < 4) {
663 throw new SSLException(
664 "Invalid ServerKeyExchange message");
666 if (msg[0] != 0x03) {
667 throw new SSLException("Unsupported unnamed curve");
669 curve = SSL.GetCurveByID(IO.Dec16be(msg, 1));
672 if (msg.Length - off < plen) {
673 throw new SSLException(
674 "Invalid ServerKeyExchange message");
676 byte[] point = new byte[plen];
677 Array.Copy(msg, off, point, 0, plen);
682 if (Version >= SSL.TLS12) {
683 if (msg.Length - off < 2) {
684 throw new SSLException(
685 "Invalid ServerKeyExchange message");
687 hashId = msg[off ++];
689 throw new SSLException(
690 "Invalid hash identifier");
694 if (pkey is RSAPublicKey) {
697 } else if (pkey is ECPublicKey) {
701 throw new SSLException(
702 "Unsupported signature key type");
706 if (msg.Length - off < 2) {
707 throw new SSLException(
708 "Invalid ServerKeyExchange message");
710 int sigLen = IO.Dec16be(msg, off);
712 if (sigLen != msg.Length - off) {
713 throw new SSLException(
714 "Invalid ServerKeyExchange message");
716 byte[] sig = new byte[sigLen];
717 Array.Copy(msg, off, sig, 0, sigLen);
722 SHA1 sha1 = new SHA1();
723 md5.Update(clientRandom);
724 md5.Update(serverRandom);
725 md5.Update(msg, 0, slen);
726 sha1.Update(clientRandom);
727 sha1.Update(serverRandom);
728 sha1.Update(msg, 0, slen);
731 sha1.DoFinal(hv, 16);
733 IDigest h = SSL.GetHashByID(hashId);
734 h.Update(clientRandom);
735 h.Update(serverRandom);
736 h.Update(msg, 0, slen);
742 RSAPublicKey rpk = pkey as RSAPublicKey;
744 throw new SSLException(
745 "Wrong public key type for RSA");
748 ok = RSA.VerifyND(rpk, hv, sig);
754 head1 = RSA.PKCS1_MD5;
755 head2 = RSA.PKCS1_MD5_ALT;
758 head1 = RSA.PKCS1_SHA1;
759 head2 = RSA.PKCS1_SHA1_ALT;
762 head1 = RSA.PKCS1_SHA224;
763 head2 = RSA.PKCS1_SHA224_ALT;
766 head1 = RSA.PKCS1_SHA256;
767 head2 = RSA.PKCS1_SHA256_ALT;
770 head1 = RSA.PKCS1_SHA384;
771 head2 = RSA.PKCS1_SHA384_ALT;
774 head1 = RSA.PKCS1_SHA512;
775 head2 = RSA.PKCS1_SHA512_ALT;
778 throw new SSLException(
779 "Unsupported hash algorithm: "
782 ok = RSA.Verify(rpk, head1, head2, hv, sig);
784 } else if (sigId == 3) {
785 ECPublicKey epk = pkey as ECPublicKey;
787 throw new SSLException(
788 "Wrong public key type for ECDSA");
790 ok = ECDSA.Verify(epk, hv, sig);
792 throw new SSLException(
793 "Unsupported signature type: " + sigId);
797 throw new SSLException(
798 "Invalid signature on ServerKeyExchange");
803 void SendClientKeyExchangeRSA(RSAPublicKey pkey)
805 byte[] pms = new byte[48];
806 IO.Enc16be(Version, pms, 0);
807 RNG.GetBytes(pms, 2, pms.Length - 2);
808 byte[] epms = RSA.Encrypt(pkey, pms);
810 StartHandshakeMessage(SSL.CLIENT_KEY_EXCHANGE);
811 IO.Write16(ms, epms.Length);
812 ms.Write(epms, 0, epms.Length);
813 EndHandshakeMessage(ms);
817 void SendClientKeyExchangeECDH(ECPublicKey pkey)
819 ECCurve curve = pkey.Curve;
820 SendClientKeyExchangeECDH(curve, pkey.Pub);
823 void SendClientKeyExchangeECDH(ECCurve curve, byte[] pub)
825 byte[] k = curve.MakeRandomSecret();
828 * Compute the point P = k*G that we will send.
830 byte[] P = curve.GetGenerator(false);
831 uint good = curve.Mul(P, k, P, false);
834 * Compute the shared secret Q = k*Pub.
836 byte[] Q = new byte[P.Length];
837 good &= curve.Mul(pub, k, Q, false);
841 * This might happen only if the server's public
842 * key is not part of the proper subgroup. This
843 * cannot happen with NIST's "P" curves.
845 throw new SSLException("ECDH failed");
852 StartHandshakeMessage(SSL.CLIENT_KEY_EXCHANGE);
853 ms.WriteByte((byte)P.Length);
854 ms.Write(P, 0, P.Length);
855 EndHandshakeMessage(ms);
858 * Extract premaster secret.
861 int xoff = curve.GetXoff(out xlen);
862 byte[] pms = new byte[xlen];
863 Array.Copy(Q, xoff, pms, 0, xlen);
867 internal override void ProcessExtraHandshake()
870 * If we receive a non-empty handshake message, then
871 * it should be an HelloRequest. Note that we expect
872 * the request not to be mixed with application data
873 * records (though it could be split).
878 * We accept to renegotiate only if the server supports
879 * the Secure Renegotiation extension.
881 if (renegSupport != 1) {
882 SendWarning(SSL.NO_RENEGOTIATION);
888 * We do a new handshake. We explicitly refuse to reuse
889 * session parameters, because there is no point in
890 * renegotiation if this resumes the same session.
896 internal override void PrepareRenegotiate()
899 * Nothing to do to trigger a new handshake, the client
900 * just has to send the ClientHello right away.