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); 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 (at) gmail.com/pcpE101B7F4" 177 // to="RECIPIENT (at) gmail.com/pcp8B87F0A3" 178 // type="set" id="3"> 179 // <session xmlns="http://www.google.com/session" 180 // type="initiate" id="2508605813" 181 // initiator="INITIATOR (at) 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 (at) gmail.com/pcpE101B7F4" 197 // to="INITIATOR (at) gmail.com/pcpE101B7F4" 198 // type="set" id="5"> 199 // <session xmlns="http://www.google.com/session" 200 // type="accept" id="2508605813" 201 // initiator="sdoyon911 (at) 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 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, 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( 384 content_name, "tcp", ICE_CANDIDATE_COMPONENT_DEFAULT)); 385 } 386 387 } // namespace cricket 388