~ubuntu-branches/ubuntu/saucy/kopete/saucy-proposed

« back to all changes in this revision

Viewing changes to protocols/jabber/googletalk/libjingle/talk/session/tunnel/securetunnelsessionclient.cc

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2013-06-21 02:22:39 UTC
  • Revision ID: package-import@ubuntu.com-20130621022239-63l3zc8p0nf26pt6
Tags: upstream-4.10.80
ImportĀ upstreamĀ versionĀ 4.10.80

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
43
namespace cricket {
 
44
 
 
45
// XML elements and namespaces for XMPP stanzas used in content exchanges.
 
46
 
 
47
const char NS_SECURE_TUNNEL[] = "http://www.google.com/talk/securetunnel";
 
48
const buzz::StaticQName QN_SECURE_TUNNEL_DESCRIPTION =
 
49
    { NS_SECURE_TUNNEL, "description" };
 
50
const buzz::StaticQName QN_SECURE_TUNNEL_TYPE =
 
51
    { NS_SECURE_TUNNEL, "type" };
 
52
const buzz::StaticQName QN_SECURE_TUNNEL_CLIENT_CERT =
 
53
    { NS_SECURE_TUNNEL, "client-cert" };
 
54
const buzz::StaticQName QN_SECURE_TUNNEL_SERVER_CERT =
 
55
    { NS_SECURE_TUNNEL, "server-cert" };
 
56
const 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
 
 
63
struct 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
 
 
82
SecureTunnelSessionClient::SecureTunnelSessionClient(
 
83
    const buzz::Jid& jid, SessionManager* manager)
 
84
    : TunnelSessionClient(jid, manager, NS_SECURE_TUNNEL) {
 
85
}
 
86
 
 
87
void SecureTunnelSessionClient::SetIdentity(talk_base::SSLIdentity* identity) {
 
88
  ASSERT(identity_.get() == NULL);
 
89
  identity_.reset(identity);
 
90
}
 
91
 
 
92
bool SecureTunnelSessionClient::GenerateIdentity() {
 
93
  ASSERT(identity_.get() == NULL);
 
94
  identity_.reset(talk_base::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
 
 
115
talk_base::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.
 
123
static talk_base::SSLCertificate* ParseCertificate(
 
124
    const std::string& pem_cert) {
 
125
  if (pem_cert.empty())
 
126
    return NULL;
 
127
  return talk_base::SSLCertificate::FromPEMString(pem_cert, NULL);
 
128
}
 
129
 
 
130
TunnelSession* SecureTunnelSessionClient::MakeTunnelSession(
 
131
    Session* session, talk_base::Thread* stream_thread,
 
132
    TunnelSessionRole role) {
 
133
  return new SecureTunnelSession(this, session, stream_thread, role);
 
134
}
 
135
 
 
136
bool 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
 
 
149
void 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
  talk_base::scoped_ptr<talk_base::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
 
 
219
bool SecureTunnelSessionClient::ParseContent(SignalingProtocol protocol,
 
220
                                             const buzz::XmlElement* elem,
 
221
                                             const 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
 
 
245
bool 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
 
 
272
SessionDescription* NewSecureTunnelSessionDescription(
 
273
    const std::string& content_name, const ContentDescription* content) {
 
274
  SessionDescription* sdesc = new SessionDescription();
 
275
  sdesc->AddContent(content_name, NS_SECURE_TUNNEL, content);
 
276
  return sdesc;
 
277
}
 
278
 
 
279
SessionDescription* 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
 
 
289
SessionDescription* 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
 
 
310
SecureTunnelSession::SecureTunnelSession(
 
311
    SecureTunnelSessionClient* client, Session* session,
 
312
    talk_base::Thread* stream_thread, TunnelSessionRole role)
 
313
    : TunnelSession(client, session, stream_thread),
 
314
      role_(role) {
 
315
}
 
316
 
 
317
talk_base::StreamInterface* SecureTunnelSession::MakeSecureStream(
 
318
    talk_base::StreamInterface* stream) {
 
319
  talk_base::SSLStreamAdapter* ssl_stream =
 
320
      talk_base::SSLStreamAdapter::Create(stream);
 
321
  talk_base::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 talk_base::StreamReference(ssl_stream));
 
338
  return ssl_stream_reference_->NewReference();
 
339
}
 
340
 
 
341
talk_base::StreamInterface* SecureTunnelSession::GetStream() {
 
342
  ASSERT(channel_ != NULL);
 
343
  ASSERT(ssl_stream_reference_.get() == NULL);
 
344
  return MakeSecureStream(channel_->GetStream());
 
345
}
 
346
 
 
347
void 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
  talk_base::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
  talk_base::SSLStreamAdapter* ssl_stream =
 
374
      static_cast<talk_base::SSLStreamAdapter*>(
 
375
          ssl_stream_reference_->GetStream());
 
376
  ssl_stream->SetPeerCertificate(peer_cert);  // pass ownership of certificate.
 
377
  // We no longer need our handle to the ssl stream.
 
378
  ssl_stream_reference_.reset();
 
379
  LOG(LS_INFO) << "Connecting tunnel";
 
380
  // This will try to connect the PseudoTcpChannel. If and when that
 
381
  // succeeds, then ssl negotiation will take place, and when that
 
382
  // succeeds, the tunnel stream will finally open.
 
383
  VERIFY(channel_->Connect(content_name, "tcp"));
 
384
}
 
385
 
 
386
}  // namespace cricket