1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/spdy/spdy_http_stream.h"
7 #include "crypto/ec_private_key.h"
8 #include "crypto/ec_signature_creator.h"
9 #include "crypto/signature_creator.h"
10 #include "net/base/asn1_util.h"
11 #include "net/base/default_server_bound_cert_store.h"
12 #include "net/http/http_response_headers.h"
13 #include "net/http/http_response_info.h"
14 #include "net/spdy/spdy_http_utils.h"
15 #include "net/spdy/spdy_session.h"
16 #include "net/spdy/spdy_test_util_spdy3.h"
17 #include "testing/gtest/include/gtest/gtest.h"
19 using namespace net::test_spdy3
;
23 class SpdyHttpStreamSpdy3Test
: public testing::Test
{
25 OrderedSocketData
* data() { return data_
.get(); }
27 SpdyHttpStreamSpdy3Test() {}
29 virtual void SetUp() {
30 SpdySession::set_default_protocol(kProtoSPDY3
);
33 virtual void TearDown() {
34 crypto::ECSignatureCreator::SetFactoryForTesting(NULL
);
35 MessageLoop::current()->RunAllPending();
38 int InitSession(MockRead
* reads
, size_t reads_count
,
39 MockWrite
* writes
, size_t writes_count
,
40 HostPortPair
& host_port_pair
) {
41 HostPortProxyPair
pair(host_port_pair
, ProxyServer::Direct());
42 data_
.reset(new OrderedSocketData(reads
, reads_count
,
43 writes
, writes_count
));
44 session_deps_
.socket_factory
->AddSocketDataProvider(data_
.get());
45 http_session_
= SpdySessionDependencies::SpdyCreateSession(&session_deps_
);
46 session_
= http_session_
->spdy_session_pool()->Get(pair
, BoundNetLog());
47 transport_params_
= new TransportSocketParams(host_port_pair
,
48 MEDIUM
, false, false);
49 TestCompletionCallback callback
;
50 scoped_ptr
<ClientSocketHandle
> connection(new ClientSocketHandle
);
51 EXPECT_EQ(ERR_IO_PENDING
,
52 connection
->Init(host_port_pair
.ToString(),
56 http_session_
->GetTransportSocketPool(
57 HttpNetworkSession::NORMAL_SOCKET_POOL
),
59 EXPECT_EQ(OK
, callback
.WaitForResult());
60 return session_
->InitializeWithSocket(connection
.release(), false, OK
);
63 void TestSendCredentials(
64 ServerBoundCertService
* server_bound_cert_service
,
65 const std::string
& cert
,
66 const std::string
& proof
,
67 SSLClientCertType type
);
69 SpdySessionDependencies session_deps_
;
70 scoped_ptr
<OrderedSocketData
> data_
;
71 scoped_refptr
<HttpNetworkSession
> http_session_
;
72 scoped_refptr
<SpdySession
> session_
;
73 scoped_refptr
<TransportSocketParams
> transport_params_
;
76 SpdyTestStateHelper spdy_state_
;
79 TEST_F(SpdyHttpStreamSpdy3Test
, SendRequest
) {
80 scoped_ptr
<SpdyFrame
> req(ConstructSpdyGet(NULL
, 0, false, 1, LOWEST
));
81 MockWrite writes
[] = {
82 CreateMockWrite(*req
.get(), 1),
84 scoped_ptr
<SpdyFrame
> resp(ConstructSpdyGetSynReply(NULL
, 0, 1));
86 CreateMockRead(*resp
, 2),
87 MockRead(SYNCHRONOUS
, 0, 3) // EOF
90 HostPortPair
host_port_pair("www.google.com", 80);
91 HostPortProxyPair
pair(host_port_pair
, ProxyServer::Direct());
92 EXPECT_EQ(OK
, InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
),
95 HttpRequestInfo request
;
96 request
.method
= "GET";
97 request
.url
= GURL("http://www.google.com/");
98 TestCompletionCallback callback
;
99 HttpResponseInfo response
;
100 HttpRequestHeaders headers
;
102 scoped_ptr
<SpdyHttpStream
> http_stream(
103 new SpdyHttpStream(session_
.get(), true));
106 http_stream
->InitializeStream(&request
, net_log
, CompletionCallback()));
108 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, NULL
, &response
,
109 callback
.callback()));
110 EXPECT_TRUE(http_session_
->spdy_session_pool()->HasSession(pair
));
112 // This triggers the MockWrite and read 2
113 callback
.WaitForResult();
115 // This triggers read 3. The empty read causes the session to shut down.
116 data()->CompleteRead();
118 // Because we abandoned the stream, we don't expect to find a session in the
120 EXPECT_FALSE(http_session_
->spdy_session_pool()->HasSession(pair
));
121 EXPECT_TRUE(data()->at_read_eof());
122 EXPECT_TRUE(data()->at_write_eof());
125 TEST_F(SpdyHttpStreamSpdy3Test
, SendChunkedPost
) {
126 UploadDataStream::set_merge_chunks(false);
128 scoped_ptr
<SpdyFrame
> req(ConstructChunkedSpdyPost(NULL
, 0));
129 scoped_ptr
<SpdyFrame
> chunk1(ConstructSpdyBodyFrame(1, false));
130 scoped_ptr
<SpdyFrame
> chunk2(ConstructSpdyBodyFrame(1, true));
131 MockWrite writes
[] = {
132 CreateMockWrite(*req
.get(), 1),
133 CreateMockWrite(*chunk1
, 2), // POST upload frames
134 CreateMockWrite(*chunk2
, 3),
136 scoped_ptr
<SpdyFrame
> resp(ConstructSpdyPostSynReply(NULL
, 0));
138 CreateMockRead(*resp
, 4),
139 CreateMockRead(*chunk1
, 5),
140 CreateMockRead(*chunk2
, 5),
141 MockRead(SYNCHRONOUS
, 0, 6) // EOF
144 HostPortPair
host_port_pair("www.google.com", 80);
145 HostPortProxyPair
pair(host_port_pair
, ProxyServer::Direct());
146 EXPECT_EQ(OK
, InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
),
149 HttpRequestInfo request
;
150 request
.method
= "POST";
151 request
.url
= GURL("http://www.google.com/");
152 request
.upload_data
= new UploadData();
153 request
.upload_data
->set_is_chunked(true);
154 request
.upload_data
->AppendChunk(kUploadData
, kUploadDataSize
, false);
155 request
.upload_data
->AppendChunk(kUploadData
, kUploadDataSize
, true);
156 TestCompletionCallback callback
;
157 HttpResponseInfo response
;
158 HttpRequestHeaders headers
;
160 SpdyHttpStream
http_stream(session_
.get(), true);
163 http_stream
.InitializeStream(&request
, net_log
, CompletionCallback()));
165 // http_stream.SendRequest() will take ownership of upload_stream.
166 UploadDataStream
* upload_stream
= new UploadDataStream(request
.upload_data
);
167 ASSERT_EQ(OK
, upload_stream
->Init());
168 EXPECT_EQ(ERR_IO_PENDING
, http_stream
.SendRequest(
169 headers
, upload_stream
, &response
, callback
.callback()));
170 EXPECT_TRUE(http_session_
->spdy_session_pool()->HasSession(pair
));
172 // This triggers the MockWrite and read 2
173 callback
.WaitForResult();
175 // This triggers read 3. The empty read causes the session to shut down.
176 data()->CompleteRead();
177 MessageLoop::current()->RunAllPending();
179 // Because we abandoned the stream, we don't expect to find a session in the
181 EXPECT_FALSE(http_session_
->spdy_session_pool()->HasSession(pair
));
182 EXPECT_TRUE(data()->at_read_eof());
183 EXPECT_TRUE(data()->at_write_eof());
186 // Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058
187 TEST_F(SpdyHttpStreamSpdy3Test
, SpdyURLTest
) {
188 const char * const full_url
= "http://www.google.com/foo?query=what#anchor";
189 const char * const base_url
= "http://www.google.com/foo?query=what";
190 scoped_ptr
<SpdyFrame
> req(ConstructSpdyGet(base_url
, false, 1, LOWEST
));
191 MockWrite writes
[] = {
192 CreateMockWrite(*req
.get(), 1),
194 scoped_ptr
<SpdyFrame
> resp(ConstructSpdyGetSynReply(NULL
, 0, 1));
196 CreateMockRead(*resp
, 2),
197 MockRead(SYNCHRONOUS
, 0, 3) // EOF
200 HostPortPair
host_port_pair("www.google.com", 80);
201 HostPortProxyPair
pair(host_port_pair
, ProxyServer::Direct());
202 EXPECT_EQ(OK
, InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
),
205 HttpRequestInfo request
;
206 request
.method
= "GET";
207 request
.url
= GURL(full_url
);
208 TestCompletionCallback callback
;
209 HttpResponseInfo response
;
210 HttpRequestHeaders headers
;
212 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
215 http_stream
->InitializeStream(&request
, net_log
, CompletionCallback()));
217 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, NULL
, &response
,
218 callback
.callback()));
220 SpdyHeaderBlock
* spdy_header
=
221 http_stream
->stream()->spdy_headers().get();
222 EXPECT_TRUE(spdy_header
!= NULL
);
223 if (spdy_header
->find(":path") != spdy_header
->end())
224 EXPECT_EQ("/foo?query=what", spdy_header
->find(":path")->second
);
226 FAIL() << "No url is set in spdy_header!";
228 // This triggers the MockWrite and read 2
229 callback
.WaitForResult();
231 // This triggers read 3. The empty read causes the session to shut down.
232 data()->CompleteRead();
234 // Because we abandoned the stream, we don't expect to find a session in the
236 EXPECT_FALSE(http_session_
->spdy_session_pool()->HasSession(pair
));
237 EXPECT_TRUE(data()->at_read_eof());
238 EXPECT_TRUE(data()->at_write_eof());
243 void GetECServerBoundCertAndProof(
244 const std::string
& origin
,
245 ServerBoundCertService
* server_bound_cert_service
,
247 std::string
* proof
) {
248 TestCompletionCallback callback
;
249 std::vector
<uint8
> requested_cert_types
;
250 requested_cert_types
.push_back(CLIENT_CERT_ECDSA_SIGN
);
251 SSLClientCertType cert_type
;
253 ServerBoundCertService::RequestHandle request_handle
;
254 int rv
= server_bound_cert_service
->GetDomainBoundCert(
255 origin
, requested_cert_types
, &cert_type
, &key
, cert
, callback
.callback(),
257 EXPECT_EQ(ERR_IO_PENDING
, rv
);
258 EXPECT_EQ(OK
, callback
.WaitForResult());
259 EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN
, cert_type
);
261 unsigned char secret
[32];
262 memset(secret
, 'A', arraysize(secret
));
264 // Convert the key string into a vector<unit8>
265 std::vector
<uint8
> key_data(key
.begin(), key
.end());
267 base::StringPiece spki_piece
;
268 ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(*cert
, &spki_piece
));
269 std::vector
<uint8
> spki(spki_piece
.data(),
270 spki_piece
.data() + spki_piece
.size());
272 std::vector
<uint8
> proof_data
;
273 scoped_ptr
<crypto::ECPrivateKey
> private_key(
274 crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
275 ServerBoundCertService::kEPKIPassword
, key_data
, spki
));
276 scoped_ptr
<crypto::ECSignatureCreator
> creator(
277 crypto::ECSignatureCreator::Create(private_key
.get()));
278 creator
->Sign(secret
, arraysize(secret
), &proof_data
);
279 proof
->assign(proof_data
.begin(), proof_data
.end());
284 // Constructs a standard SPDY SYN_STREAM frame for a GET request with
286 SpdyFrame
* ConstructCredentialRequestFrame(int slot
, const GURL
& url
,
288 const SpdyHeaderInfo syn_headers
= {
292 ConvertRequestPriorityToSpdyPriority(LOWEST
, 3),
302 // TODO(rch): this is ugly. Clean up.
303 std::string str_path
= url
.PathForRequest();
304 std::string str_scheme
= url
.scheme();
305 std::string str_host
= url
.host();
306 if (url
.has_port()) {
308 str_host
+= url
.port();
310 scoped_array
<char> req(new char[str_path
.size() + 1]);
311 scoped_array
<char> scheme(new char[str_scheme
.size() + 1]);
312 scoped_array
<char> host(new char[str_host
.size() + 1]);
313 memcpy(req
.get(), str_path
.c_str(), str_path
.size());
314 memcpy(scheme
.get(), str_scheme
.c_str(), str_scheme
.size());
315 memcpy(host
.get(), str_host
.c_str(), str_host
.size());
316 req
.get()[str_path
.size()] = '\0';
317 scheme
.get()[str_scheme
.size()] = '\0';
318 host
.get()[str_host
.size()] = '\0';
320 const char* const headers
[] = {
332 return ConstructSpdyPacket(
333 syn_headers
, NULL
, 0, headers
, arraysize(headers
)/2);
336 // TODO(rch): When openssl supports server bound certifictes, this
337 // guard can be removed
338 #if !defined(USE_OPENSSL)
339 // Test that if we request a resource for a new origin on a session that
340 // used domain bound certificates, that we send a CREDENTIAL frame for
341 // the new domain before we send the new request.
342 void SpdyHttpStreamSpdy3Test::TestSendCredentials(
343 ServerBoundCertService
* server_bound_cert_service
,
344 const std::string
& cert
,
345 const std::string
& proof
,
346 SSLClientCertType type
) {
347 const char* kUrl1
= "https://www.google.com/";
348 const char* kUrl2
= "https://www.gmail.com/";
353 cred
.certs
.push_back(cert
);
355 scoped_ptr
<SpdyFrame
> req(ConstructCredentialRequestFrame(
357 scoped_ptr
<SpdyFrame
> credential(ConstructSpdyCredential(cred
));
358 scoped_ptr
<SpdyFrame
> req2(ConstructCredentialRequestFrame(
360 MockWrite writes
[] = {
361 CreateMockWrite(*req
.get(), 0),
362 CreateMockWrite(*credential
.get(), 2),
363 CreateMockWrite(*req2
.get(), 3),
366 scoped_ptr
<SpdyFrame
> resp(ConstructSpdyGetSynReply(NULL
, 0, 1));
367 scoped_ptr
<SpdyFrame
> resp2(ConstructSpdyGetSynReply(NULL
, 0, 3));
369 CreateMockRead(*resp
, 1),
370 CreateMockRead(*resp2
, 4),
371 MockRead(SYNCHRONOUS
, 0, 5) // EOF
374 HostPortPair
host_port_pair(HostPortPair::FromURL(GURL(kUrl1
)));
375 HostPortProxyPair
pair(host_port_pair
, ProxyServer::Direct());
377 DeterministicMockClientSocketFactory
* socket_factory
=
378 session_deps_
.deterministic_socket_factory
.get();
379 scoped_refptr
<DeterministicSocketData
> data(
380 new DeterministicSocketData(reads
, arraysize(reads
),
381 writes
, arraysize(writes
)));
382 socket_factory
->AddSocketDataProvider(data
.get());
383 SSLSocketDataProvider
ssl(SYNCHRONOUS
, OK
);
384 ssl
.domain_bound_cert_type
= type
;
385 ssl
.server_bound_cert_service
= server_bound_cert_service
;
386 ssl
.protocol_negotiated
= kProtoSPDY3
;
387 socket_factory
->AddSSLSocketDataProvider(&ssl
);
388 http_session_
= SpdySessionDependencies::SpdyCreateSessionDeterministic(
390 session_
= http_session_
->spdy_session_pool()->Get(pair
, BoundNetLog());
391 transport_params_
= new TransportSocketParams(host_port_pair
,
392 MEDIUM
, false, false);
393 TestCompletionCallback callback
;
394 scoped_ptr
<ClientSocketHandle
> connection(new ClientSocketHandle
);
395 SSLConfig ssl_config
;
396 scoped_refptr
<SOCKSSocketParams
> socks_params
;
397 scoped_refptr
<HttpProxySocketParams
> http_proxy_params
;
398 scoped_refptr
<SSLSocketParams
> ssl_params(
399 new SSLSocketParams(transport_params_
,
402 ProxyServer::SCHEME_DIRECT
,
408 EXPECT_EQ(ERR_IO_PENDING
,
409 connection
->Init(host_port_pair
.ToString(),
413 http_session_
->GetSSLSocketPool(
414 HttpNetworkSession::NORMAL_SOCKET_POOL
),
416 callback
.WaitForResult();
418 session_
->InitializeWithSocket(connection
.release(), true, OK
));
420 HttpRequestInfo request
;
421 request
.method
= "GET";
422 request
.url
= GURL(kUrl1
);
423 HttpResponseInfo response
;
424 HttpRequestHeaders headers
;
426 scoped_ptr
<SpdyHttpStream
> http_stream(
427 new SpdyHttpStream(session_
.get(), true));
430 http_stream
->InitializeStream(&request
, net_log
, CompletionCallback()));
432 // EXPECT_FALSE(session_->NeedsCredentials(request.url));
433 // GURL new_origin(kUrl2);
434 // EXPECT_TRUE(session_->NeedsCredentials(new_origin));
436 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, NULL
, &response
,
437 callback
.callback()));
438 EXPECT_TRUE(http_session_
->spdy_session_pool()->HasSession(pair
));
441 callback
.WaitForResult();
443 // Start up second request for resource on a new origin.
444 scoped_ptr
<SpdyHttpStream
> http_stream2(
445 new SpdyHttpStream(session_
.get(), true));
446 request
.url
= GURL(kUrl2
);
449 http_stream2
->InitializeStream(&request
, net_log
, CompletionCallback()));
450 EXPECT_EQ(ERR_IO_PENDING
, http_stream2
->SendRequest(headers
, NULL
, &response
,
451 callback
.callback()));
453 callback
.WaitForResult();
455 EXPECT_EQ(ERR_IO_PENDING
, http_stream2
->ReadResponseHeaders(
456 callback
.callback()));
458 EXPECT_EQ(OK
, callback
.WaitForResult());
459 ASSERT_TRUE(response
.headers
.get() != NULL
);
460 ASSERT_EQ(200, response
.headers
->response_code());
463 class MockECSignatureCreator
: public crypto::ECSignatureCreator
{
465 explicit MockECSignatureCreator(crypto::ECPrivateKey
* key
) : key_(key
) {}
467 virtual bool Sign(const uint8
* data
,
469 std::vector
<uint8
>* signature
) OVERRIDE
{
470 std::vector
<uint8
> private_key_value
;
471 key_
->ExportValue(&private_key_value
);
472 std::string head
= "fakesignature";
473 std::string tail
= "/fakesignature";
476 signature
->insert(signature
->end(), head
.begin(), head
.end());
477 signature
->insert(signature
->end(), private_key_value
.begin(),
478 private_key_value
.end());
479 signature
->insert(signature
->end(), '-');
480 signature
->insert(signature
->end(), data
, data
+ data_len
);
481 signature
->insert(signature
->end(), tail
.begin(), tail
.end());
486 crypto::ECPrivateKey
* key_
;
487 DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreator
);
490 class MockECSignatureCreatorFactory
: public crypto::ECSignatureCreatorFactory
{
492 MockECSignatureCreatorFactory() {}
493 virtual ~MockECSignatureCreatorFactory() {}
495 virtual crypto::ECSignatureCreator
* Create(
496 crypto::ECPrivateKey
* key
) OVERRIDE
{
497 return new MockECSignatureCreator(key
);
500 DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreatorFactory
);
503 TEST_F(SpdyHttpStreamSpdy3Test
, SendCredentialsEC
) {
504 scoped_ptr
<crypto::ECSignatureCreatorFactory
> ec_signature_creator_factory(
505 new MockECSignatureCreatorFactory());
506 crypto::ECSignatureCreator::SetFactoryForTesting(
507 ec_signature_creator_factory
.get());
509 scoped_ptr
<ServerBoundCertService
> server_bound_cert_service(
510 new ServerBoundCertService(new DefaultServerBoundCertStore(NULL
)));
513 GetECServerBoundCertAndProof("http://www.gmail.com/",
514 server_bound_cert_service
.get(),
517 TestSendCredentials(server_bound_cert_service
.get(), cert
, proof
,
518 CLIENT_CERT_ECDSA_SIGN
);
521 #endif // !defined(USE_OPENSSL)
523 // TODO(willchan): Write a longer test for SpdyStream that exercises all