internal override void ProcessExtraHandshake() { /* * If we receive a non-empty handshake message, then * it should be an HelloRequest. Note that we expect * the request not to be mixed with application data * records (though it could be split). */ ReadHelloRequests(); /* * We accept to renegotiate only if the server supports * the Secure Renegotiation extension. */ if (renegSupport != 1) { SendWarning(SSL.NO_RENEGOTIATION); SetAppData(); return; } /* * We do a new handshake. We explicitly refuse to reuse * session parameters, because there is no point in * renegotiation if this resumes the same session. */ resumeParams = null; DoHandshake(); }
/* see ISessionCache */ public void Store(SSLSessionParameters sp) { lock (mutex) { string ids = IDToString(sp.SessionID); int x; if (spx.TryGetValue(ids, out x)) { if ((x + 1) < count) { Array.Copy(data, x + 1, data, x, count - x - 1); } spx[ids] = count - 1; data[count - 1] = sp; return; } if (count == maxCount) { SSLSessionParameters esp = data[0]; Array.Copy(data, 1, data, 0, count - 1); count--; spx.Remove(IDToString(esp.SessionID)); } spx[ids] = count; data[count] = sp; count++; } }
/* * Create the client over the provided stream. If the * 'sessionParameters' are not null, then the client will try to * resume that session. Note that session parameters may include * a "target server name", in which case the ServerName * property will be set to that name, which will be included * in the ClientHello as the Server Name Indication extension. */ public SSLClient(Stream sub, SSLSessionParameters sessionParameters) : base(sub) { sentExtensions = new List <int>(); resumeParams = sessionParameters; if (resumeParams != null) { string name = resumeParams.ServerName; if (name != null) { ServerName = name; } } }
/* see ISessionCache */ public SSLSessionParameters Retrieve(byte[] id, string serverName) { lock (mutex) { int x; if (!spx.TryGetValue(IDToString(id), out x)) { return(null); } SSLSessionParameters sp = data[x]; if ((x + 1) < count) { Array.Copy(data, x + 1, data, x, count - x - 1); data[count - 1] = sp; } return(sp); } }
bool ParseClientHello(out bool resume) { resume = false; int mt; byte[] msg = ReadHandshakeMessage(out mt, FirstHandshakeDone); if (msg == null) { /* * Client rejected renegotiation attempt. This cannot * happen if we are invoked from * ProcessExtraHandshake() because that method is * invoked only when there is buffered handshake * data. */ return(false); } if (mt != SSL.CLIENT_HELLO) { throw new SSLException(string.Format("Unexpected" + " handshake message {0} (expecting a" + " ClientHello)", mt)); } /* * Maximum protocol version supported by the client. */ if (msg.Length < 35) { throw new SSLException("Invalid ClientHello"); } ClientVersionMax = IO.Dec16be(msg, 0); if (ClientVersionMax < VersionMin) { throw new SSLException(string.Format( "No acceptable version (client max = 0x{0:X4})", ClientVersionMax)); } /* * Client random (32 bytes). */ Array.Copy(msg, 2, clientRandom, 0, 32); /* * Session ID sent by the client: at most 32 bytes. */ int idLen = msg[34]; int off = 35; if (idLen > 32 || (off + idLen) > msg.Length) { throw new SSLException("Invalid ClientHello"); } byte[] clientSessionID = new byte[idLen]; Array.Copy(msg, off, clientSessionID, 0, idLen); off += idLen; /* * List of client cipher suites. */ if ((off + 2) > msg.Length) { throw new SSLException("Invalid ClientHello"); } int csLen = IO.Dec16be(msg, off); off += 2; if ((off + csLen) > msg.Length) { throw new SSLException("Invalid ClientHello"); } List <int> clientSuites = new List <int>(); bool seenReneg = false; while (csLen > 0) { int cs = IO.Dec16be(msg, off); off += 2; csLen -= 2; if (cs == SSL.FALLBACK_SCSV) { if (ClientVersionMax < VersionMax) { throw new SSLException( "Undue fallback detected"); } } else if (cs == SSL.EMPTY_RENEGOTIATION_INFO_SCSV) { if (FirstHandshakeDone) { throw new SSLException( "Reneg SCSV in renegotiation"); } seenReneg = true; } else { clientSuites.Add(cs); } } /* * List of compression methods. We only accept method 0 * (no compression). */ if ((off + 1) > msg.Length) { throw new SSLException("Invalid ClientHello"); } int compLen = msg[off++]; if ((off + compLen) > msg.Length) { throw new SSLException("Invalid ClientHello"); } bool foundUncompressed = false; while (compLen-- > 0) { if (msg[off++] == 0x00) { foundUncompressed = true; } } if (!foundUncompressed) { throw new SSLException("No common compression support"); } /* * Extensions. */ ClientHashAndSign = null; ClientCurves = null; if (off < msg.Length) { if ((off + 2) > msg.Length) { throw new SSLException("Invalid ClientHello"); } int tlen = IO.Dec16be(msg, off); off += 2; if ((off + tlen) != msg.Length) { throw new SSLException("Invalid ClientHello"); } while (off < msg.Length) { if ((off + 4) > msg.Length) { throw new SSLException( "Invalid ClientHello"); } int etype = IO.Dec16be(msg, off); int elen = IO.Dec16be(msg, off + 2); off += 4; if ((off + elen) > msg.Length) { throw new SSLException( "Invalid ClientHello"); } switch (etype) { case 0x0000: ParseExtSNI(msg, off, elen); break; case 0x000D: ParseExtSignatures(msg, off, elen); break; case 0x000A: ParseExtCurves(msg, off, elen); break; case 0xFF01: ParseExtSecureReneg(msg, off, elen); seenReneg = true; break; // Max Frag Length // ALPN // FIXME } off += elen; } } /* * If we are renegotiating and we did not see the * Secure Renegotiation extension, then this is an error. */ if (FirstHandshakeDone && !seenReneg) { throw new SSLException( "Missing Secure Renegotiation extension"); } /* * Use prescribed default values for supported algorithms * and curves, when not otherwise advertised by the client. */ if (ClientCurves == null) { ClientCurves = new List <int>(); foreach (int cs in clientSuites) { if (SSL.IsECDH(cs) || SSL.IsECDHE(cs)) { ClientCurves.Add(SSL.NIST_P256); break; } } } if (ClientHashAndSign == null) { bool withRSA = false; bool withECDSA = false; foreach (int cs in clientSuites) { if (SSL.IsRSA(cs) || SSL.IsECDH_RSA(cs) || SSL.IsECDHE_RSA(cs)) { withRSA = true; } if (SSL.IsECDH_ECDSA(cs) || SSL.IsECDHE_ECDSA(cs)) { withECDSA = true; } } ClientHashAndSign = new List <int>(); if (withRSA) { ClientHashAndSign.Add(SSL.RSA_SHA1); } if (withECDSA) { ClientHashAndSign.Add(SSL.ECDSA_SHA1); } } /* * Filter curves and algorithms with regards to our own * configuration. */ CommonCurves = FilterList(ClientCurves, SupportedCurves, EnforceServerOrder); ClientHashAndSign = FilterList(ClientHashAndSign, SupportedHashAndSign, EnforceServerOrder); /* * Selected protocol version (can be overridden by * resumption). */ Version = Math.Min(ClientVersionMax, VersionMax); string forcedVersion = GetQuirkString("forceVersion"); if (forcedVersion != null) { switch (forcedVersion) { case "TLS10": Version = SSL.TLS10; break; case "TLS11": Version = SSL.TLS11; break; case "TLS12": Version = SSL.TLS12; break; default: throw new Exception(string.Format( "Unknown forced version: '{0}'", forcedVersion)); } } /* * Recompute list of acceptable cipher suites. We keep * only suites which are common to the client and server, * with some extra filters. * * Note that when using static ECDH, it is up to the * policy callback to determine whether the curves match * the contents of the certificate. * * We also build a list of common suites for session * resumption: this one may include suites whose * asymmetric crypto is not supported, because session * resumption uses only symmetric crypto. */ CommonCipherSuites = new List <int>(); List <int> commonSuitesResume = new List <int>(); bool canTLS12 = Version >= SSL.TLS12; bool mustTLS12 = false; if (GetQuirkBool("forceTls12CipherSuite")) { canTLS12 = true; mustTLS12 = true; } bool canSignRSA; bool canSignECDSA; if (Version >= SSL.TLS12) { canSignRSA = false; canSignECDSA = false; foreach (int alg in ClientHashAndSign) { int sa = alg & 0xFF; switch (sa) { case SSL.RSA: canSignRSA = true; break; case SSL.ECDSA: canSignECDSA = true; break; } } } else { /* * For pre-1.2, the hash-and-sign configuration does * not matter, only the cipher suites themselves. So * we claim support of both RSA and ECDSA signatures * to avoid trimming the list too much. */ canSignRSA = true; canSignECDSA = true; } bool canECDHE = CommonCurves.Count > 0; foreach (int cs in clientSuites) { if (!canTLS12 && SSL.IsTLS12(cs)) { continue; } if (mustTLS12 && !SSL.IsTLS12(cs)) { continue; } commonSuitesResume.Add(cs); if (!canECDHE && SSL.IsECDHE(cs)) { continue; } if (!canSignRSA && SSL.IsECDHE_RSA(cs)) { continue; } if (!canSignECDSA && SSL.IsECDHE_ECDSA(cs)) { continue; } CommonCipherSuites.Add(cs); } CommonCipherSuites = FilterList(CommonCipherSuites, SupportedCipherSuites, EnforceServerOrder); commonSuitesResume = FilterList(commonSuitesResume, SupportedCipherSuites, EnforceServerOrder); /* * If resuming, then use the remembered session parameters, * but only if they are compatible with what the client * sent AND what we currently support. */ SSLSessionParameters sp = null; if (idLen > 0 && !NoResume && SessionCache != null) { sp = SessionCache.Retrieve( clientSessionID, ServerName); if (sp != null && sp.ServerName != null && ServerName != null) { /* * When resuming a session, if there is * an explicit name sent by the client, * and the cached parameters also include * an explicit name, then both names * shall match. */ string s1 = sp.ServerName.ToLowerInvariant(); string s2 = ServerName.ToLowerInvariant(); if (s1 != s2) { sp = null; } } } if (sp != null) { bool resumeOK = true; if (sp.Version < VersionMin || sp.Version > VersionMax || sp.Version > ClientVersionMax) { resumeOK = false; } if (!commonSuitesResume.Contains(sp.CipherSuite)) { resumeOK = false; } if (resumeOK) { /* * Session resumption is acceptable. */ resume = true; sessionID = clientSessionID; Version = sp.Version; CipherSuite = sp.CipherSuite; sessionID = clientSessionID; SetMasterSecret(sp.MasterSecret); return(true); } } /* * Not resuming. Let's select parameters. * Protocol version was already set. */ if (CommonCipherSuites.Count == 0) { throw new SSLException("No common cipher suite"); } serverChoices = ServerPolicy.Apply(this); CipherSuite = serverChoices.GetCipherSuite(); /* * We create a new session ID, even if we don't have a * session cache, because the session parameters could * be extracted manually by the application. */ sessionID = new byte[32]; RNG.GetBytes(sessionID); return(true); }