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