• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 25457312714

06 May 2026 07:43PM UTC coverage: 89.331% (-2.3%) from 91.667%
25457312714

push

github

randombit
In TLS 1.3 verification of client certs, check the correct extension for OCSP

This was checking if the client asked us (the server) for OCSP, instead of
checking if we asked the client for OCSP when we sent the CertificateRequest.

107574 of 120422 relevant lines covered (89.33%)

11482758.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.35
/src/tests/test_name_constraint.cpp
1
/*
2
* (C) 2015,2016 Kai Michaelis
3
*     2026 Jack Lloyd
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#if defined(BOTAN_HAS_X509_CERTIFICATES)
11
   #include <botan/ber_dec.h>
12
   #include <botan/pkix_types.h>
13
   #include <botan/x509cert.h>
14
   #include <botan/x509path.h>
15
   #include <botan/internal/calendar.h>
16
#endif
17

18
namespace Botan_Tests {
19

20
namespace {
21

22
#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1) && \
23
   defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
24

25
class Name_Constraint_Tests final : public Test {
1✔
26
   public:
27
      std::vector<Test::Result> run() override {
1✔
28
         const std::vector<std::tuple<std::string, std::string, std::string, std::string>> test_cases = {
1✔
29
            std::make_tuple("Root_Email_Name_Constraint.crt",
1✔
30
                            "Invalid_Email_Name_Constraint.crt",
31
                            "",
32
                            "Certificate does not pass name constraint"),
33
            std::make_tuple("Root_DN_Name_Constraint.crt",
1✔
34
                            "Invalid_DN_Name_Constraint.crt",
35
                            "",
36
                            "Certificate does not pass name constraint"),
37
            std::make_tuple("Root_DN_Name_Constraint.crt", "Valid_DN_Name_Constraint.crt", "", "Verified"),
1✔
38
            std::make_tuple(
1✔
39
               "Root_DNS_Name_Constraint.crt", "Valid_DNS_Name_Constraint.crt", "aexample.com", "Verified"),
40
            std::make_tuple("Root_IP_Name_Constraint.crt", "Valid_IP_Name_Constraint.crt", "", "Verified"),
1✔
41
            std::make_tuple("Root_IP_Name_Constraint.crt",
1✔
42
                            "Invalid_IP_Name_Constraint.crt",
43
                            "",
44
                            "Certificate does not pass name constraint"),
45
         };
8✔
46
         std::vector<Test::Result> results;
1✔
47
         const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
48

49
         const std::chrono::system_clock::time_point validation_time =
1✔
50
            Botan::calendar_point(2016, 10, 21, 4, 20, 0).to_std_timepoint();
1✔
51

52
         for(const auto& t : test_cases) {
7✔
53
            const Botan::X509_Certificate root(Test::data_file("x509/name_constraint/" + std::get<0>(t)));
12✔
54
            const Botan::X509_Certificate sub(Test::data_file("x509/name_constraint/" + std::get<1>(t)));
12✔
55
            Botan::Certificate_Store_In_Memory trusted;
6✔
56
            Test::Result result("X509v3 Name Constraints: " + std::get<1>(t));
6✔
57

58
            trusted.add_certificate(root);
6✔
59
            Botan::Path_Validation_Result path_result = Botan::x509_path_validate(
6✔
60
               sub, restrictions, trusted, std::get<2>(t), Botan::Usage_Type::TLS_SERVER_AUTH, validation_time);
6✔
61

62
            if(path_result.successful_validation() && path_result.trust_root() != root) {
6✔
63
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
×
64
            }
65

66
            result.test_str_eq("validation result", path_result.result_string(), std::get<3>(t));
6✔
67
            results.emplace_back(result);
6✔
68
         }
6✔
69

70
         return results;
1✔
71
      }
2✔
72
};
73

74
BOTAN_REGISTER_TEST("x509", "x509_path_name_constraint", Name_Constraint_Tests);
75

76
// Verify that DNS constraints are case-insensitive also when falling back to the CN
77
class Name_Constraint_Excluded_CN_Case_Test final : public Test {
1✔
78
   public:
79
      std::vector<Test::Result> run() override {
1✔
80
         Test::Result result("X509v3 Name Constraints: excluded DNS with mixed-case CN and no SAN");
1✔
81

82
         const Botan::X509_Certificate root(
1✔
83
            Test::data_file("x509/name_constraint/Root_DNS_Excluded_Mixed_Case_CN.crt"));
2✔
84
         const Botan::X509_Certificate leaf(
1✔
85
            Test::data_file("x509/name_constraint/Invalid_DNS_Excluded_Mixed_Case_CN.crt"));
2✔
86

87
         Botan::Certificate_Store_In_Memory trusted;
1✔
88
         trusted.add_certificate(root);
1✔
89

90
         const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
91
         const auto validation_time = Botan::calendar_point(2026, 6, 1, 0, 0, 0).to_std_timepoint();
1✔
92

93
         const auto path_result = Botan::x509_path_validate(
1✔
94
            leaf, restrictions, trusted, "" /* hostname */, Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
95

96
         result.test_str_eq(
1✔
97
            "validation result", path_result.result_string(), "Certificate does not pass name constraint");
1✔
98

99
         return {result};
3✔
100
      }
2✔
101
};
102

103
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_excluded_cn_case", Name_Constraint_Excluded_CN_Case_Test);
104

105
class Name_Constraint_Empty_Subject_Test final : public Test {
1✔
106
   public:
107
      std::vector<Test::Result> run() override {
1✔
108
         /*
109
         - `root.pem`:
110
              Self-signed CA, `O=Acme NC Root, C=US`
111
         - `intermediate.pem`:
112
              CA signed by root, `O=Acme NC Intermediate, C=US`, critical `nameConstraints` with
113
              `permittedSubtrees: [directoryName=O=Acme, C=US]`
114
         - `leaf.pem`:
115
              End-entity signed by the intermediate, empty subject DN and critical SAN
116
              `directoryName=CN=server, O=Acme, C=US`
117
         */
118
         Test::Result result("X509v3 Name Constraints: empty subject + SAN directoryName inside permittedSubtrees");
1✔
119

120
         const Botan::X509_Certificate root(Test::data_file("x509/name_constraint_empty_subject/root.pem"));
2✔
121
         const Botan::X509_Certificate intermediate(
1✔
122
            Test::data_file("x509/name_constraint_empty_subject/intermediate.pem"));
2✔
123
         const Botan::X509_Certificate leaf(Test::data_file("x509/name_constraint_empty_subject/leaf.pem"));
2✔
124

125
         result.test_is_true("Leaf subject DN is empty", leaf.subject_dn().empty());
1✔
126
         result.test_sz_eq("Leaf SAN has one directoryName entry", leaf.subject_alt_name().directory_names().size(), 1);
1✔
127

128
         Botan::Certificate_Store_In_Memory trusted;
1✔
129
         trusted.add_certificate(root);
1✔
130

131
         const std::vector<Botan::X509_Certificate> chain{leaf, intermediate};
3✔
132
         const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
133
         const auto validation_time = Botan::calendar_point(2027, 1, 1, 0, 0, 0).to_std_timepoint();
1✔
134

135
         const auto path_result = Botan::x509_path_validate(
1✔
136
            chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
1✔
137

138
         result.test_str_eq("validation result", path_result.result_string(), "Verified");
1✔
139

140
         return {result};
3✔
141
      }
3✔
142
};
143

144
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_empty_subject", Name_Constraint_Empty_Subject_Test);
145

146
class Name_Constraint_IPv6_Chain_Tests final : public Test {
1✔
147
   public:
148
      std::vector<Test::Result> run() override {
1✔
149
         struct Case {
1✔
150
               std::string label;
151
               std::string dir;
152
               std::vector<std::string> intermediates;
153
               std::string leaf;
154
               bool accept;
155
         };
156

157
         const std::vector<Case> cases = {
1✔
158
            // IPv6 permittedSubtree 2001:db8::/32
159
            {"IPv6 permit: SAN inside subtree", "permitted", {}, "leaf_valid.pem", true},
1✔
160
            {"IPv6 permit: SAN outside subtree", "permitted", {}, "leaf_invalid.pem", false},
161

162
            // IPv6 excludedSubtree 2001:db8::/32
163
            {"IPv6 exclude: SAN outside subtree", "excluded", {}, "leaf_valid.pem", true},
164
            {"IPv6 exclude: SAN inside subtree", "excluded", {}, "leaf_invalid.pem", false},
165

166
            // Root permits only IPv4 10.0.0.0/8, so an IPv6 SAN must be rejected
167
            // because iPAddress is a single GeneralName form (RFC 5280 4.2.1.10).
168
            {"IPv4-only permit: IPv4 SAN", "cross_v4only", {}, "leaf_valid.pem", true},
169
            {"IPv4-only permit: IPv6 SAN", "cross_v4only", {}, "leaf_invalid.pem", false},
170

171
            // Similar to previous - root permits only IPv6 2001:db8::/32 so IPv4 must be rejected
172
            {"IPv6-only permit: IPv6 SAN", "cross_v6only", {}, "leaf_valid.pem", true},
173
            {"IPv6-only permit: IPv4 SAN", "cross_v6only", {}, "leaf_invalid.pem", false},
174

175
            // Constraints across multiple issuers
176
            // - root permits {10/8, 2001:db8::/32}
177
            // - intermediate narrows permits to {10.1/16, 2001:db8:cafe::/48} and excludes
178
            //   {10.1.99/24, 2001:db8:cafe:bad::/64}.
179
            //
180
            // Every leaf has one IPv4 and one IPv6 SAN
181
            {"Mixed v4+v6: all SANs in range", "mixed_multi", {"int.pem"}, "leaf_valid.pem", true},
182
            {"Mixed v4+v6: IPv4 outside int permit", "mixed_multi", {"int.pem"}, "leaf_invalid_int_v4.pem", false},
183
            {"Mixed v4+v6: IPv6 outside int permit", "mixed_multi", {"int.pem"}, "leaf_invalid_int_v6.pem", false},
184
            {"Mixed v4+v6: IPv4 hits int exclude", "mixed_multi", {"int.pem"}, "leaf_invalid_excl_v4.pem", false},
185
            {"Mixed v4+v6: IPv6 hits int exclude", "mixed_multi", {"int.pem"}, "leaf_invalid_excl_v6.pem", false},
186
            {"Mixed v4+v6: IPv4 outside root permit", "mixed_multi", {"int.pem"}, "leaf_invalid_root_v4.pem", false},
187
            {"Mixed v4+v6: IPv6 outside root permit", "mixed_multi", {"int.pem"}, "leaf_invalid_root_v6.pem", false},
188

189
            // Here the root excludes IPv4 10.0.0.0/8 and the leaf certs have IPv6 SAN; the
190
            // invalid leaf has a IPv4-mapped IPv6 address matching 10.0.0.0/8 while the valid leaf
191
            // has some other IPv6 address which is not excluded
192
            {"v4 exclude: mapped-v6 SAN inside v4 excl", "v4_exclude_mapped", {}, "leaf_invalid.pem", false},
193
            {"v4 exclude: mapped-v6 SAN outside v4 excl", "v4_exclude_mapped", {}, "leaf_valid.pem", true},
194

195
            // Here the root permits only IPv4 10.0.0.0/8. The invalid leaf has an IPv6 address in the SAN
196
            // which should be rejected as not being in the range. The 'valid' leaf is a questionable case: it
197
            // has an IPv6 SAN which is an IPv4-mapped IPv6 address inside 10.0.0.0/8. Arguably it really is
198
            // valid; RFC 5280 is silent on the issue. But lacking a clear consensus, it is rejected for now.
199
            {"v4 permit: mapped-v6 SAN inside v4 permit", "v4_permit_mapped", {}, "leaf_valid.pem", false},
200
            {"v4 permit: mapped-v6 SAN outside v4 permit", "v4_permit_mapped", {}, "leaf_invalid.pem", false},
201
         };
20✔
202

203
         const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
204

205
         const auto validation_time = Botan::calendar_point(2027, 4, 22, 20, 0, 0).to_std_timepoint();
1✔
206

207
         std::vector<Test::Result> results;
1✔
208
         for(const auto& c : cases) {
20✔
209
            const std::string base = "x509/name_constraint_ipv6/" + c.dir + "/";
38✔
210
            const Botan::X509_Certificate root(Test::data_file(base + "root.pem"));
38✔
211
            const Botan::X509_Certificate leaf(Test::data_file(base + c.leaf));
38✔
212

213
            std::vector<Botan::X509_Certificate> chain{leaf};
38✔
214
            for(const auto& intermediate_file : c.intermediates) {
26✔
215
               chain.emplace_back(Test::data_file(base + intermediate_file));
14✔
216
            }
217

218
            Botan::Certificate_Store_In_Memory trusted;
19✔
219
            trusted.add_certificate(root);
19✔
220

221
            const auto pv = Botan::x509_path_validate(
19✔
222
               chain, restrictions, trusted, "" /* hostname */, Botan::Usage_Type::UNSPECIFIED, validation_time);
19✔
223

224
            Test::Result result("X509v3 Name Constraints (IPv6 chains): " + c.label);
19✔
225

226
            const std::string expected = c.accept ? "Verified" : "Certificate does not pass name constraint";
32✔
227
            result.test_str_eq("path validation result", pv.result_string(), expected);
19✔
228
            results.emplace_back(std::move(result));
19✔
229
         }
19✔
230

231
         return results;
1✔
232
      }
22✔
233
};
234

235
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ipv6_chains", Name_Constraint_IPv6_Chain_Tests);
236

237
/*
238
* Validate that GeneralName iPAddress decoding rejects masks that are not a
239
* contiguous CIDR prefix. Drives the decoder with hand-rolled BER for a
240
* single [7] IMPLICIT OCTET STRING carrying {net || mask}.
241
*/
242
class Name_Constraint_IP_Mask_Tests final : public Text_Based_Test {
×
243
   public:
244
      Name_Constraint_IP_Mask_Tests() : Text_Based_Test("x509/general_name_ip.vec", "Address,Netmask") {}
2✔
245

246
      Test::Result run_one_test(const std::string& header, const VarMap& vars) override {
19✔
247
         Test::Result result("GeneralName iPAddress mask validation");
19✔
248

249
         const auto address = vars.get_req_bin("Address");
19✔
250
         const auto netmask = vars.get_req_bin("Netmask");
19✔
251

252
         const auto der = encode_address(address, netmask);
19✔
253

254
         Botan::BER_Decoder decoder(der, Botan::BER_Decoder::Limits::DER());
19✔
255
         Botan::GeneralName gn;
19✔
256

257
         if(header == "Valid") {
19✔
258
            try {
12✔
259
               gn.decode_from(decoder);
12✔
260
               result.test_success("Accepted valid GeneralName IP encoding");
12✔
261
            } catch(Botan::Decoding_Error&) {
×
262
               result.test_failure("Rejected valid GeneralName IP encoding");
×
263
            }
×
264
         } else {
265
            try {
7✔
266
               gn.decode_from(decoder);
7✔
267
               result.test_failure("Accepted invalid GeneralName IP encoding");
×
268
            } catch(Botan::Decoding_Error&) {
7✔
269
               result.test_success("Rejected invalid GeneralName IP encoding");
7✔
270
            }
7✔
271
         }
272

273
         return result;
38✔
274
      }
19✔
275

276
   private:
277
      static std::vector<uint8_t> encode_address(std::span<const uint8_t> address, std::span<const uint8_t> netmask) {
19✔
278
         std::vector<uint8_t> der;
19✔
279
         // [7] IMPLICIT OCTET STRING, primitive, context-specific.
280
         der.push_back(0x87);
19✔
281
         // Short for length is sufficient here
282
         der.push_back(static_cast<uint8_t>(address.size() + netmask.size()));
19✔
283
         der.insert(der.end(), address.begin(), address.end());
19✔
284
         der.insert(der.end(), netmask.begin(), netmask.end());
19✔
285
         return der;
19✔
286
      }
×
287
};
288

289
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ip_mask", Name_Constraint_IP_Mask_Tests);
290

291
#endif
292

293
}  // namespace
294

295
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc