1 // Copyright 2016 The Chromium Authors
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 "ocsp.h"
6
7 #include "string_util.h"
8 #include "test_helpers.h"
9 #include "encode_values.h"
10 #include <gtest/gtest.h>
11 #include <openssl/base64.h>
12 #include <openssl/pool.h>
13
14 namespace bssl {
15
16 namespace {
17
18 constexpr int64_t kOCSPAgeOneWeek = 7 * 24 * 60 * 60;
19
GetFilePath(const std::string & file_name)20 std::string GetFilePath(const std::string& file_name) {
21 return std::string("testdata/ocsp_unittest/") + file_name;
22 }
23
ParseCertificate(std::string_view data)24 std::shared_ptr<const ParsedCertificate> ParseCertificate(
25 std::string_view 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 ()bssl::__anon393e3cac0111::PrintTestName125 std::string operator()(const testing::TestParamInfo<TestParams>& info) const {
126 std::string_view name(info.param.file_name);
127 // Strip ".pem" from the end as GTest names cannot contain period.
128 name.remove_suffix(4);
129 return std::string(name);
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 int64_t kVerifyTime = 1488672000;
156
157 // Test that CheckOCSP() works.
158 OCSPVerifyResult::ResponseStatus response_status;
159 OCSPRevocationStatus revocation_status =
160 CheckOCSP(ocsp_data, cert_data, ca_data, kVerifyTime, kOCSPAgeOneWeek,
161 &response_status);
162
163 EXPECT_EQ(params.expected_revocation_status, revocation_status);
164 EXPECT_EQ(params.expected_response_status, response_status);
165
166 // Check that CreateOCSPRequest() works.
167 std::shared_ptr<const ParsedCertificate> cert = ParseCertificate(cert_data);
168 ASSERT_TRUE(cert);
169
170 std::shared_ptr<const ParsedCertificate> issuer = ParseCertificate(ca_data);
171 ASSERT_TRUE(issuer);
172
173 std::vector<uint8_t> encoded_request;
174 ASSERT_TRUE(CreateOCSPRequest(cert.get(), issuer.get(), &encoded_request));
175
176 EXPECT_EQ(der::Input(encoded_request), der::Input(request_data));
177 }
178
179 std::string_view kGetURLTestParams[] = {
180 "http://www.example.com/",
181 "http://www.example.com/path/",
182 "http://www.example.com/path",
183 "http://www.example.com/path?query"
184 "http://user:pass@www.example.com/path?query",
185 };
186
187 class CreateOCSPGetURLTest : public ::testing::TestWithParam<std::string_view> {
188 };
189
190 INSTANTIATE_TEST_SUITE_P(All,
191 CreateOCSPGetURLTest,
192 ::testing::ValuesIn(kGetURLTestParams));
193
TEST_P(CreateOCSPGetURLTest,Basic)194 TEST_P(CreateOCSPGetURLTest, Basic) {
195 std::string ca_data;
196 std::string cert_data;
197 std::string request_data;
198 const PemBlockMapping mappings[] = {
199 {"CA CERTIFICATE", &ca_data},
200 {"CERTIFICATE", &cert_data},
201 {"OCSP REQUEST", &request_data},
202 };
203
204 // Load one of the test files. (Doesn't really matter which one as
205 // constructing the DER is tested elsewhere).
206 ASSERT_TRUE(
207 ReadTestDataFromPemFile(GetFilePath("good_response.pem"), mappings));
208
209 std::shared_ptr<const ParsedCertificate> cert = ParseCertificate(cert_data);
210 ASSERT_TRUE(cert);
211
212 std::shared_ptr<const ParsedCertificate> issuer = ParseCertificate(ca_data);
213 ASSERT_TRUE(issuer);
214
215 std::optional<std::string> url =
216 CreateOCSPGetURL(cert.get(), issuer.get(), GetParam());
217 ASSERT_TRUE(url);
218
219 // Try to extract the encoded data and compare against |request_data|.
220 //
221 // A known answer output test would be better as this just reverses the logic
222 // from the implementation file.
223 std::string b64 = url->substr(GetParam().size() + 1);
224
225 // Hex un-escape the data.
226 b64 = bssl::string_util::FindAndReplace(b64, "%2B", "+");
227 b64 = bssl::string_util::FindAndReplace(b64, "%2F", "/");
228 b64 = bssl::string_util::FindAndReplace(b64, "%3D", "=");
229
230 // Base64 decode the data.
231 size_t len;
232 EXPECT_TRUE(EVP_DecodedLength(&len, b64.size()));
233 std::vector<uint8_t> decoded(len);
234 EXPECT_TRUE(EVP_DecodeBase64(decoded.data(), &len, len,
235 reinterpret_cast<const uint8_t*>(b64.data()),
236 b64.size()));
237 std::string decoded_string(decoded.begin(), decoded.begin() + len);
238
239 EXPECT_EQ(request_data, decoded_string);
240 }
241
242 } // namespace
243
244 } // namespace net
245