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 |