1 // Copyright 2016 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.
4 
5 #include "net/cert/internal/ocsp.h"
6 
7 #include "base/base64.h"
8 #include "base/strings/string_util.h"
9 #include "net/cert/internal/test_helpers.h"
10 #include "net/der/encode_values.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 #include "third_party/boringssl/src/include/openssl/pool.h"
13 #include "url/gurl.h"
14 
15 namespace net {
16 
17 namespace {
18 
19 const base::TimeDelta kOCSPAgeOneWeek = base::TimeDelta::FromDays(7);
20 
GetFilePath(const std::string & file_name)21 std::string GetFilePath(const std::string& file_name) {
22   return std::string("net/data/ocsp_unittest/") + file_name;
23 }
24 
ParseCertificate(base::StringPiece data)25 scoped_refptr<ParsedCertificate> ParseCertificate(base::StringPiece data) {
26   CertErrors errors;
27   return ParsedCertificate::Create(
28       bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new(
29           reinterpret_cast<const uint8_t*>(data.data()), data.size(), nullptr)),
30       {}, &errors);
31 }
32 
33 struct TestParams {
34   const char* file_name;
35   OCSPRevocationStatus expected_revocation_status;
36   OCSPVerifyResult::ResponseStatus expected_response_status;
37 };
38 
39 class CheckOCSPTest : public ::testing::TestWithParam<TestParams> {};
40 
41 const TestParams kTestParams[] = {
42     {"good_response.pem", OCSPRevocationStatus::GOOD,
43      OCSPVerifyResult::PROVIDED},
44 
45     {"good_response_sha256.pem", OCSPRevocationStatus::GOOD,
46      OCSPVerifyResult::PROVIDED},
47 
48     {"no_response.pem", OCSPRevocationStatus::UNKNOWN,
49      OCSPVerifyResult::NO_MATCHING_RESPONSE},
50 
51     {"malformed_request.pem", OCSPRevocationStatus::UNKNOWN,
52      OCSPVerifyResult::ERROR_RESPONSE},
53 
54     {"bad_status.pem", OCSPRevocationStatus::UNKNOWN,
55      OCSPVerifyResult::PARSE_RESPONSE_ERROR},
56 
57     {"bad_ocsp_type.pem", OCSPRevocationStatus::UNKNOWN,
58      OCSPVerifyResult::PARSE_RESPONSE_ERROR},
59 
60     {"bad_signature.pem", OCSPRevocationStatus::UNKNOWN,
61      OCSPVerifyResult::PROVIDED},
62 
63     {"ocsp_sign_direct.pem", OCSPRevocationStatus::GOOD,
64      OCSPVerifyResult::PROVIDED},
65 
66     {"ocsp_sign_indirect.pem", OCSPRevocationStatus::GOOD,
67      OCSPVerifyResult::PROVIDED},
68 
69     {"ocsp_sign_indirect_missing.pem", OCSPRevocationStatus::UNKNOWN,
70      OCSPVerifyResult::PROVIDED},
71 
72     {"ocsp_sign_bad_indirect.pem", OCSPRevocationStatus::UNKNOWN,
73      OCSPVerifyResult::PROVIDED},
74 
75     {"ocsp_extra_certs.pem", OCSPRevocationStatus::GOOD,
76      OCSPVerifyResult::PROVIDED},
77 
78     {"has_version.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED},
79 
80     {"responder_name.pem", OCSPRevocationStatus::GOOD,
81      OCSPVerifyResult::PROVIDED},
82 
83     {"responder_id.pem", OCSPRevocationStatus::GOOD,
84      OCSPVerifyResult::PROVIDED},
85 
86     {"has_extension.pem", OCSPRevocationStatus::GOOD,
87      OCSPVerifyResult::PROVIDED},
88 
89     {"good_response_next_update.pem", OCSPRevocationStatus::GOOD,
90      OCSPVerifyResult::PROVIDED},
91 
92     {"revoke_response.pem", OCSPRevocationStatus::REVOKED,
93      OCSPVerifyResult::PROVIDED},
94 
95     {"revoke_response_reason.pem", OCSPRevocationStatus::REVOKED,
96      OCSPVerifyResult::PROVIDED},
97 
98     {"unknown_response.pem", OCSPRevocationStatus::UNKNOWN,
99      OCSPVerifyResult::PROVIDED},
100 
101     {"multiple_response.pem", OCSPRevocationStatus::UNKNOWN,
102      OCSPVerifyResult::PROVIDED},
103 
104     {"other_response.pem", OCSPRevocationStatus::UNKNOWN,
105      OCSPVerifyResult::NO_MATCHING_RESPONSE},
106 
107     {"has_single_extension.pem", OCSPRevocationStatus::GOOD,
108      OCSPVerifyResult::PROVIDED},
109 
110     {"has_critical_single_extension.pem", OCSPRevocationStatus::UNKNOWN,
111      OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION},
112 
113     {"has_critical_response_extension.pem", OCSPRevocationStatus::UNKNOWN,
114      OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION},
115 
116     {"has_critical_ct_extension.pem", OCSPRevocationStatus::GOOD,
117      OCSPVerifyResult::PROVIDED},
118 
119     {"missing_response.pem", OCSPRevocationStatus::UNKNOWN,
120      OCSPVerifyResult::NO_MATCHING_RESPONSE},
121 };
122 
123 // Parameterised test name generator for tests depending on RenderTextBackend.
124 struct PrintTestName {
operator ()net::__anon611608ac0111::PrintTestName125   std::string operator()(const testing::TestParamInfo<TestParams>& info) const {
126     base::StringPiece name(info.param.file_name);
127     // Strip ".pem" from the end as GTest names cannot contain period.
128     name.remove_suffix(4);
129     return name.as_string();
130   }
131 };
132 
133 INSTANTIATE_TEST_SUITE_P(All,
134                          CheckOCSPTest,
135                          ::testing::ValuesIn(kTestParams),
136                          PrintTestName());
137 
TEST_P(CheckOCSPTest,FromFile)138 TEST_P(CheckOCSPTest, FromFile) {
139   const TestParams& params = GetParam();
140 
141   std::string ocsp_data;
142   std::string ca_data;
143   std::string cert_data;
144   std::string request_data;
145   const PemBlockMapping mappings[] = {
146       {"OCSP RESPONSE", &ocsp_data},
147       {"CA CERTIFICATE", &ca_data},
148       {"CERTIFICATE", &cert_data},
149       {"OCSP REQUEST", &request_data},
150   };
151 
152   ASSERT_TRUE(ReadTestDataFromPemFile(GetFilePath(params.file_name), mappings));
153 
154   // Mar 5 00:00:00 2017 GMT
155   base::Time kVerifyTime =
156       base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(1488672000);
157 
158   // Test that CheckOCSP() works.
159   OCSPVerifyResult::ResponseStatus response_status;
160   OCSPRevocationStatus revocation_status =
161       CheckOCSP(ocsp_data, cert_data, ca_data, kVerifyTime, kOCSPAgeOneWeek,
162                 &response_status);
163 
164   EXPECT_EQ(params.expected_revocation_status, revocation_status);
165   EXPECT_EQ(params.expected_response_status, response_status);
166 
167   // Check that CreateOCSPRequest() works.
168   scoped_refptr<ParsedCertificate> cert = ParseCertificate(cert_data);
169   ASSERT_TRUE(cert);
170 
171   scoped_refptr<ParsedCertificate> issuer = ParseCertificate(ca_data);
172   ASSERT_TRUE(issuer);
173 
174   std::vector<uint8_t> encoded_request;
175   ASSERT_TRUE(CreateOCSPRequest(cert.get(), issuer.get(), &encoded_request));
176 
177   EXPECT_EQ(der::Input(encoded_request.data(), encoded_request.size()),
178             der::Input(&request_data));
179 }
180 
181 base::StringPiece kGetURLTestParams[] = {
182     "http://www.example.com/", "http://www.example.com/path/",
183     "http://www.example.com/path",
184     "http://www.example.com/path?query"
185     "http://user:pass@www.example.com/path?query",
186 };
187 
188 class CreateOCSPGetURLTest
189     : public ::testing::TestWithParam<base::StringPiece> {};
190 
191 INSTANTIATE_TEST_SUITE_P(All,
192                          CreateOCSPGetURLTest,
193                          ::testing::ValuesIn(kGetURLTestParams));
194 
TEST_P(CreateOCSPGetURLTest,Basic)195 TEST_P(CreateOCSPGetURLTest, Basic) {
196   std::string ca_data;
197   std::string cert_data;
198   std::string request_data;
199   const PemBlockMapping mappings[] = {
200       {"CA CERTIFICATE", &ca_data},
201       {"CERTIFICATE", &cert_data},
202       {"OCSP REQUEST", &request_data},
203   };
204 
205   // Load one of the test files. (Doesn't really matter which one as
206   // constructing the DER is tested elsewhere).
207   ASSERT_TRUE(
208       ReadTestDataFromPemFile(GetFilePath("good_response.pem"), mappings));
209 
210   scoped_refptr<ParsedCertificate> cert = ParseCertificate(cert_data);
211   ASSERT_TRUE(cert);
212 
213   scoped_refptr<ParsedCertificate> issuer = ParseCertificate(ca_data);
214   ASSERT_TRUE(issuer);
215 
216   GURL url = CreateOCSPGetURL(cert.get(), issuer.get(), GetParam());
217 
218   // Try to extract the encoded data and compare against |request_data|.
219   //
220   // A known answer output test would be better as this just reverses the logic
221   // from the implementaiton file.
222   std::string b64 = url.spec().substr(GetParam().size() + 1);
223 
224   // Hex un-escape the data.
225   base::ReplaceSubstringsAfterOffset(&b64, 0, "%2B", "+");
226   base::ReplaceSubstringsAfterOffset(&b64, 0, "%2F", "/");
227   base::ReplaceSubstringsAfterOffset(&b64, 0, "%3D", "=");
228 
229   // Base64 decode the data.
230   std::string decoded;
231   ASSERT_TRUE(base::Base64Decode(b64, &decoded));
232 
233   EXPECT_EQ(request_data, decoded);
234 }
235 
236 }  // namespace
237 
238 }  // namespace net
239