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