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