| OLD | NEW |
| (Empty) |
| 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 "webrtc/p2p/base/transportchannel.h" | |
| 31 #include "webrtc/libjingle/session/tunnel/pseudotcpchannel.h" | |
| 32 #include "webrtc/libjingle/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 | |
| 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(rtc::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(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 | |
| 115 rtc::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 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 | |
| 130 TunnelSession* SecureTunnelSessionClient::MakeTunnelSession( | |
| 131 Session* session, rtc::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 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 | |
| 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 rtc::Thread* stream_thread, TunnelSessionRole role) | |
| 313 : TunnelSession(client, session, stream_thread), | |
| 314 role_(role) { | |
| 315 } | |
| 316 | |
| 317 rtc::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 | |
| 341 rtc::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 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 | |
| OLD | NEW |