1/*
2 * libjingle
3 * Copyright 2004--2008, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  1. Redistributions of source code must retain the above copyright notice,
9 *     this list of conditions and the following disclaimer.
10 *  2. Redistributions in binary form must reproduce the above copyright notice,
11 *     this list of conditions and the following disclaimer in the documentation
12 *     and/or other materials provided with the distribution.
13 *  3. The name of the author may not be used to endorse or promote products
14 *     derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28// SecureTunnelSessionClient and SecureTunnelSession implementation.
29
30#include "talk/session/tunnel/securetunnelsessionclient.h"
31#include "talk/base/basicdefs.h"
32#include "talk/base/basictypes.h"
33#include "talk/base/common.h"
34#include "talk/base/helpers.h"
35#include "talk/base/logging.h"
36#include "talk/base/stringutils.h"
37#include "talk/base/sslidentity.h"
38#include "talk/base/sslstreamadapter.h"
39#include "talk/p2p/base/transportchannel.h"
40#include "talk/xmllite/xmlelement.h"
41#include "talk/session/tunnel/pseudotcpchannel.h"
42
43namespace cricket {
44
45// XML elements and namespaces for XMPP stanzas used in content exchanges.
46
47const std::string NS_SECURE_TUNNEL("http://www.google.com/talk/securetunnel");
48const buzz::QName QN_SECURE_TUNNEL_DESCRIPTION(NS_SECURE_TUNNEL,
49                                               "description");
50const buzz::QName QN_SECURE_TUNNEL_TYPE(NS_SECURE_TUNNEL, "type");
51const buzz::QName QN_SECURE_TUNNEL_CLIENT_CERT(NS_SECURE_TUNNEL,
52                                               "client-cert");
53const buzz::QName QN_SECURE_TUNNEL_SERVER_CERT(NS_SECURE_TUNNEL,
54                                               "server-cert");
55const std::string CN_SECURE_TUNNEL("securetunnel");
56
57// SecureTunnelContentDescription
58
59// TunnelContentDescription is extended to hold string forms of the
60// client and server certificate, PEM encoded.
61
62struct SecureTunnelContentDescription : public ContentDescription {
63  std::string description;
64  std::string client_pem_certificate;
65  std::string server_pem_certificate;
66
67  SecureTunnelContentDescription(const std::string& desc,
68                                 const std::string& client_pem_cert,
69                                 const std::string& server_pem_cert)
70      : description(desc),
71        client_pem_certificate(client_pem_cert),
72        server_pem_certificate(server_pem_cert) {
73  }
74};
75
76// SecureTunnelSessionClient
77
78SecureTunnelSessionClient::SecureTunnelSessionClient(
79    const buzz::Jid& jid, SessionManager* manager)
80    : TunnelSessionClient(jid, manager, NS_SECURE_TUNNEL) {
81}
82
83void SecureTunnelSessionClient::SetIdentity(talk_base::SSLIdentity* identity) {
84  ASSERT(identity_.get() == NULL);
85  identity_.reset(identity);
86}
87
88bool SecureTunnelSessionClient::GenerateIdentity() {
89  ASSERT(identity_.get() == NULL);
90  identity_.reset(talk_base::SSLIdentity::Generate(
91      // The name on the certificate does not matter: the peer will
92      // make sure the cert it gets during SSL negotiation matches the
93      // one it got from XMPP. It would be neat to put something
94      // recognizable in there such as the JID, except this will show
95      // in clear during the SSL negotiation and so it could be a
96      // privacy issue. Specifying an empty string here causes
97      // it to use a random string.
98#ifdef _DEBUG
99      jid().Str()
100#else
101      ""
102#endif
103      ));
104  if (identity_.get() == NULL) {
105    LOG(LS_ERROR) << "Failed to generate SSL identity";
106    return false;
107  }
108  return true;
109}
110
111talk_base::SSLIdentity& SecureTunnelSessionClient::GetIdentity() const {
112  ASSERT(identity_.get() != NULL);
113  return *identity_;
114}
115
116// Parses a certificate from a PEM encoded string.
117// Returns NULL on failure.
118// The caller is responsible for freeing the returned object.
119static talk_base::SSLCertificate* ParseCertificate(
120    const std::string& pem_cert) {
121  if (pem_cert.empty())
122    return NULL;
123  return talk_base::SSLCertificate::FromPEMString(pem_cert, NULL);
124}
125
126TunnelSession* SecureTunnelSessionClient::MakeTunnelSession(
127    Session* session, talk_base::Thread* stream_thread,
128    TunnelSessionRole role) {
129  return new SecureTunnelSession(this, session, stream_thread, role);
130}
131
132bool FindSecureTunnelContent(const cricket::SessionDescription* sdesc,
133                             std::string* name,
134                             const SecureTunnelContentDescription** content) {
135  const ContentInfo* cinfo = sdesc->FirstContentByType(NS_SECURE_TUNNEL);
136  if (cinfo == NULL)
137    return false;
138
139  *name = cinfo->name;
140  *content = static_cast<const SecureTunnelContentDescription*>(
141      cinfo->description);
142  return true;
143}
144
145void SecureTunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid,
146                                                 Session *session) {
147  std::string content_name;
148  const SecureTunnelContentDescription* content = NULL;
149  if (!FindSecureTunnelContent(session->remote_description(),
150                               &content_name, &content)) {
151    ASSERT(false);
152  }
153
154  // Validate the certificate
155  talk_base::scoped_ptr<talk_base::SSLCertificate> peer_cert(
156      ParseCertificate(content->client_pem_certificate));
157  if (peer_cert.get() == NULL) {
158    LOG(LS_ERROR)
159        << "Rejecting incoming secure tunnel with invalid cetificate";
160    DeclineTunnel(session);
161    return;
162  }
163  // If there were a convenient place we could have cached the
164  // peer_cert so as not to have to parse it a second time when
165  // configuring the tunnel.
166  SignalIncomingTunnel(this, jid, content->description, session);
167}
168
169// The XML representation of a session initiation request (XMPP IQ),
170// containing the initiator's SecureTunnelContentDescription,
171// looks something like this:
172// <iq from="INITIATOR@gmail.com/pcpE101B7F4"
173//       to="RECIPIENT@gmail.com/pcp8B87F0A3"
174//       type="set" id="3">
175//   <session xmlns="http://www.google.com/session"
176//       type="initiate" id="2508605813"
177//       initiator="INITIATOR@gmail.com/pcpE101B7F4">
178//     <description xmlns="http://www.google.com/talk/securetunnel">
179//       <type>send:filename</type>
180//       <client-cert>
181//         -----BEGIN CERTIFICATE-----
182//         INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
183//         -----END CERTIFICATE-----
184//       </client-cert>
185//     </description>
186//     <transport xmlns="http://www.google.com/transport/p2p"/>
187//   </session>
188// </iq>
189
190// The session accept iq, containing the recipient's certificate and
191// echoing the initiator's certificate, looks something like this:
192// <iq from="RECIPIENT@gmail.com/pcpE101B7F4"
193//     to="INITIATOR@gmail.com/pcpE101B7F4"
194//     type="set" id="5">
195//   <session xmlns="http://www.google.com/session"
196//       type="accept" id="2508605813"
197//       initiator="sdoyon911@gmail.com/pcpE101B7F4">
198//     <description xmlns="http://www.google.com/talk/securetunnel">
199//       <type>send:FILENAME</type>
200//       <client-cert>
201//         -----BEGIN CERTIFICATE-----
202//         INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
203//         -----END CERTIFICATE-----
204//       </client-cert>
205//       <server-cert>
206//         -----BEGIN CERTIFICATE-----
207//         RECIPIENT'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
208//         -----END CERTIFICATE-----
209//       </server-cert>
210//     </description>
211//   </session>
212// </iq>
213
214
215bool SecureTunnelSessionClient::ParseContent(SignalingProtocol protocol,
216                                             const buzz::XmlElement* elem,
217                                             const ContentDescription** content,
218                                             ParseError* error) {
219  const buzz::XmlElement* type_elem = elem->FirstNamed(QN_SECURE_TUNNEL_TYPE);
220
221  if (type_elem == NULL)
222    // Missing mandatory XML element.
223    return false;
224
225  // Here we consider the certificate components to be optional. In
226  // practice the client certificate is always present, and the server
227  // certificate is initially missing from the session description
228  // sent during session initiation. OnAccept() will enforce that we
229  // have a certificate for our peer.
230  const buzz::XmlElement* client_cert_elem =
231      elem->FirstNamed(QN_SECURE_TUNNEL_CLIENT_CERT);
232  const buzz::XmlElement* server_cert_elem =
233      elem->FirstNamed(QN_SECURE_TUNNEL_SERVER_CERT);
234  *content = new SecureTunnelContentDescription(
235      type_elem->BodyText(),
236      client_cert_elem ? client_cert_elem->BodyText() : "",
237      server_cert_elem ? server_cert_elem->BodyText() : "");
238  return true;
239}
240
241bool SecureTunnelSessionClient::WriteContent(
242    SignalingProtocol protocol, const ContentDescription* untyped_content,
243    buzz::XmlElement** elem, WriteError* error) {
244  const SecureTunnelContentDescription* content =
245      static_cast<const SecureTunnelContentDescription*>(untyped_content);
246
247  buzz::XmlElement* root =
248      new buzz::XmlElement(QN_SECURE_TUNNEL_DESCRIPTION, true);
249  buzz::XmlElement* type_elem = new buzz::XmlElement(QN_SECURE_TUNNEL_TYPE);
250  type_elem->SetBodyText(content->description);
251  root->AddElement(type_elem);
252  if (!content->client_pem_certificate.empty()) {
253    buzz::XmlElement* client_cert_elem =
254        new buzz::XmlElement(QN_SECURE_TUNNEL_CLIENT_CERT);
255    client_cert_elem->SetBodyText(content->client_pem_certificate);
256    root->AddElement(client_cert_elem);
257  }
258  if (!content->server_pem_certificate.empty()) {
259    buzz::XmlElement* server_cert_elem =
260        new buzz::XmlElement(QN_SECURE_TUNNEL_SERVER_CERT);
261    server_cert_elem->SetBodyText(content->server_pem_certificate);
262    root->AddElement(server_cert_elem);
263  }
264  *elem = root;
265  return true;
266}
267
268SessionDescription* NewSecureTunnelSessionDescription(
269    const std::string& content_name, const ContentDescription* content) {
270  SessionDescription* sdesc = new SessionDescription();
271  sdesc->AddContent(content_name, NS_SECURE_TUNNEL, content);
272  return sdesc;
273}
274
275SessionDescription* SecureTunnelSessionClient::CreateOffer(
276    const buzz::Jid &jid, const std::string &description) {
277  // We are the initiator so we are the client. Put our cert into the
278  // description.
279  std::string pem_cert = GetIdentity().certificate().ToPEMString();
280  return NewSecureTunnelSessionDescription(
281      CN_SECURE_TUNNEL,
282      new SecureTunnelContentDescription(description, pem_cert, ""));
283}
284
285SessionDescription* SecureTunnelSessionClient::CreateAnswer(
286    const SessionDescription* offer) {
287  std::string content_name;
288  const SecureTunnelContentDescription* offer_tunnel = NULL;
289  if (!FindSecureTunnelContent(offer, &content_name, &offer_tunnel))
290    return NULL;
291
292  // We are accepting a session request. We need to add our cert, the
293  // server cert, into the description. The client cert was validated
294  // in OnIncomingTunnel().
295  ASSERT(!offer_tunnel->client_pem_certificate.empty());
296  return NewSecureTunnelSessionDescription(
297      content_name,
298      new SecureTunnelContentDescription(
299          offer_tunnel->description,
300          offer_tunnel->client_pem_certificate,
301          GetIdentity().certificate().ToPEMString()));
302}
303
304// SecureTunnelSession
305
306SecureTunnelSession::SecureTunnelSession(
307    SecureTunnelSessionClient* client, Session* session,
308    talk_base::Thread* stream_thread, TunnelSessionRole role)
309    : TunnelSession(client, session, stream_thread),
310      role_(role) {
311}
312
313talk_base::StreamInterface* SecureTunnelSession::MakeSecureStream(
314    talk_base::StreamInterface* stream) {
315  talk_base::SSLStreamAdapter* ssl_stream =
316      talk_base::SSLStreamAdapter::Create(stream);
317  talk_base::SSLIdentity* identity =
318      static_cast<SecureTunnelSessionClient*>(client_)->
319      GetIdentity().GetReference();
320  ssl_stream->SetIdentity(identity);
321  if (role_ == RESPONDER)
322    ssl_stream->SetServerRole();
323  ssl_stream->StartSSLWithPeer();
324
325  // SSL negotiation will start on the stream as soon as it
326  // opens. However our SSLStreamAdapter still hasn't been told what
327  // certificate to allow for our peer. If we are the initiator, we do
328  // not have the peer's certificate yet: we will obtain it from the
329  // session accept message which we will receive later (see
330  // OnAccept()). We won't Connect() the PseudoTcpChannel until we get
331  // that, so the stream will stay closed until then.  Keep a handle
332  // on the streem so we can configure the peer certificate later.
333  ssl_stream_reference_.reset(new talk_base::StreamReference(ssl_stream));
334  return ssl_stream_reference_->NewReference();
335}
336
337talk_base::StreamInterface* SecureTunnelSession::GetStream() {
338  ASSERT(channel_ != NULL);
339  ASSERT(ssl_stream_reference_.get() == NULL);
340  return MakeSecureStream(channel_->GetStream());
341}
342
343void SecureTunnelSession::OnAccept() {
344  // We have either sent or received a session accept: it's time to
345  // connect the tunnel. First we must set the peer certificate.
346  ASSERT(channel_ != NULL);
347  ASSERT(session_ != NULL);
348  std::string content_name;
349  const SecureTunnelContentDescription* remote_tunnel = NULL;
350  if (!FindSecureTunnelContent(session_->remote_description(),
351                               &content_name, &remote_tunnel)) {
352    session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
353    return;
354  }
355
356  const std::string& cert_pem =
357      role_ == INITIATOR ? remote_tunnel->server_pem_certificate :
358                           remote_tunnel->client_pem_certificate;
359  talk_base::SSLCertificate* peer_cert =
360      ParseCertificate(cert_pem);
361  if (peer_cert == NULL) {
362    ASSERT(role_ == INITIATOR);  // when RESPONDER we validated it earlier
363    LOG(LS_ERROR)
364        << "Rejecting secure tunnel accept with invalid cetificate";
365    session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
366    return;
367  }
368  ASSERT(ssl_stream_reference_.get() != NULL);
369  talk_base::SSLStreamAdapter* ssl_stream =
370      static_cast<talk_base::SSLStreamAdapter*>(
371          ssl_stream_reference_->GetStream());
372  ssl_stream->SetPeerCertificate(peer_cert);  // pass ownership of certificate.
373  // We no longer need our handle to the ssl stream.
374  ssl_stream_reference_.reset();
375  LOG(LS_INFO) << "Connecting tunnel";
376  // This will try to connect the PseudoTcpChannel. If and when that
377  // succeeds, then ssl negotiation will take place, and when that
378  // succeeds, the tunnel stream will finally open.
379  VERIFY(channel_->Connect(content_name, "tcp"));
380}
381
382}  // namespace cricket
383