3
* Copyright 2004--2008, Google Inc.
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions are met:
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.
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.
28
// SecureTunnelSessionClient and SecureTunnelSession implementation.
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"
45
// XML elements and namespaces for XMPP stanzas used in content exchanges.
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";
58
// SecureTunnelContentDescription
60
// TunnelContentDescription is extended to hold string forms of the
61
// client and server certificate, PEM encoded.
63
struct SecureTunnelContentDescription : public ContentDescription {
64
std::string description;
65
std::string client_pem_certificate;
66
std::string server_pem_certificate;
68
SecureTunnelContentDescription(const std::string& desc,
69
const std::string& client_pem_cert,
70
const std::string& server_pem_cert)
72
client_pem_certificate(client_pem_cert),
73
server_pem_certificate(server_pem_cert) {
75
virtual ContentDescription* Copy() const {
76
return new SecureTunnelContentDescription(*this);
80
// SecureTunnelSessionClient
82
SecureTunnelSessionClient::SecureTunnelSessionClient(
83
const buzz::Jid& jid, SessionManager* manager)
84
: TunnelSessionClient(jid, manager, NS_SECURE_TUNNEL) {
87
void SecureTunnelSessionClient::SetIdentity(talk_base::SSLIdentity* identity) {
88
ASSERT(identity_.get() == NULL);
89
identity_.reset(identity);
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.
108
if (identity_.get() == NULL) {
109
LOG(LS_ERROR) << "Failed to generate SSL identity";
115
talk_base::SSLIdentity& SecureTunnelSessionClient::GetIdentity() const {
116
ASSERT(identity_.get() != NULL);
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())
127
return talk_base::SSLCertificate::FromPEMString(pem_cert, NULL);
130
TunnelSession* SecureTunnelSessionClient::MakeTunnelSession(
131
Session* session, talk_base::Thread* stream_thread,
132
TunnelSessionRole role) {
133
return new SecureTunnelSession(this, session, stream_thread, role);
136
bool FindSecureTunnelContent(const cricket::SessionDescription* sdesc,
138
const SecureTunnelContentDescription** content) {
139
const ContentInfo* cinfo = sdesc->FirstContentByType(NS_SECURE_TUNNEL);
144
*content = static_cast<const SecureTunnelContentDescription*>(
149
void SecureTunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid,
151
std::string content_name;
152
const SecureTunnelContentDescription* content = NULL;
153
if (!FindSecureTunnelContent(session->remote_description(),
154
&content_name, &content)) {
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) {
163
<< "Rejecting incoming secure tunnel with invalid cetificate";
164
DeclineTunnel(session);
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);
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>
185
// -----BEGIN CERTIFICATE-----
186
// INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
187
// -----END CERTIFICATE-----
190
// <transport xmlns="http://www.google.com/transport/p2p"/>
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>
205
// -----BEGIN CERTIFICATE-----
206
// INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
207
// -----END CERTIFICATE-----
210
// -----BEGIN CERTIFICATE-----
211
// RECIPIENT'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
212
// -----END CERTIFICATE-----
219
bool SecureTunnelSessionClient::ParseContent(SignalingProtocol protocol,
220
const buzz::XmlElement* elem,
221
const ContentDescription** content,
223
const buzz::XmlElement* type_elem = elem->FirstNamed(QN_SECURE_TUNNEL_TYPE);
225
if (type_elem == NULL)
226
// Missing mandatory XML element.
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() : "");
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);
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);
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);
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);
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
283
std::string pem_cert = GetIdentity().certificate().ToPEMString();
284
return NewSecureTunnelSessionDescription(
286
new SecureTunnelContentDescription(description, pem_cert, ""));
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))
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(
302
new SecureTunnelContentDescription(
303
offer_tunnel->description,
304
offer_tunnel->client_pem_certificate,
305
GetIdentity().certificate().ToPEMString()));
308
// SecureTunnelSession
310
SecureTunnelSession::SecureTunnelSession(
311
SecureTunnelSessionClient* client, Session* session,
312
talk_base::Thread* stream_thread, TunnelSessionRole role)
313
: TunnelSession(client, session, stream_thread),
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();
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();
341
talk_base::StreamInterface* SecureTunnelSession::GetStream() {
342
ASSERT(channel_ != NULL);
343
ASSERT(ssl_stream_reference_.get() == NULL);
344
return MakeSecureStream(channel_->GetStream());
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);
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
368
<< "Rejecting secure tunnel accept with invalid cetificate";
369
session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
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"));
386
} // namespace cricket