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