1f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch/*
2f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * libjingle
3f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * Copyright 2004--2008, Google Inc.
4f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch *
5f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * Redistribution and use in source and binary forms, with or without
6f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * modification, are permitted provided that the following conditions are met:
7f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch *
8f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch *  1. Redistributions of source code must retain the above copyright notice,
9f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch *     this list of conditions and the following disclaimer.
10f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch *  2. Redistributions in binary form must reproduce the above copyright notice,
11f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch *     this list of conditions and the following disclaimer in the documentation
12f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch *     and/or other materials provided with the distribution.
13f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch *  3. The name of the author may not be used to endorse or promote products
14f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch *     derived from this software without specific prior written permission.
15f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch *
16f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch */
27f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
28f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// SecureTunnelSessionClient and SecureTunnelSession implementation.
29f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
30f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/session/tunnel/securetunnelsessionclient.h"
31f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/base/basicdefs.h"
32f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/base/basictypes.h"
33f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/base/common.h"
34f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/base/helpers.h"
35f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/base/logging.h"
36f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/base/stringutils.h"
37f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/base/sslidentity.h"
38f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/base/sslstreamadapter.h"
39f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/p2p/base/transportchannel.h"
40f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/xmllite/xmlelement.h"
41f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#include "talk/session/tunnel/pseudotcpchannel.h"
42f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
43f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochnamespace cricket {
44f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
453345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// XML elements and namespaces for XMPP stanzas used in content exchanges.
46f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
47f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochconst std::string NS_SECURE_TUNNEL("http://www.google.com/talk/securetunnel");
48f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochconst buzz::QName QN_SECURE_TUNNEL_DESCRIPTION(NS_SECURE_TUNNEL,
49f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch                                               "description");
50f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochconst buzz::QName QN_SECURE_TUNNEL_TYPE(NS_SECURE_TUNNEL, "type");
51f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochconst buzz::QName QN_SECURE_TUNNEL_CLIENT_CERT(NS_SECURE_TUNNEL,
52f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch                                               "client-cert");
53f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochconst buzz::QName QN_SECURE_TUNNEL_SERVER_CERT(NS_SECURE_TUNNEL,
54f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch                                               "server-cert");
553345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickconst std::string CN_SECURE_TUNNEL("securetunnel");
56f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
573345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// SecureTunnelContentDescription
58f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
593345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// TunnelContentDescription is extended to hold string forms of the
60f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// client and server certificate, PEM encoded.
61f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
623345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickstruct SecureTunnelContentDescription : public ContentDescription {
63f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  std::string description;
64f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  std::string client_pem_certificate;
65f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  std::string server_pem_certificate;
66f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
673345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  SecureTunnelContentDescription(const std::string& desc,
68f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch                                 const std::string& client_pem_cert,
69f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch                                 const std::string& server_pem_cert)
70f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      : description(desc),
71f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch        client_pem_certificate(client_pem_cert),
72f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch        server_pem_certificate(server_pem_cert) {
73f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  }
74f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch};
75f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
76f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// SecureTunnelSessionClient
77f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
78f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen MurdochSecureTunnelSessionClient::SecureTunnelSessionClient(
79f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    const buzz::Jid& jid, SessionManager* manager)
80f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    : TunnelSessionClient(jid, manager, NS_SECURE_TUNNEL) {
81f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
82f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
83f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochvoid SecureTunnelSessionClient::SetIdentity(talk_base::SSLIdentity* identity) {
84f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ASSERT(identity_.get() == NULL);
85f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  identity_.reset(identity);
86f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
87f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
88f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochbool SecureTunnelSessionClient::GenerateIdentity() {
89f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ASSERT(identity_.get() == NULL);
90f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  identity_.reset(talk_base::SSLIdentity::Generate(
91f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      // The name on the certificate does not matter: the peer will
92f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      // make sure the cert it gets during SSL negotiation matches the
93f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      // one it got from XMPP. It would be neat to put something
94f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      // recognizable in there such as the JID, except this will show
95f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      // in clear during the SSL negotiation and so it could be a
96f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      // privacy issue. Specifying an empty string here causes
97f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      // it to use a random string.
98f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#ifdef _DEBUG
99f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      jid().Str()
100f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#else
101f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      ""
102f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch#endif
103f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      ));
104f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  if (identity_.get() == NULL) {
105f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    LOG(LS_ERROR) << "Failed to generate SSL identity";
106f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    return false;
107f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  }
108f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  return true;
109f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
110f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
111f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochtalk_base::SSLIdentity& SecureTunnelSessionClient::GetIdentity() const {
112f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ASSERT(identity_.get() != NULL);
113f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  return *identity_;
114f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
115f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
116f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// Parses a certificate from a PEM encoded string.
117f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// Returns NULL on failure.
118f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// The caller is responsible for freeing the returned object.
119f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochstatic talk_base::SSLCertificate* ParseCertificate(
120f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    const std::string& pem_cert) {
121f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  if (pem_cert.empty())
122f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    return NULL;
123f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  return talk_base::SSLCertificate::FromPEMString(pem_cert, NULL);
124f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
125f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
126f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen MurdochTunnelSession* SecureTunnelSessionClient::MakeTunnelSession(
127f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    Session* session, talk_base::Thread* stream_thread,
128f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    TunnelSessionRole role) {
129f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  return new SecureTunnelSession(this, session, stream_thread, role);
130f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
131f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
132731df977c0511bca2206b5f333555b1205ff1f43Iain Merrickbool FindSecureTunnelContent(const cricket::SessionDescription* sdesc,
133731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick                             std::string* name,
134731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick                             const SecureTunnelContentDescription** content) {
1353345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  const ContentInfo* cinfo = sdesc->FirstContentByType(NS_SECURE_TUNNEL);
1363345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  if (cinfo == NULL)
137731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick    return false;
1383345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
139731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  *name = cinfo->name;
140731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  *content = static_cast<const SecureTunnelContentDescription*>(
1413345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick      cinfo->description);
142731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  return true;
1433345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick}
1443345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
145f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochvoid SecureTunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid,
146f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch                                                 Session *session) {
147731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  std::string content_name;
148731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  const SecureTunnelContentDescription* content = NULL;
149731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  if (!FindSecureTunnelContent(session->remote_description(),
150731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick                               &content_name, &content)) {
151731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick    ASSERT(false);
152731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  }
1533345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
154f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // Validate the certificate
155f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  talk_base::scoped_ptr<talk_base::SSLCertificate> peer_cert(
1563345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick      ParseCertificate(content->client_pem_certificate));
157f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  if (peer_cert.get() == NULL) {
158f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    LOG(LS_ERROR)
159f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch        << "Rejecting incoming secure tunnel with invalid cetificate";
160f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    DeclineTunnel(session);
161f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    return;
162f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  }
163f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // If there were a convenient place we could have cached the
164f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // peer_cert so as not to have to parse it a second time when
165f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // configuring the tunnel.
1663345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  SignalIncomingTunnel(this, jid, content->description, session);
167f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
168f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
169f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// The XML representation of a session initiation request (XMPP IQ),
1703345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick// containing the initiator's SecureTunnelContentDescription,
171f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// looks something like this:
172f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// <iq from="INITIATOR@gmail.com/pcpE101B7F4"
173f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       to="RECIPIENT@gmail.com/pcp8B87F0A3"
174f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       type="set" id="3">
175f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//   <session xmlns="http://www.google.com/session"
176f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       type="initiate" id="2508605813"
177f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       initiator="INITIATOR@gmail.com/pcpE101B7F4">
178f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//     <description xmlns="http://www.google.com/talk/securetunnel">
179f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       <type>send:filename</type>
180f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       <client-cert>
181f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//         -----BEGIN CERTIFICATE-----
182f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//         INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
183f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//         -----END CERTIFICATE-----
184f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       </client-cert>
185f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//     </description>
186f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//     <transport xmlns="http://www.google.com/transport/p2p"/>
187f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//   </session>
188f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// </iq>
189f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
190f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// The session accept iq, containing the recipient's certificate and
191f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// echoing the initiator's certificate, looks something like this:
192f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// <iq from="RECIPIENT@gmail.com/pcpE101B7F4"
193f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//     to="INITIATOR@gmail.com/pcpE101B7F4"
194f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//     type="set" id="5">
195f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//   <session xmlns="http://www.google.com/session"
196f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       type="accept" id="2508605813"
197f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       initiator="sdoyon911@gmail.com/pcpE101B7F4">
198f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//     <description xmlns="http://www.google.com/talk/securetunnel">
199f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       <type>send:FILENAME</type>
200f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       <client-cert>
201f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//         -----BEGIN CERTIFICATE-----
202f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//         INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
203f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//         -----END CERTIFICATE-----
204f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       </client-cert>
205f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       <server-cert>
206f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//         -----BEGIN CERTIFICATE-----
207f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//         RECIPIENT'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
208f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//         -----END CERTIFICATE-----
209f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//       </server-cert>
210f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//     </description>
211f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch//   </session>
212f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// </iq>
213f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
214f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
215731df977c0511bca2206b5f333555b1205ff1f43Iain Merrickbool SecureTunnelSessionClient::ParseContent(SignalingProtocol protocol,
216731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick                                             const buzz::XmlElement* elem,
2173345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick                                             const ContentDescription** content,
2183345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick                                             ParseError* error) {
2193345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  const buzz::XmlElement* type_elem = elem->FirstNamed(QN_SECURE_TUNNEL_TYPE);
2203345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
221f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  if (type_elem == NULL)
222f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    // Missing mandatory XML element.
2233345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick    return false;
2243345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
225f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // Here we consider the certificate components to be optional. In
226f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // practice the client certificate is always present, and the server
227f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // certificate is initially missing from the session description
228f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // sent during session initiation. OnAccept() will enforce that we
229f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // have a certificate for our peer.
230f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  const buzz::XmlElement* client_cert_elem =
2313345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick      elem->FirstNamed(QN_SECURE_TUNNEL_CLIENT_CERT);
232f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  const buzz::XmlElement* server_cert_elem =
2333345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick      elem->FirstNamed(QN_SECURE_TUNNEL_SERVER_CERT);
2343345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  *content = new SecureTunnelContentDescription(
235f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      type_elem->BodyText(),
236f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      client_cert_elem ? client_cert_elem->BodyText() : "",
237f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      server_cert_elem ? server_cert_elem->BodyText() : "");
2383345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  return true;
239f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
240f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
2413345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrickbool SecureTunnelSessionClient::WriteContent(
242731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick    SignalingProtocol protocol, const ContentDescription* untyped_content,
2433345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick    buzz::XmlElement** elem, WriteError* error) {
2443345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  const SecureTunnelContentDescription* content =
2453345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick      static_cast<const SecureTunnelContentDescription*>(untyped_content);
2463345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
247f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  buzz::XmlElement* root =
248f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      new buzz::XmlElement(QN_SECURE_TUNNEL_DESCRIPTION, true);
249f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  buzz::XmlElement* type_elem = new buzz::XmlElement(QN_SECURE_TUNNEL_TYPE);
2503345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  type_elem->SetBodyText(content->description);
251f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  root->AddElement(type_elem);
2523345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  if (!content->client_pem_certificate.empty()) {
253f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    buzz::XmlElement* client_cert_elem =
254f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch        new buzz::XmlElement(QN_SECURE_TUNNEL_CLIENT_CERT);
2553345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick    client_cert_elem->SetBodyText(content->client_pem_certificate);
256f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    root->AddElement(client_cert_elem);
257f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  }
2583345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  if (!content->server_pem_certificate.empty()) {
259f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    buzz::XmlElement* server_cert_elem =
260f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch        new buzz::XmlElement(QN_SECURE_TUNNEL_SERVER_CERT);
2613345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick    server_cert_elem->SetBodyText(content->server_pem_certificate);
262f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    root->AddElement(server_cert_elem);
263f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  }
2643345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  *elem = root;
2653345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  return true;
2663345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick}
2673345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
2683345a6884c488ff3a535c2c9acdd33d74b37e311Iain MerrickSessionDescription* NewSecureTunnelSessionDescription(
269731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick    const std::string& content_name, const ContentDescription* content) {
2703345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  SessionDescription* sdesc = new SessionDescription();
271731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  sdesc->AddContent(content_name, NS_SECURE_TUNNEL, content);
2723345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  return sdesc;
273f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
274f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
2753345a6884c488ff3a535c2c9acdd33d74b37e311Iain MerrickSessionDescription* SecureTunnelSessionClient::CreateOffer(
276f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    const buzz::Jid &jid, const std::string &description) {
277f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // We are the initiator so we are the client. Put our cert into the
278f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // description.
279f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  std::string pem_cert = GetIdentity().certificate().ToPEMString();
2803345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  return NewSecureTunnelSessionDescription(
281731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick      CN_SECURE_TUNNEL,
2823345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick      new SecureTunnelContentDescription(description, pem_cert, ""));
283f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
284f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
2853345a6884c488ff3a535c2c9acdd33d74b37e311Iain MerrickSessionDescription* SecureTunnelSessionClient::CreateAnswer(
2863345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick    const SessionDescription* offer) {
287731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  std::string content_name;
288731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  const SecureTunnelContentDescription* offer_tunnel = NULL;
289731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  if (!FindSecureTunnelContent(offer, &content_name, &offer_tunnel))
2903345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick    return NULL;
2913345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
292f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // We are accepting a session request. We need to add our cert, the
293f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // server cert, into the description. The client cert was validated
294f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // in OnIncomingTunnel().
2953345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  ASSERT(!offer_tunnel->client_pem_certificate.empty());
2963345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick  return NewSecureTunnelSessionDescription(
297731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick      content_name,
2983345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick      new SecureTunnelContentDescription(
2993345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick          offer_tunnel->description,
3003345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick          offer_tunnel->client_pem_certificate,
3013345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick          GetIdentity().certificate().ToPEMString()));
302f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
303f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
304f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch// SecureTunnelSession
305f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
306f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen MurdochSecureTunnelSession::SecureTunnelSession(
307f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    SecureTunnelSessionClient* client, Session* session,
308f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    talk_base::Thread* stream_thread, TunnelSessionRole role)
309f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    : TunnelSession(client, session, stream_thread),
310f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      role_(role) {
311f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
312f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
313f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochtalk_base::StreamInterface* SecureTunnelSession::MakeSecureStream(
314f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    talk_base::StreamInterface* stream) {
315f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  talk_base::SSLStreamAdapter* ssl_stream =
316f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      talk_base::SSLStreamAdapter::Create(stream);
317f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  talk_base::SSLIdentity* identity =
318f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      static_cast<SecureTunnelSessionClient*>(client_)->
319f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      GetIdentity().GetReference();
320f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ssl_stream->SetIdentity(identity);
321f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  if (role_ == RESPONDER)
322f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    ssl_stream->SetServerRole();
323f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ssl_stream->StartSSLWithPeer();
324f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
325f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // SSL negotiation will start on the stream as soon as it
326f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // opens. However our SSLStreamAdapter still hasn't been told what
327f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // certificate to allow for our peer. If we are the initiator, we do
328f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // not have the peer's certificate yet: we will obtain it from the
329f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // session accept message which we will receive later (see
330f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // OnAccept()). We won't Connect() the PseudoTcpChannel until we get
331f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // that, so the stream will stay closed until then.  Keep a handle
332f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // on the streem so we can configure the peer certificate later.
333f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ssl_stream_reference_.reset(new talk_base::StreamReference(ssl_stream));
334f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  return ssl_stream_reference_->NewReference();
335f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
336f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
337f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochtalk_base::StreamInterface* SecureTunnelSession::GetStream() {
338f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ASSERT(channel_ != NULL);
339f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ASSERT(ssl_stream_reference_.get() == NULL);
340f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  return MakeSecureStream(channel_->GetStream());
341f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
342f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
343f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdochvoid SecureTunnelSession::OnAccept() {
344f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // We have either sent or received a session accept: it's time to
345f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // connect the tunnel. First we must set the peer certificate.
346f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ASSERT(channel_ != NULL);
347f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ASSERT(session_ != NULL);
348731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  std::string content_name;
349731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  const SecureTunnelContentDescription* remote_tunnel = NULL;
350731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  if (!FindSecureTunnelContent(session_->remote_description(),
351731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick                               &content_name, &remote_tunnel)) {
352731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick    session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
353731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick    return;
354731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  }
3553345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick
356f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  const std::string& cert_pem =
3573345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick      role_ == INITIATOR ? remote_tunnel->server_pem_certificate :
3583345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick                           remote_tunnel->client_pem_certificate;
359f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  talk_base::SSLCertificate* peer_cert =
360f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      ParseCertificate(cert_pem);
361f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  if (peer_cert == NULL) {
362f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    ASSERT(role_ == INITIATOR);  // when RESPONDER we validated it earlier
363f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    LOG(LS_ERROR)
364f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch        << "Rejecting secure tunnel accept with invalid cetificate";
365731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick    session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
366f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch    return;
367f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  }
368f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ASSERT(ssl_stream_reference_.get() != NULL);
369f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  talk_base::SSLStreamAdapter* ssl_stream =
370f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch      static_cast<talk_base::SSLStreamAdapter*>(
371f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch          ssl_stream_reference_->GetStream());
372f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ssl_stream->SetPeerCertificate(peer_cert);  // pass ownership of certificate.
373f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // We no longer need our handle to the ssl stream.
374f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  ssl_stream_reference_.reset();
375f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  LOG(LS_INFO) << "Connecting tunnel";
376f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // This will try to connect the PseudoTcpChannel. If and when that
377f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // succeeds, then ssl negotiation will take place, and when that
378f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch  // succeeds, the tunnel stream will finally open.
379731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick  VERIFY(channel_->Connect(content_name, "tcp"));
380f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}
381f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch
382f74420b3285b9fe04a7e00aa3b8c0ab07ea344bcBen Murdoch}  // namespace cricket
383