1 // Copyright 2016 The Chromium Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "ocsp.h"
16 
17 #include <gtest/gtest.h>
18 
19 #include <openssl/base64.h>
20 #include <openssl/pool.h>
21 #include <openssl/span.h>
22 
23 #include "encode_values.h"
24 #include "string_util.h"
25 #include "test_helpers.h"
26 
27 BSSL_NAMESPACE_BEGIN
28 
29 namespace {
30 
31 constexpr int64_t kOCSPAgeOneWeek = 7 * 24 * 60 * 60;
32 
GetFilePath(const std::string & file_name)33 std::string GetFilePath(const std::string &file_name) {
34   return std::string("testdata/ocsp_unittest/") + file_name;
35 }
36 
ParseCertificate(std::string_view data)37 std::shared_ptr<const ParsedCertificate> ParseCertificate(
38     std::string_view data) {
39   CertErrors errors;
40   return ParsedCertificate::Create(
41       bssl::UniquePtr<CRYPTO_BUFFER>(
42           CRYPTO_BUFFER_new(reinterpret_cast<const uint8_t *>(data.data()),
43                             data.size(), nullptr)),
44       {}, &errors);
45 }
46 
47 struct TestParams {
48   const char *file_name;
49   OCSPRevocationStatus expected_revocation_status;
50   OCSPVerifyResult::ResponseStatus expected_response_status;
51 };
52 
53 class CheckOCSPTest : public ::testing::TestWithParam<TestParams> {};
54 
55 const TestParams kTestParams[] = {
56     {"good_response.pem", OCSPRevocationStatus::GOOD,
57      OCSPVerifyResult::PROVIDED},
58 
59     {"good_response_sha256.pem", OCSPRevocationStatus::GOOD,
60      OCSPVerifyResult::PROVIDED},
61 
62     {"no_response.pem", OCSPRevocationStatus::UNKNOWN,
63      OCSPVerifyResult::NO_MATCHING_RESPONSE},
64 
65     {"malformed_request.pem", OCSPRevocationStatus::UNKNOWN,
66      OCSPVerifyResult::ERROR_RESPONSE},
67 
68     {"bad_status.pem", OCSPRevocationStatus::UNKNOWN,
69      OCSPVerifyResult::PARSE_RESPONSE_ERROR},
70 
71     {"bad_ocsp_type.pem", OCSPRevocationStatus::UNKNOWN,
72      OCSPVerifyResult::PARSE_RESPONSE_ERROR},
73 
74     {"bad_signature.pem", OCSPRevocationStatus::UNKNOWN,
75      OCSPVerifyResult::PROVIDED},
76 
77     {"ocsp_sign_direct.pem", OCSPRevocationStatus::GOOD,
78      OCSPVerifyResult::PROVIDED},
79 
80     {"ocsp_sign_indirect.pem", OCSPRevocationStatus::GOOD,
81      OCSPVerifyResult::PROVIDED},
82 
83     {"ocsp_sign_indirect_missing.pem", OCSPRevocationStatus::UNKNOWN,
84      OCSPVerifyResult::PROVIDED},
85 
86     {"ocsp_sign_bad_indirect.pem", OCSPRevocationStatus::UNKNOWN,
87      OCSPVerifyResult::PROVIDED},
88 
89     {"ocsp_extra_certs.pem", OCSPRevocationStatus::GOOD,
90      OCSPVerifyResult::PROVIDED},
91 
92     {"has_version.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED},
93 
94     {"responder_name.pem", OCSPRevocationStatus::GOOD,
95      OCSPVerifyResult::PROVIDED},
96 
97     {"responder_id.pem", OCSPRevocationStatus::GOOD,
98      OCSPVerifyResult::PROVIDED},
99 
100     {"has_extension.pem", OCSPRevocationStatus::GOOD,
101      OCSPVerifyResult::PROVIDED},
102 
103     {"good_response_next_update.pem", OCSPRevocationStatus::GOOD,
104      OCSPVerifyResult::PROVIDED},
105 
106     {"revoke_response.pem", OCSPRevocationStatus::REVOKED,
107      OCSPVerifyResult::PROVIDED},
108 
109     {"revoke_response_reason.pem", OCSPRevocationStatus::REVOKED,
110      OCSPVerifyResult::PROVIDED},
111 
112     {"unknown_response.pem", OCSPRevocationStatus::UNKNOWN,
113      OCSPVerifyResult::PROVIDED},
114 
115     {"multiple_response.pem", OCSPRevocationStatus::UNKNOWN,
116      OCSPVerifyResult::PROVIDED},
117 
118     {"other_response.pem", OCSPRevocationStatus::UNKNOWN,
119      OCSPVerifyResult::NO_MATCHING_RESPONSE},
120 
121     {"has_single_extension.pem", OCSPRevocationStatus::GOOD,
122      OCSPVerifyResult::PROVIDED},
123 
124     {"has_critical_single_extension.pem", OCSPRevocationStatus::UNKNOWN,
125      OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION},
126 
127     {"has_critical_response_extension.pem", OCSPRevocationStatus::UNKNOWN,
128      OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION},
129 
130     {"has_critical_ct_extension.pem", OCSPRevocationStatus::GOOD,
131      OCSPVerifyResult::PROVIDED},
132 
133     {"missing_response.pem", OCSPRevocationStatus::UNKNOWN,
134      OCSPVerifyResult::NO_MATCHING_RESPONSE},
135 };
136 
137 // Parameterised test name generator for tests depending on RenderTextBackend.
138 struct PrintTestName {
operator ()__anon1a6202c40111::PrintTestName139   std::string operator()(const testing::TestParamInfo<TestParams> &info) const {
140     std::string_view name(info.param.file_name);
141     // Strip ".pem" from the end as GTest names cannot contain period.
142     name.remove_suffix(4);
143     return std::string(name);
144   }
145 };
146 
147 INSTANTIATE_TEST_SUITE_P(All, CheckOCSPTest, ::testing::ValuesIn(kTestParams),
148                          PrintTestName());
149 
TEST_P(CheckOCSPTest,FromFile)150 TEST_P(CheckOCSPTest, FromFile) {
151   const TestParams &params = GetParam();
152 
153   std::string ocsp_data;
154   std::string ca_data;
155   std::string cert_data;
156   std::string request_data;
157   const PemBlockMapping mappings[] = {
158       {"OCSP RESPONSE", &ocsp_data},
159       {"CA CERTIFICATE", &ca_data},
160       {"CERTIFICATE", &cert_data},
161       {"OCSP REQUEST", &request_data},
162   };
163 
164   ASSERT_TRUE(ReadTestDataFromPemFile(GetFilePath(params.file_name), mappings));
165 
166   // Mar 5 00:00:00 2017 GMT
167   int64_t kVerifyTime = 1488672000;
168 
169   // Test that CheckOCSP() works.
170   OCSPVerifyResult::ResponseStatus response_status;
171   OCSPRevocationStatus revocation_status =
172       CheckOCSP(ocsp_data, cert_data, ca_data, kVerifyTime, kOCSPAgeOneWeek,
173                 &response_status);
174 
175   EXPECT_EQ(params.expected_revocation_status, revocation_status);
176   EXPECT_EQ(params.expected_response_status, response_status);
177 
178   // Check that CreateOCSPRequest() works.
179   std::shared_ptr<const ParsedCertificate> cert = ParseCertificate(cert_data);
180   ASSERT_TRUE(cert);
181 
182   std::shared_ptr<const ParsedCertificate> issuer = ParseCertificate(ca_data);
183   ASSERT_TRUE(issuer);
184 
185   std::vector<uint8_t> encoded_request;
186   ASSERT_TRUE(CreateOCSPRequest(cert.get(), issuer.get(), &encoded_request));
187 
188   EXPECT_EQ(der::Input(encoded_request),
189             der::Input(StringAsBytes(request_data)));
190 }
191 
192 std::string_view kGetURLTestParams[] = {
193     "http://www.example.com/",
194     "http://www.example.com/path/",
195     "http://www.example.com/path",
196     "http://www.example.com/path?query"
197     "http://user:pass@www.example.com/path?query",
198 };
199 
200 class CreateOCSPGetURLTest : public ::testing::TestWithParam<std::string_view> {
201 };
202 
203 INSTANTIATE_TEST_SUITE_P(All, CreateOCSPGetURLTest,
204                          ::testing::ValuesIn(kGetURLTestParams));
205 
TEST_P(CreateOCSPGetURLTest,Basic)206 TEST_P(CreateOCSPGetURLTest, Basic) {
207   std::string ca_data;
208   std::string cert_data;
209   std::string request_data;
210   const PemBlockMapping mappings[] = {
211       {"CA CERTIFICATE", &ca_data},
212       {"CERTIFICATE", &cert_data},
213       {"OCSP REQUEST", &request_data},
214   };
215 
216   // Load one of the test files. (Doesn't really matter which one as
217   // constructing the DER is tested elsewhere).
218   ASSERT_TRUE(
219       ReadTestDataFromPemFile(GetFilePath("good_response.pem"), mappings));
220 
221   std::shared_ptr<const ParsedCertificate> cert = ParseCertificate(cert_data);
222   ASSERT_TRUE(cert);
223 
224   std::shared_ptr<const ParsedCertificate> issuer = ParseCertificate(ca_data);
225   ASSERT_TRUE(issuer);
226 
227   std::optional<std::string> url =
228       CreateOCSPGetURL(cert.get(), issuer.get(), GetParam());
229   ASSERT_TRUE(url);
230 
231   // Try to extract the encoded data and compare against |request_data|.
232   //
233   // A known answer output test would be better as this just reverses the logic
234   // from the implementation file.
235   std::string b64 = url->substr(GetParam().size() + 1);
236 
237   // Hex un-escape the data.
238   b64 = bssl::string_util::FindAndReplace(b64, "%2B", "+");
239   b64 = bssl::string_util::FindAndReplace(b64, "%2F", "/");
240   b64 = bssl::string_util::FindAndReplace(b64, "%3D", "=");
241 
242   // Base64 decode the data.
243   size_t len;
244   EXPECT_TRUE(EVP_DecodedLength(&len, b64.size()));
245   std::vector<uint8_t> decoded(len);
246   EXPECT_TRUE(EVP_DecodeBase64(decoded.data(), &len, len,
247                                reinterpret_cast<const uint8_t *>(b64.data()),
248                                b64.size()));
249   std::string decoded_string(decoded.begin(), decoded.begin() + len);
250 
251   EXPECT_EQ(request_data, decoded_string);
252 }
253 
254 }  // namespace
255 
256 BSSL_NAMESPACE_END
257