Home | History | Annotate | Download | only in tunnel
      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