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

randombit / botan / 15725004337

18 Jun 2025 05:52AM UTC coverage: 90.539% (-0.02%) from 90.558%
15725004337

Pull #4890

github

web-flow
Merge 0636720b8 into cfc7bc1c6
Pull Request #4890: Improve RFC 3779 extension api

98455 of 108743 relevant lines covered (90.54%)

12317237.8 hits per line

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

98.3
/src/tests/test_x509_rpki.cpp
1
/*
2
* (C) 2025 Jack Lloyd
3
* (C) 2025 Anton Einax, Dominik Schricker
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/certstor.h>
12
   #include <botan/pk_algs.h>
13
   #include <botan/pubkey.h>
14
   #include <botan/x509_ca.h>
15
   #include <botan/x509_ext.h>
16
   #include <botan/x509path.h>
17
   #include <botan/x509self.h>
18
   #include <botan/internal/calendar.h>
19
#endif
20

21
#if defined(BOTAN_HAS_ECC_GROUP)
22
   #include <botan/ec_group.h>
23
#endif
24

25
namespace Botan_Tests {
26

27
namespace {
28

29
#if defined(BOTAN_HAS_X509_CERTIFICATES)
30

31
struct CA_Creation_Result {
32
      Botan::X509_Certificate ca_cert;
33
      Botan::X509_CA ca;
34
      std::unique_ptr<Botan::Private_Key> sub_key;
35
      std::string sig_algo;
36
      std::string hash_fn;
37
};
38

39
Botan::X509_Time from_date(const int y, const int m, const int d) {
150✔
40
   const size_t this_year = Botan::calendar_point(std::chrono::system_clock::now()).year();
150✔
41

42
   Botan::calendar_point t(static_cast<uint32_t>(this_year + y), m, d, 0, 0, 0);
150✔
43
   return Botan::X509_Time(t.to_std_timepoint());
150✔
44
}
45

46
std::unique_ptr<Botan::Private_Key> generate_key(const std::string& algo, Botan::RandomNumberGenerator& rng) {
105✔
47
   std::string params;
105✔
48
   if(algo == "ECDSA") {
105✔
49
      params = "secp256r1";
105✔
50

51
   #if defined(BOTAN_HAS_ECC_GROUP)
52
      if(Botan::EC_Group::supports_named_group("secp192r1")) {
105✔
53
         params = "secp192r1";
105✔
54
      }
55
   #endif
56
   } else if(algo == "Ed25519") {
×
57
      params = "";
×
58
   } else if(algo == "RSA") {
×
59
      params = "1536";
×
60
   }
61

62
   return Botan::create_private_key(algo, rng, params);
105✔
63
}
105✔
64

65
Botan::X509_Cert_Options ca_opts(const std::string& sig_padding = "") {
74✔
66
   Botan::X509_Cert_Options opts("Test CA/US/Botan Project/Testing");
74✔
67

68
   opts.uri = "https://botan.randombit.net";
74✔
69
   opts.dns = "botan.randombit.net";
74✔
70
   opts.email = "testing@randombit.net";
74✔
71
   opts.set_padding_scheme(sig_padding);
74✔
72

73
   opts.CA_key(1);
74✔
74

75
   return opts;
74✔
76
}
×
77

78
Botan::X509_Cert_Options req_opts(const std::string& algo, const std::string& sig_padding = "") {
31✔
79
   Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing");
31✔
80

81
   opts.uri = "https://botan.randombit.net";
31✔
82
   opts.dns = "botan.randombit.net";
31✔
83
   opts.email = "testing@randombit.net";
31✔
84
   opts.set_padding_scheme(sig_padding);
31✔
85

86
   opts.not_before("160101200000Z");
31✔
87
   opts.not_after("300101200000Z");
31✔
88

89
   opts.challenge = "zoom";
31✔
90

91
   if(algo == "RSA") {
31✔
92
      opts.constraints = Botan::Key_Constraints::KeyEncipherment;
×
93
   } else if(algo == "DSA" || algo == "ECDSA" || algo == "ECGDSA" || algo == "ECKCDSA") {
31✔
94
      opts.constraints = Botan::Key_Constraints::DigitalSignature;
31✔
95
   }
96

97
   return opts;
31✔
98
}
×
99

100
std::tuple<std::string, std::string, std::string> get_sig_algo_padding() {
74✔
101
   #if defined(BOTAN_HAS_ECDSA)
102
   const std::string sig_algo{"ECDSA"};
74✔
103
   const std::string padding_method;
74✔
104
   const std::string hash_fn{"SHA-256"};
74✔
105
   #elif defined(BOTAN_HAS_ED25519)
106
   const std::string sig_algo{"Ed25519"};
107
   const std::string padding_method;
108
   const std::string hash_fn{"SHA-512"};
109
   #elif defined(BOTAN_HAS_RSA)
110
   const std::string sig_algo{"RSA"};
111
   const std::string padding_method{"EMSA3(SHA-256)"};
112
   const std::string hash_fn{"SHA-256"};
113
   #endif
114

115
   return std::make_tuple(sig_algo, padding_method, hash_fn);
148✔
116
}
74✔
117

118
Botan::X509_Certificate make_self_signed(std::unique_ptr<Botan::RandomNumberGenerator>& rng,
5✔
119
                                         const Botan::X509_Cert_Options& opts = std::move(ca_opts())) {
120
   auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding();
5✔
121
   auto key = generate_key(sig_algo, *rng);
5✔
122
   const auto cert = Botan::X509::create_self_signed_cert(opts, *key, hash_fn, *rng);
5✔
123

124
   return cert;
5✔
125
}
5✔
126

127
CA_Creation_Result make_ca(std::unique_ptr<Botan::RandomNumberGenerator>& rng,
31✔
128
                           const Botan::X509_Cert_Options& opts = std::move(ca_opts())) {
129
   auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding();
31✔
130
   auto ca_key = generate_key(sig_algo, *rng);
31✔
131
   const auto ca_cert = Botan::X509::create_self_signed_cert(opts, *ca_key, hash_fn, *rng);
31✔
132
   Botan::X509_CA ca(ca_cert, *ca_key, hash_fn, padding_method, *rng);
31✔
133
   auto sub_key = generate_key(sig_algo, *rng);
31✔
134

135
   return CA_Creation_Result{ca_cert, std::move(ca), std::move(sub_key), sig_algo, hash_fn};
93✔
136
}
62✔
137

138
std::pair<Botan::X509_Certificate, Botan::X509_CA> make_and_sign_ca(
38✔
139
   std::unique_ptr<Botan::Certificate_Extension> ext,
140
   Botan::X509_CA& parent_ca,
141
   std::unique_ptr<Botan::RandomNumberGenerator>& rng) {
142
   auto [sig_algo, padding_method, hash_fn] = get_sig_algo_padding();
38✔
143

144
   Botan::X509_Cert_Options opts = ca_opts();
38✔
145
   opts.extensions.add(std::move(ext));
38✔
146

147
   std::unique_ptr<Botan::Private_Key> key = generate_key(sig_algo, *rng);
38✔
148

149
   Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *key, hash_fn, *rng);
38✔
150
   Botan::X509_Certificate cert = parent_ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));
38✔
151
   Botan::X509_CA ca(cert, *key, hash_fn, padding_method, *rng);
38✔
152

153
   return std::make_pair(std::move(cert), std::move(ca));
38✔
154
}
76✔
155

156
constexpr auto IPv4 = Botan::Cert_Extension::IPAddressBlocks::Version::IPv4;
157
constexpr auto IPv6 = Botan::Cert_Extension::IPAddressBlocks::Version::IPv6;
158

159
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
160

161
Test::Result test_x509_ip_addr_blocks_extension_decode() {
1✔
162
   Test::Result result("X509 IP Address Block decode");
1✔
163
   result.start_timer();
1✔
164
   using Botan::Cert_Extension::IPAddressBlocks;
1✔
165

166
   {
1✔
167
      const std::string filename("IPAddrBlocksAll.pem");
1✔
168
      Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename));
2✔
169
      auto ip_addr_blocks = cert.v3_extensions().get_extension_object_as<IPAddressBlocks>();
1✔
170

171
      const auto& addr_blocks = ip_addr_blocks->addr_blocks();
1✔
172
      result.confirm("cert has IPAddrBlocks extension", ip_addr_blocks != nullptr, true);
2✔
173
      result.test_eq("cert has two IpAddrBlocks", addr_blocks.size(), 2);
1✔
174

175
      const auto& ipv4block = std::get<IPAddressBlocks::IPAddressChoice<IPv4>>(addr_blocks[0].addr_choice());
1✔
176
      const auto& ipv6block = std::get<IPAddressBlocks::IPAddressChoice<IPv6>>(addr_blocks[1].addr_choice());
1✔
177

178
      auto& v4_blocks = ipv4block.ranges().value();
1✔
179

180
      // cert contains (in this order)
181
      // 192.168.0.0 - 192.168.127.255 (192.168.0.0/17)
182
      // 193.168.0.0 - 193.169.255.255 (193.168.0.0/15)
183
      // 194.168.0.0 - 195.175.1.2
184
      // 196.168.0.1 - 196.168.0.1 (196.168.0.1/32)
185

186
      result.test_eq("ipv4 block 0 min", v4_blocks[0].min().value(), {192, 168, 0, 0});
2✔
187
      result.test_eq("ipv4 block 0 max", v4_blocks[0].max().value(), {192, 168, 127, 255});
2✔
188

189
      result.test_eq("ipv4 block 1 min", v4_blocks[1].min().value(), {193, 168, 0, 0});
2✔
190
      result.test_eq("ipv4 block 1 max", v4_blocks[1].max().value(), {193, 169, 255, 255});
2✔
191
      result.test_eq("ipv4 block 2 min", v4_blocks[2].min().value(), {194, 168, 0, 0});
2✔
192
      result.test_eq("ipv4 block 2 max", v4_blocks[2].max().value(), {195, 175, 1, 2});
2✔
193

194
      result.test_eq("ipv4 block 3 min", v4_blocks[3].min().value(), {196, 168, 0, 1});
2✔
195
      result.test_eq("ipv4 block 3 max", v4_blocks[3].max().value(), {196, 168, 0, 1});
2✔
196

197
      auto& v6_blocks = ipv6block.ranges().value();
1✔
198

199
      // cert contains (in this order)
200
      // fa80::/65
201
      // fe20::/37
202
      // 2003:0:6829:3435:420:10c5:0:c4/128
203
      // ab01:0:0:0:0:0:0:1-cd02:0:0:0:0:0:0:2
204

205
      result.test_eq("ipv6 block 0 min",
2✔
206
                     v6_blocks[0].min().value(),
1✔
207
                     {0x20, 0x03, 0x00, 0x00, 0x68, 0x29, 0x34, 0x35, 0x04, 0x20, 0x10, 0xc5, 0x00, 0x00, 0x00, 0xc4});
208
      result.test_eq("ipv6 block 0 max",
2✔
209
                     v6_blocks[0].max().value(),
1✔
210
                     {0x20, 0x03, 0x00, 0x00, 0x68, 0x29, 0x34, 0x35, 0x04, 0x20, 0x10, 0xc5, 0x00, 0x00, 0x00, 0xc4});
211
      result.test_eq("ipv6 block 1 min",
2✔
212
                     v6_blocks[1].min().value(),
1✔
213
                     {0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01});
214
      result.test_eq("ipv6 block 1 max",
2✔
215
                     v6_blocks[1].max().value(),
1✔
216
                     {0xcd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02});
217
      result.test_eq("ipv6 block 2 min",
2✔
218
                     v6_blocks[2].min().value(),
1✔
219
                     {0xfa, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
220
      result.test_eq("ipv6 block 2 max",
2✔
221
                     v6_blocks[2].max().value(),
1✔
222
                     {0xfa, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff});
223
      result.test_eq("ipv6 block 3 min",
2✔
224
                     v6_blocks[3].min().value(),
1✔
225
                     {0xfe, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
226
      result.test_eq("ipv6 block 3 max",
2✔
227
                     v6_blocks[3].max().value(),
1✔
228
                     {0xfe, 0x20, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff});
229
   }
1✔
230
   {
1✔
231
      const std::string filename("IPAddrBlocksUnsorted.pem");
1✔
232
      Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename));
2✔
233
      auto ip_addr_blocks = cert.v3_extensions().get_extension_object_as<IPAddressBlocks>();
1✔
234

235
      // cert contains (in this order)
236
      // IPv6 (1) inherit
237
      // IPv6 0xff....0xff
238
      // IPv4 (2) inherit
239
      // IPv4 (1) 192.168.0.0 - 192.168.2.1
240
      // IPv4 (1) 192.168.2.2 - 200.0.0.0
241
      // IPv4 inherit
242

243
      // IPv4 ranges should be merged, IPv4 should come before IPv6, all should be sorted by safi
244

245
      const auto& addr_blocks = ip_addr_blocks->addr_blocks();
1✔
246
      result.test_eq("cert has two IpAddrBlocks", addr_blocks.size(), 5);
1✔
247

248
      result.test_eq("block 0 has no safi", addr_blocks[0].safi(), std::optional<uint8_t>{std::nullopt});
1✔
249
      result.confirm(
2✔
250
         "block 0 is inherited",
251
         !std::get<IPAddressBlocks::IPAddressChoice<IPv4>>(addr_blocks[0].addr_choice()).ranges().has_value());
1✔
252

253
      result.test_eq("block 1 has correct safi", addr_blocks[1].safi(), std::optional<uint8_t>{1});
1✔
254
      const auto& block_1 =
1✔
255
         std::get<IPAddressBlocks::IPAddressChoice<IPv4>>(addr_blocks[1].addr_choice()).ranges().value();
1✔
256

257
      result.confirm("block 1 has correct size", block_1.size() == 1);
2✔
258
      result.test_eq("block 1 min is correct", block_1[0].min().value(), {192, 168, 0, 0});
2✔
259
      result.test_eq("block 1 max is correct", block_1[0].max().value(), {200, 0, 0, 0});
2✔
260

261
      result.test_eq("block 2 has correct safi", addr_blocks[2].safi(), std::optional<uint8_t>{2});
1✔
262
      result.confirm(
2✔
263
         "block 2 is inherited",
264
         !std::get<IPAddressBlocks::IPAddressChoice<IPv4>>(addr_blocks[2].addr_choice()).ranges().has_value());
1✔
265

266
      result.test_eq("block 3 has no safi", addr_blocks[3].safi(), std::optional<uint8_t>{std::nullopt});
1✔
267
      const auto& block_3 =
1✔
268
         std::get<IPAddressBlocks::IPAddressChoice<IPv6>>(addr_blocks[3].addr_choice()).ranges().value();
1✔
269

270
      result.confirm("block 3 has correct size", block_3.size() == 1);
2✔
271
      result.test_eq("block 3 min is correct",
2✔
272
                     block_3[0].min().value(),
1✔
273
                     {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff});
274
      result.test_eq("block 3 max is correct",
2✔
275
                     block_3[0].max().value(),
1✔
276
                     {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff});
277

278
      result.test_eq("block 24 has correct safi", addr_blocks[4].safi(), std::optional<uint8_t>{1});
1✔
279
      result.confirm(
2✔
280
         "block 4 is inherited",
281
         !std::get<IPAddressBlocks::IPAddressChoice<IPv6>>(addr_blocks[4].addr_choice()).ranges().has_value());
1✔
282
   }
1✔
283
   {
1✔
284
      const std::string filename("InvalidIPAddrBlocks.pem");
1✔
285
      Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename));
2✔
286

287
      // cert contains the 10.0.32.0/20 prefix, but with a 9 for the unused bits
288

289
      result.confirm("extension is present", cert.v3_extensions().extension_set(IPAddressBlocks::static_oid()));
2✔
290

291
      auto ext = cert.v3_extensions().get_extension_object_as<IPAddressBlocks>();
1✔
292
      result.confirm("extension is not decoded", ext == nullptr);
2✔
293
   }
1✔
294

295
   result.end_timer();
1✔
296
   return result;
1✔
297
}
×
298

299
Test::Result test_x509_as_blocks_extension_decode() {
1✔
300
   Test::Result result("X509 AS Block decode");
1✔
301
   result.start_timer();
1✔
302
   using Botan::Cert_Extension::ASBlocks;
1✔
303

304
   {
1✔
305
      const std::string filename("ASNumberCert.pem");
1✔
306
      Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename));
2✔
307

308
      auto as_blocks = cert.v3_extensions().get_extension_object_as<ASBlocks>();
1✔
309

310
      const auto& identifier = as_blocks->as_identifiers();
1✔
311
      result.confirm("cert has ASBlock extension", as_blocks != nullptr, true);
2✔
312

313
      const auto& asnum = identifier.asnum().value().ranges().value();
1✔
314
      const auto& rdi = identifier.rdi().value().ranges().value();
1✔
315

316
      // cert contains asnum 0-999, 5042, 0-4294967295
317
      result.confirm("asnum entry 0 min", asnum[0].min() == 0, true);
2✔
318
      result.confirm("asnum entry 0 max", asnum[0].max() == 4294967295, true);
2✔
319

320
      // and rdi 1234-5678, 32768, 0-4294967295
321
      result.confirm("rdi entry 0 min", rdi[0].min() == 0, true);
2✔
322
      result.confirm("rdi entry 0 max", rdi[0].max() == 4294967295, true);
2✔
323
   }
1✔
324
   {
1✔
325
      const std::string filename("ASNumberOnly.pem");
1✔
326
      Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename));
2✔
327

328
      auto as_blocks = cert.v3_extensions().get_extension_object_as<ASBlocks>();
1✔
329

330
      const auto& identifier = as_blocks->as_identifiers();
1✔
331
      result.confirm("cert has ASBlock extension", as_blocks != nullptr, true);
2✔
332

333
      const auto& asnum = identifier.asnum().value().ranges().value();
1✔
334
      result.confirm("cert has no RDI entries", identifier.rdi().has_value(), false);
2✔
335

336
      // contains 0-999, 0-4294967295
337
      result.confirm("asnum entry 0 min", asnum[0].min() == 0, true);
2✔
338
      result.confirm("asnum entry 0 max", asnum[0].max() == 4294967295, true);
2✔
339
   }
1✔
340
   {
1✔
341
      const std::string filename("ASRdiOnly.pem");
1✔
342
      Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename));
2✔
343

344
      auto as_blocks = cert.v3_extensions().get_extension_object_as<ASBlocks>();
1✔
345

346
      const auto& identifier = as_blocks->as_identifiers();
1✔
347
      result.confirm("cert has ASBlock extension", as_blocks != nullptr, true);
2✔
348

349
      result.confirm("cert has no ASNUM entries", identifier.asnum().has_value(), false);
2✔
350
      const auto& rdi = identifier.rdi().value().ranges().value();
1✔
351

352
      // contains 1234-5678, 0-4294967295
353
      result.confirm("rdi entry 0 min", rdi[0].min() == 0, true);
2✔
354
      result.confirm("rdi entry 0 max", rdi[0].max() == 4294967295, true);
2✔
355
   }
1✔
356
   {
1✔
357
      const std::string filename("ASNumberInherit.pem");
1✔
358
      Botan::X509_Certificate cert(Test::data_file("x509/x509test/" + filename));
2✔
359

360
      auto as_blocks = cert.v3_extensions().get_extension_object_as<ASBlocks>();
1✔
361

362
      const auto& identifier = as_blocks->as_identifiers();
1✔
363
      result.confirm("cert has ASBlock extension", as_blocks != nullptr, true);
2✔
364

365
      result.confirm("asnum has no entries", identifier.asnum().value().ranges().has_value(), false);
2✔
366
      const auto& rdi = identifier.rdi().value().ranges().value();
1✔
367

368
      // contains 1234-5678, 0-4294967295
369
      result.confirm("rdi entry 0 min", rdi[0].min() == 0, true);
2✔
370
      result.confirm("rdi entry 0 max", rdi[0].max() == 4294967295, true);
2✔
371
   }
1✔
372

373
   result.end_timer();
1✔
374
   return result;
1✔
375
}
×
376

377
   #endif
378

379
Test::Result test_x509_ip_addr_blocks_rfc3779_example() {
1✔
380
   Test::Result result("X509 IP Address Blocks rfc3779 example");
1✔
381
   result.start_timer();
1✔
382

383
   using Botan::Cert_Extension::IPAddressBlocks;
1✔
384
   auto rng = Test::new_rng(__func__);
1✔
385

386
   // construct like in https://datatracker.ietf.org/doc/html/rfc3779#page-18
387
   std::unique_ptr<IPAddressBlocks> blocks_1 = std::make_unique<IPAddressBlocks>();
1✔
388
   blocks_1->add_address<IPv4>({10, 0, 32, 0}, {10, 0, 47, 255}, 1);
1✔
389
   blocks_1->add_address<IPv4>({10, 0, 64, 0}, {10, 0, 64, 255}, 1);
1✔
390
   blocks_1->add_address<IPv4>({10, 1, 0, 0}, {10, 1, 255, 255}, 1);
1✔
391
   blocks_1->add_address<IPv4>({10, 2, 48, 0}, {10, 2, 63, 255}, 1);
1✔
392
   blocks_1->add_address<IPv4>({10, 2, 64, 0}, {10, 2, 64, 255}, 1);
1✔
393
   blocks_1->add_address<IPv4>({10, 3, 0, 0}, {10, 3, 255, 255}, 1);
1✔
394
   blocks_1->inherit<IPv6>();
1✔
395

396
   Botan::X509_Cert_Options opts_1 = ca_opts();
1✔
397
   opts_1.extensions.add(std::move(blocks_1));
1✔
398

399
   auto cert_1 = make_self_signed(rng, opts_1);
1✔
400

401
   auto bits_1 = cert_1.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid());
1✔
402

403
   result.test_eq(
1✔
404
      "extension is encoded as specified",
405
      bits_1,
406
      "3035302B040300010130240304040A00200304000A00400303000A01300C0304040A02300304000A02400303000A033006040200020500");
407

408
   auto ext_1 = cert_1.v3_extensions().get_extension_object_as<IPAddressBlocks>();
1✔
409

410
   auto ext_1_addr_fam_1 = ext_1->addr_blocks()[0];
1✔
411
   result.test_eq("extension 1 ipv4 safi", ext_1_addr_fam_1.safi(), std::optional<uint8_t>(1));
1✔
412
   auto ext_1_ranges =
1✔
413
      std::get<IPAddressBlocks::IPAddressChoice<IPv4>>(ext_1_addr_fam_1.addr_choice()).ranges().value();
1✔
414
   result.test_eq("extension 1 range 1 min", ext_1_ranges[0].min().value(), {10, 0, 32, 0});
2✔
415
   result.test_eq("extension 1 range 1 max", ext_1_ranges[0].max().value(), {10, 0, 47, 255});
2✔
416

417
   result.test_eq("extension 1 range 2 min", ext_1_ranges[1].min().value(), {10, 0, 64, 0});
2✔
418
   result.test_eq("extension 1 range 2 max", ext_1_ranges[1].max().value(), {10, 0, 64, 255});
2✔
419

420
   result.test_eq("extension 1 range 3 min", ext_1_ranges[2].min().value(), {10, 1, 0, 0});
2✔
421
   result.test_eq("extension 1 range 3 max", ext_1_ranges[2].max().value(), {10, 1, 255, 255});
2✔
422

423
   result.test_eq("extension 1 range 4 min", ext_1_ranges[3].min().value(), {10, 2, 48, 0});
2✔
424
   result.test_eq("extension 1 range 4 max", ext_1_ranges[3].max().value(), {10, 2, 64, 255});
2✔
425

426
   result.test_eq("extension 1 range 5 min", ext_1_ranges[4].min().value(), {10, 3, 0, 0});
2✔
427
   result.test_eq("extension 1 range 5 max", ext_1_ranges[4].max().value(), {10, 3, 255, 255});
2✔
428

429
   result.test_eq("extension 1 ipv6 safi", ext_1->addr_blocks()[1].safi(), std::optional<uint8_t>{std::nullopt});
1✔
430
   result.confirm(
2✔
431
      "extension 1 ipv6 inherited",
432
      !std::get<IPAddressBlocks::IPAddressChoice<IPv6>>(ext_1->addr_blocks()[1].addr_choice()).ranges().has_value());
1✔
433

434
   // https://datatracker.ietf.org/doc/html/rfc3779#page-20
435
   std::unique_ptr<IPAddressBlocks> blocks_2 = std::make_unique<IPAddressBlocks>();
1✔
436
   blocks_2->add_address<IPv6>(
1✔
437
      {0x20, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
438
      {0x20, 0x01, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff});
439
   blocks_2->add_address<IPv4>({10, 0, 0, 0}, {10, 255, 255, 255}, 1);
1✔
440
   blocks_2->add_address<IPv4>({172, 16, 0, 0}, {172, 31, 255, 255}, 1);
1✔
441
   blocks_2->inherit<IPv4>(2);
1✔
442

443
   Botan::X509_Cert_Options opts_2 = ca_opts();
1✔
444
   opts_2.extensions.add(std::move(blocks_2));
1✔
445

446
   auto cert_2 = make_self_signed(rng, opts_2);
1✔
447

448
   auto bits_2 = cert_2.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid());
1✔
449

450
   // see https://www.rfc-editor.org/errata/eid6792 as to why the B0 specified in the RFC is a AC here
451
   result.test_eq("extension is encoded as specified",
1✔
452
                  bits_2,
453
                  "302C3010040300010130090302000A030304AC10300704030001020500300F040200023009030700200100000002");
454

455
   auto ext_2 = cert_2.v3_extensions().get_extension_object_as<IPAddressBlocks>();
1✔
456

457
   auto ext_2_addr_fam_1 = ext_2->addr_blocks()[0];
1✔
458
   result.test_eq("extension 2 ipv4 1 safi", ext_2_addr_fam_1.safi(), std::optional<uint8_t>(1));
1✔
459
   auto ext_2_ranges_1 =
1✔
460
      std::get<IPAddressBlocks::IPAddressChoice<IPv4>>(ext_2_addr_fam_1.addr_choice()).ranges().value();
1✔
461
   result.test_eq("extension 2 fam 1 range 1 min", ext_2_ranges_1[0].min().value(), {10, 0, 0, 0});
2✔
462
   result.test_eq("extension 2 fam 1 range 1 max", ext_2_ranges_1[0].max().value(), {10, 255, 255, 255});
2✔
463

464
   result.test_eq("extension 2 fam 1 range 2 min", ext_2_ranges_1[1].min().value(), {172, 16, 0, 0});
2✔
465
   result.test_eq("extension 2 fam 1 range 2 max", ext_2_ranges_1[1].max().value(), {172, 31, 255, 255});
2✔
466

467
   result.test_eq("extension 2 ipv4 2 safi", ext_2->addr_blocks()[1].safi(), std::optional<uint8_t>{2});
1✔
468
   result.confirm(
2✔
469
      "extension 2 ipv4 2 inherited",
470
      !std::get<IPAddressBlocks::IPAddressChoice<IPv4>>(ext_2->addr_blocks()[1].addr_choice()).ranges().has_value());
1✔
471

472
   auto ext_2_addr_fam_3 = ext_2->addr_blocks()[2];
1✔
473
   result.test_eq("extension 2 ipv4 1 safi", ext_2_addr_fam_3.safi(), std::optional<uint8_t>(std::nullopt));
1✔
474
   auto ext_2_ranges_3 =
1✔
475
      std::get<IPAddressBlocks::IPAddressChoice<IPv6>>(ext_2_addr_fam_3.addr_choice()).ranges().value();
1✔
476
   result.test_eq("extension 2 fam 3 range 1 min",
2✔
477
                  ext_2_ranges_3[0].min().value(),
1✔
478
                  {0x20, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
479
   result.test_eq("extension 2 fam 3 range 1 max",
2✔
480
                  ext_2_ranges_3[0].max().value(),
1✔
481
                  {0x20, 0x01, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff});
482

483
   result.end_timer();
1✔
484
   return result;
2✔
485
}
7✔
486

487
Test::Result test_x509_ip_addr_blocks_encoding() {
1✔
488
   Test::Result result("X509 IP Address Blocks encoding");
1✔
489
   result.start_timer();
1✔
490

491
   using Botan::Cert_Extension::IPAddressBlocks;
1✔
492
   auto rng = Test::new_rng(__func__);
1✔
493

494
   std::unique_ptr<IPAddressBlocks> blocks = std::make_unique<IPAddressBlocks>();
1✔
495

496
   // 64 - 127
497
   blocks->add_address<IPv4>({192, 168, 0b01000000, 0}, {192, 168, 0b01111111, 255}, 2);
1✔
498

499
   blocks->add_address<IPv4>({255, 255, 255, 255});
1✔
500
   // encoded as prefix
501
   blocks->add_address<IPv4>({190, 5, 0, 0}, {190, 5, 0b01111111, 255});
1✔
502
   // encoded as min, max
503
   blocks->add_address<IPv4>({127, 0, 0, 1}, {189, 5, 7, 255});
1✔
504

505
   // full address range
506
   blocks->add_address<IPv4>({0, 0, 0, 0}, {255, 255, 255, 255}, 1);
1✔
507

508
   blocks->add_address<IPv4>({123, 123, 2, 1});
1✔
509

510
   Botan::X509_Cert_Options opts = ca_opts();
1✔
511
   opts.extensions.add(std::move(blocks));
1✔
512

513
   auto cert = make_self_signed(rng, opts);
1✔
514
   auto bits = cert.v3_extensions().get_extension_bits(IPAddressBlocks::static_oid());
1✔
515

516
   // hand validated with https://lapo.it/asn1js/
517
   result.test_eq(
1✔
518
      "extension is encoded as specified",
519
      bits,
520
      "304630290402000130230305007B7B0201300D0305007F000001030403BD0500030407BE0500030500FFFFFFFF300A04030001013003030100300D04030001023006030406C0A840");
521

522
   result.end_timer();
1✔
523
   return result;
1✔
524
}
2✔
525

526
Test::Result test_x509_ip_addr_blocks_path_validation_success() {
1✔
527
   Test::Result result("X509 IP Address Blocks path validation success");
1✔
528
   result.start_timer();
1✔
529

530
   using Botan::Cert_Extension::IPAddressBlocks;
1✔
531
   auto rng = Test::new_rng(__func__);
1✔
532

533
   /*
534
   Creates a certificate chain of length 4.
535
   Root: ipv4 and ipv6
536
   Inherit: has both values as 'inherit'
537
   Dynamic: has either both 'inherit', both with values, or just one with a value
538
   Subject: both ipv4 and ipv6 as a subset of Root / Dynamic
539
   */
540

541
   // Root cert
542
   std::unique_ptr<IPAddressBlocks> root_blocks = std::make_unique<IPAddressBlocks>();
1✔
543

544
   root_blocks->add_address<IPv4>({120, 0, 0, 1}, {130, 140, 150, 160}, 42);
1✔
545
   root_blocks->add_address<IPv4>({10, 0, 0, 1}, {10, 255, 255, 255}, 42);
1✔
546

547
   root_blocks->add_address<IPv6>(
1✔
548
      {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
549
      {0xA0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
550
   root_blocks->add_address<IPv6>(
1✔
551
      {0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
552
      {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
553

554
   // Inherit cert
555
   std::unique_ptr<IPAddressBlocks> inherit_blocks = std::make_unique<IPAddressBlocks>();
1✔
556

557
   inherit_blocks->inherit<IPv4>(42);
1✔
558
   inherit_blocks->inherit<IPv6>();
1✔
559

560
   // Subject cert
561
   std::unique_ptr<IPAddressBlocks> sub_blocks = std::make_unique<IPAddressBlocks>();
1✔
562

563
   sub_blocks->add_address<IPv4>({124, 0, 255, 0}, {126, 0, 0, 1}, 42);
1✔
564
   sub_blocks->add_address<IPv4>({10, 0, 2, 1}, {10, 42, 0, 255}, 42);
1✔
565

566
   sub_blocks->add_address<IPv6>(
1✔
567
      {0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
568
      {0x0D, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
569

570
   Botan::X509_Cert_Options root_opts = ca_opts();
1✔
571
   root_opts.extensions.add(std::move(root_blocks));
1✔
572
   auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts);
1✔
573
   Botan::X509_Cert_Options sub_opts = req_opts(sig_algo);
1✔
574
   sub_opts.extensions.add(std::move(sub_blocks));
1✔
575
   auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng);
2✔
576

577
   Botan::Certificate_Store_In_Memory trusted;
1✔
578
   trusted.add_certificate(root_cert);
1✔
579

580
   for(size_t i = 0; i < 4; i++) {
5✔
581
      bool include_v4 = i & 1;
4✔
582
      bool include_v6 = (i >> 1) & 1;
4✔
583

584
      // Dynamic Cert
585
      std::unique_ptr<IPAddressBlocks> dyn_blocks = std::make_unique<IPAddressBlocks>();
4✔
586
      if(include_v4) {
4✔
587
         dyn_blocks->add_address<IPv4>({122, 0, 0, 255}, {128, 255, 255, 255}, 42);
2✔
588
         dyn_blocks->add_address<IPv4>({10, 0, 0, 255}, {10, 255, 0, 1}, 42);
2✔
589
      } else {
590
         dyn_blocks->inherit<IPv4>(42);
2✔
591
      }
592

593
      if(include_v6) {
4✔
594
         dyn_blocks->add_address<IPv6>(
2✔
595
            {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
596
            {0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
597
      } else {
598
         dyn_blocks->inherit<IPv6>();
2✔
599
      }
600

601
      auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng);
8✔
602

603
      Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng);
4✔
604
      Botan::X509_Certificate sub_cert =
4✔
605
         dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));
4✔
606

607
      const Botan::Path_Validation_Restrictions restrictions(false, 80);
8✔
608
      std::vector<Botan::X509_Certificate> certs = {sub_cert, dyn_cert, inherit_cert};
16✔
609

610
      Botan::Path_Validation_Result path_result = Botan::x509_path_validate(certs, restrictions, trusted);
4✔
611
      result.require("path validation succeeds", path_result.successful_validation());
4✔
612
   }
8✔
613

614
   result.end_timer();
1✔
615
   return result;
2✔
616
}
7✔
617

618
Test::Result test_x509_ip_addr_blocks_path_validation_failure() {
1✔
619
   Test::Result result("X509 IP Address Blocks path validation failure");
1✔
620
   result.start_timer();
1✔
621

622
   using Botan::Cert_Extension::IPAddressBlocks;
1✔
623
   auto rng = Test::new_rng(__func__);
1✔
624

625
   for(size_t i = 0; i < 7; i++) {
8✔
626
      bool all_inherit = (i == 0);
7✔
627
      bool different_safi = (i == 1);
7✔
628
      bool too_small_subrange = (i == 2);
7✔
629
      bool too_large_subrange = (i == 3);
7✔
630
      bool no_more_issuer_ranges = (i == 4);
7✔
631
      bool empty_issuer_ranges = (i == 5);
7✔
632
      bool nullptr_extensions = (i == 6);
7✔
633

634
      // Root cert
635
      std::unique_ptr<IPAddressBlocks> root_blocks = std::make_unique<IPAddressBlocks>();
7✔
636
      if(!all_inherit) {
7✔
637
         root_blocks->add_address<IPv4>({120, 0, 0, 1}, {130, 140, 150, 160}, 42);
6✔
638
      } else {
639
         root_blocks->inherit<IPv4>(42);
1✔
640
      }
641

642
      Botan::X509_Cert_Options root_opts = ca_opts();
7✔
643
      if(!nullptr_extensions) {
7✔
644
         root_opts.extensions.add(std::move(root_blocks));
12✔
645
      }
646
      auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts);
7✔
647

648
      // Issuer Cert
649
      std::unique_ptr<IPAddressBlocks> iss_blocks = std::make_unique<IPAddressBlocks>();
7✔
650
      if(!all_inherit) {
7✔
651
         if(empty_issuer_ranges) {
6✔
652
            iss_blocks->restrict<IPv4>(42);
1✔
653
         } else {
654
            iss_blocks->add_address<IPv4>({122, 0, 0, 255}, {128, 255, 255, 255}, 42);
5✔
655
         }
656
      } else {
657
         iss_blocks->inherit<IPv4>(42);
1✔
658
      }
659

660
      auto [iss_cert, iss_ca] = make_and_sign_ca(std::move(iss_blocks), root_ca, rng);
14✔
661

662
      // Subject cert
663
      std::unique_ptr<IPAddressBlocks> sub_blocks = std::make_unique<IPAddressBlocks>();
7✔
664

665
      uint8_t safi = different_safi ? 41 : 42;
7✔
666

667
      if(!all_inherit) {
7✔
668
         if(too_small_subrange) {
6✔
669
            sub_blocks->add_address<IPv4>({118, 0, 255, 0}, {126, 0, 0, 1}, safi);
1✔
670
         } else if(too_large_subrange) {
5✔
671
            sub_blocks->add_address<IPv4>({124, 0, 255, 0}, {134, 0, 0, 1}, safi);
1✔
672
         } else if(no_more_issuer_ranges) {
4✔
673
            sub_blocks->add_address<IPv4>({140, 0, 0, 1}, {150, 0, 0, 1}, safi);
1✔
674
         } else {
675
            sub_blocks->add_address<IPv4>({124, 0, 255, 0}, {126, 0, 0, 1}, safi);
3✔
676
         }
677
      } else {
678
         sub_blocks->inherit<IPv4>(safi);
1✔
679
      }
680

681
      Botan::X509_Cert_Options sub_opts = req_opts(sig_algo);
7✔
682
      sub_opts.extensions.add(std::move(sub_blocks));
7✔
683

684
      Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng);
7✔
685
      Botan::X509_Certificate sub_cert =
7✔
686
         iss_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));
7✔
687

688
      const Botan::Path_Validation_Restrictions restrictions(false, 80);
14✔
689
      std::vector<Botan::X509_Certificate> certs = {sub_cert, iss_cert};
21✔
690

691
      Botan::Certificate_Store_In_Memory trusted;
7✔
692
      trusted.add_certificate(root_cert);
7✔
693

694
      Botan::Path_Validation_Result path_result = Botan::x509_path_validate(certs, restrictions, trusted);
7✔
695
      result.require("path validation fails", !path_result.successful_validation());
7✔
696
   }
15✔
697

698
   result.end_timer();
1✔
699
   return result;
1✔
700
}
8✔
701

702
Test::Result test_x509_as_blocks_rfc3779_example() {
1✔
703
   Test::Result result("X509 AS Blocks rfc3779 example");
1✔
704
   result.start_timer();
1✔
705

706
   using Botan::Cert_Extension::ASBlocks;
1✔
707
   auto rng = Test::new_rng(__func__);
1✔
708

709
   // construct like in https://datatracker.ietf.org/doc/html/rfc3779#page-21
710
   std::unique_ptr<ASBlocks> blocks = std::make_unique<ASBlocks>();
1✔
711
   blocks->add_asnum(135);
1✔
712
   blocks->add_asnum(3000, 3999);
1✔
713
   blocks->add_asnum(5001);
1✔
714
   blocks->inherit_rdi();
1✔
715

716
   Botan::X509_Cert_Options opts = ca_opts();
1✔
717
   opts.extensions.add(std::move(blocks));
1✔
718

719
   auto cert = make_self_signed(rng, opts);
1✔
720
   auto bits = cert.v3_extensions().get_extension_bits(ASBlocks::static_oid());
1✔
721

722
   result.test_eq(
1✔
723
      "extension is encoded as specified", bits, "301AA014301202020087300802020BB802020F9F02021389A1020500");
724

725
   auto as_idents = cert.v3_extensions().get_extension_object_as<ASBlocks>()->as_identifiers();
1✔
726
   auto as_ids = as_idents.asnum().value().ranges().value();
1✔
727

728
   result.confirm("", as_ids[0].min() == 135);
2✔
729

730
   result.end_timer();
1✔
731
   return result;
2✔
732
}
3✔
733

734
Test::Result test_x509_as_blocks_encoding() {
1✔
735
   Test::Result result("X509 IP Address Blocks encoding");
1✔
736
   result.start_timer();
1✔
737

738
   using Botan::Cert_Extension::ASBlocks;
1✔
739
   auto rng = Test::new_rng(__func__);
1✔
740

741
   std::unique_ptr<ASBlocks> blocks = std::make_unique<ASBlocks>();
1✔
742

743
   blocks->add_rdi(10);
1✔
744
   blocks->add_rdi(20, 30);
1✔
745
   blocks->add_rdi(42, 300);
1✔
746
   blocks->add_rdi(9, 301);
1✔
747

748
   blocks->inherit_asnum();
1✔
749
   blocks->add_asnum(20);
1✔
750
   // this overwrites the previous two
751
   blocks->restrict_asnum();
1✔
752

753
   Botan::X509_Cert_Options opts = ca_opts();
1✔
754
   opts.extensions.add(std::move(blocks));
1✔
755

756
   auto cert = make_self_signed(rng, opts);
1✔
757
   auto bits = cert.v3_extensions().get_extension_bits(ASBlocks::static_oid());
1✔
758

759
   result.test_eq("extension is encoded as specified", bits, "3011A0023000A10B300930070201090202012D");
1✔
760

761
   result.end_timer();
1✔
762
   return result;
1✔
763
}
2✔
764

765
Test::Result test_x509_as_blocks_path_validation_success() {
1✔
766
   Test::Result result("X509 AS Block path validation success");
1✔
767
   result.start_timer();
1✔
768

769
   using Botan::Cert_Extension::ASBlocks;
1✔
770
   auto rng = Test::new_rng(__func__);
1✔
771

772
   /*
773
   Creates a certificate chain of length 4.
774
   Root: both asnum and rdi
775
   Inherit: has both values as 'inherit'
776
   Dynamic: has either both 'inherit', both with values, or just one with a value
777
   Subject: both asnum and rdi as a subset of Root / Dynamic
778
   */
779

780
   // Root Cert, both as and rdi
781

782
   std::unique_ptr<ASBlocks> root_blocks = std::make_unique<ASBlocks>();
1✔
783

784
   root_blocks->add_asnum(0, 999);
1✔
785
   root_blocks->add_asnum(5042);
1✔
786
   root_blocks->add_asnum(5043, 4294967295);
1✔
787

788
   root_blocks->add_rdi(1234, 5678);
1✔
789
   root_blocks->add_rdi(32768);
1✔
790
   root_blocks->add_rdi(32769, 4294967295);
1✔
791

792
   // Inherit cert, both as 'inherit'
793
   std::unique_ptr<ASBlocks> inherit_blocks = std::make_unique<ASBlocks>();
1✔
794
   inherit_blocks->inherit_asnum();
1✔
795
   inherit_blocks->inherit_rdi();
1✔
796

797
   // Subject cert
798

799
   std::unique_ptr<ASBlocks> sub_blocks = std::make_unique<ASBlocks>();
1✔
800

801
   sub_blocks->add_asnum(120, 180);
1✔
802
   sub_blocks->add_asnum(220, 240);
1✔
803
   sub_blocks->add_asnum(260, 511);
1✔
804
   sub_blocks->add_asnum(678);
1✔
805
   sub_blocks->add_asnum(5043, 5100);
1✔
806

807
   sub_blocks->add_rdi(1500, 2300);
1✔
808
   sub_blocks->add_rdi(2500, 4000);
1✔
809
   sub_blocks->add_rdi(1567);
1✔
810
   sub_blocks->add_rdi(33100, 40000);
1✔
811

812
   Botan::X509_Cert_Options root_opts = ca_opts();
1✔
813
   root_opts.extensions.add(std::move(root_blocks));
1✔
814
   auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts);
1✔
815
   Botan::X509_Cert_Options sub_opts = req_opts(sig_algo);
1✔
816
   sub_opts.extensions.add(std::move(sub_blocks));
1✔
817
   auto [inherit_cert, inherit_ca] = make_and_sign_ca(std::move(inherit_blocks), root_ca, rng);
2✔
818

819
   Botan::Certificate_Store_In_Memory trusted;
1✔
820
   trusted.add_certificate(root_cert);
1✔
821

822
   for(size_t i = 0; i < 4; i++) {
5✔
823
      bool include_asnum = i & 1;
4✔
824
      bool include_rdi = (i >> 1) & 1;
4✔
825

826
      std::unique_ptr<ASBlocks> dyn_blocks = std::make_unique<ASBlocks>();
4✔
827
      if(include_asnum) {
4✔
828
         dyn_blocks->add_asnum(100, 600);
2✔
829
         dyn_blocks->add_asnum(678);
2✔
830
         dyn_blocks->add_asnum(5042, 5101);
2✔
831
      } else {
832
         dyn_blocks->inherit_asnum();
2✔
833
      }
834

835
      if(include_rdi) {
4✔
836
         dyn_blocks->add_rdi(1500, 5000);
2✔
837
         dyn_blocks->add_rdi(33000, 60000);
2✔
838
      } else {
839
         dyn_blocks->inherit_rdi();
2✔
840
      }
841

842
      auto [dyn_cert, dyn_ca] = make_and_sign_ca(std::move(dyn_blocks), inherit_ca, rng);
8✔
843

844
      Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng);
4✔
845
      Botan::X509_Certificate sub_cert =
4✔
846
         dyn_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));
4✔
847

848
      const Botan::Path_Validation_Restrictions restrictions(false, 80);
8✔
849
      std::vector<Botan::X509_Certificate> certs = {sub_cert, dyn_cert, inherit_cert};
16✔
850

851
      Botan::Path_Validation_Result path_result = Botan::x509_path_validate(certs, restrictions, trusted);
4✔
852
      result.require("path validation succeeds", path_result.successful_validation());
4✔
853
   }
8✔
854

855
   result.end_timer();
1✔
856
   return result;
2✔
857
}
7✔
858

859
Test::Result test_x509_as_blocks_path_validation_extension_not_present() {
1✔
860
   Test::Result result("X509 AS Block path validation extension not present");
1✔
861
   result.start_timer();
1✔
862

863
   using Botan::Cert_Extension::ASBlocks;
1✔
864
   auto rng = Test::new_rng(__func__);
1✔
865

866
   std::unique_ptr<ASBlocks> sub_blocks = std::make_unique<ASBlocks>();
1✔
867
   sub_blocks->add_asnum(120, 180);
1✔
868
   sub_blocks->add_asnum(220, 224);
1✔
869
   sub_blocks->add_asnum(260, 511);
1✔
870
   sub_blocks->add_asnum(678);
1✔
871
   sub_blocks->add_asnum(5043, 5100);
1✔
872

873
   sub_blocks->add_rdi(1500, 2300);
1✔
874
   sub_blocks->add_rdi(2500, 4000);
1✔
875
   sub_blocks->add_rdi(1567);
1✔
876
   sub_blocks->add_rdi(33100, 40000);
1✔
877

878
   // create a root ca that does not have any extension
879
   Botan::X509_Cert_Options root_opts = ca_opts();
1✔
880
   auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts);
1✔
881
   Botan::X509_Cert_Options sub_opts = req_opts(sig_algo);
1✔
882
   sub_opts.extensions.add(std::move(sub_blocks));
1✔
883
   Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng);
1✔
884
   Botan::X509_Certificate sub_cert = root_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));
1✔
885

886
   Botan::Certificate_Store_In_Memory trusted;
1✔
887
   trusted.add_certificate(root_cert);
1✔
888

889
   const Botan::Path_Validation_Restrictions restrictions(false, 80);
2✔
890
   const std::vector<Botan::X509_Certificate> certs = {sub_cert};
2✔
891

892
   Botan::Path_Validation_Result path_result = Botan::x509_path_validate(certs, restrictions, trusted);
1✔
893
   result.require("path validation fails", !path_result.successful_validation());
1✔
894

895
   result.end_timer();
1✔
896
   return result;
2✔
897
}
3✔
898

899
Test::Result test_x509_as_blocks_path_validation_failure() {
1✔
900
   Test::Result result("X509 AS Block path validation failure");
1✔
901
   result.start_timer();
1✔
902

903
   using Botan::Cert_Extension::ASBlocks;
1✔
904
   auto rng = Test::new_rng(__func__);
1✔
905

906
   /*
907
   This executes a few permutations, messing around with edge cases when it comes to constructing ranges.
908

909
   Each test is expected to fail and creates the following certificate chain:
910
   Root -> Issuer -> Subject
911

912
   00: set all the asnum choices to 'inherit' for each cert
913
   01: 00 but for rdis
914
   02: make smallest min asnum of the subject smaller than the smallest min asnum of the issuer
915
   03: 02 but for rdis
916
   04: both 02 and 03
917
   05: make largest max asnum of the subject larger than the largest max asnum of the issuer
918
   06: 05 but for rdis
919
   07: both 05 and 06
920
   08: make the certs have multiple ranges and make one asnum range that is not the smallest and not the largest overlap with it's maximum
921
   09: 08 but for rdis
922
   10: both 08 and 09
923
   11: same as 08 but the range in the subject is not contiguous, instead it is the issuers range but split into two ranges (e.g issuer range is 40-60, subject ranges are 40-49 and 51-61)
924
   12: 11 but for rdis
925
   13: both 11 and 12
926
   14: 08 but using the minimum instead of the maximum
927
   15: 14 but for rdis
928
   16: both 14 and 15
929
   17: same as 11 but using the minimum instead of the maximum
930
   18: 17 but for rdis
931
   19: both 18 and 19
932
   20: make the issuer ranges empty but have an entry in the subject ranges
933
   */
934
   for(size_t i = 0; i < 21; i++) {
22✔
935
      // enable / disable all the different edge cases
936
      bool inherit_all_asnums = (i == 0);
21✔
937
      bool inherit_all_rdis = (i == 1);
21✔
938
      bool push_asnum_min_edge_ranges = (i == 2) || (i == 4);
21✔
939
      bool push_rdi_min_edge_ranges = (i == 3) || (i == 4);
21✔
940
      bool push_asnum_max_edge_ranges = (i == 5) || (i == 7);
21✔
941
      bool push_rdi_max_edge_ranges = (i == 6) || (i == 7);
21✔
942
      bool push_asnum_max_middle_ranges = (i == 8) || (i == 10);
21✔
943
      bool push_rdi_max_middle_ranges = (i == 9) || (i == 10);
21✔
944
      bool push_asnum_max_split_ranges = (i == 11) || (i == 13);
21✔
945
      bool push_rdi_max_split_ranges = (i == 12) || (i == 13);
21✔
946
      bool push_asnum_min_middle_ranges = (i == 14) || (i == 16);
21✔
947
      bool push_rdi_min_middle_ranges = (i == 15) || (i == 16);
21✔
948
      bool push_asnum_min_split_ranges = (i == 17) || (i == 19);
21✔
949
      bool push_rdi_min_split_ranges = (i == 18) || (i == 19);
21✔
950
      bool empty_issuer_non_empty_subject = (i == 20);
21✔
951

952
      // Root cert
953
      std::unique_ptr<ASBlocks> root_blocks = std::make_unique<ASBlocks>();
21✔
954

955
      if(!inherit_all_asnums) {
21✔
956
         if(push_asnum_min_edge_ranges || push_asnum_max_edge_ranges) {
20✔
957
            // 100-200 for 02,03,04
958
            root_blocks->add_asnum(100, 200);
4✔
959
         } else if(push_asnum_max_middle_ranges || push_asnum_min_middle_ranges) {
16✔
960
            // 10-20,30-40,50-60 for 08,09,10
961
            root_blocks->add_asnum(10, 20);
4✔
962
            root_blocks->add_asnum(30, 40);
4✔
963
            root_blocks->add_asnum(50, 60);
4✔
964
         } else if(push_asnum_max_split_ranges || push_asnum_min_split_ranges) {
12✔
965
            // 10-20,30-50,60-70 for 11,12,13
966
            root_blocks->add_asnum(10, 20);
4✔
967
            root_blocks->add_asnum(30, 50);
4✔
968
            root_blocks->add_asnum(60, 70);
4✔
969
         }
970
      } else {
971
         root_blocks->inherit_asnum();
1✔
972
      }
973

974
      // same values but for rdis
975
      if(!inherit_all_rdis) {
21✔
976
         if(push_rdi_min_edge_ranges || push_rdi_max_edge_ranges) {
977
            root_blocks->add_rdi(100, 200);
4✔
978
         } else if(push_rdi_max_middle_ranges || push_rdi_min_middle_ranges) {
979
            root_blocks->add_rdi(10, 20);
4✔
980
            root_blocks->add_rdi(30, 40);
4✔
981
            root_blocks->add_rdi(50, 60);
4✔
982
         } else if(push_rdi_max_split_ranges || push_rdi_min_split_ranges) {
983
            root_blocks->add_rdi(10, 20);
4✔
984
            root_blocks->add_rdi(30, 50);
4✔
985
            root_blocks->add_rdi(60, 70);
4✔
986
         }
987
      } else {
988
         root_blocks->inherit_rdi();
1✔
989
      }
990

991
      if(empty_issuer_non_empty_subject) {
21✔
992
         root_blocks->restrict_asnum();
1✔
993
         root_blocks->restrict_rdi();
1✔
994
      }
995

996
      // Issuer cert
997
      // the issuer cert has the same ranges as the root cert
998
      // it is used to check that the 'inherit' check is bubbled up until the root cert is hit
999
      auto issu_blocks = root_blocks->copy();
21✔
1000

1001
      // Subject cert
1002
      std::unique_ptr<ASBlocks> sub_blocks = std::make_unique<ASBlocks>();
21✔
1003

1004
      std::vector<ASBlocks::ASIdOrRange> sub_as_ranges;
21✔
1005
      std::vector<ASBlocks::ASIdOrRange> sub_rdi_ranges;
21✔
1006

1007
      if(!inherit_all_asnums) {
21✔
1008
         // assign the subject asnum ranges
1009
         if(push_asnum_min_edge_ranges) {
1010
            // 99-200 for 02 (so overlapping to the left)
1011
            sub_blocks->add_asnum(99, 200);
2✔
1012
         } else if(push_asnum_max_edge_ranges) {
1013
            // 100-201 for 03 (so overlapping to the right)
1014
            sub_blocks->add_asnum(100, 201);
2✔
1015
         } else if(push_asnum_max_middle_ranges) {
1016
            // same as root, but change the range in the middle to overlap to the right for 08
1017
            sub_blocks->add_asnum(10, 20);
2✔
1018
            sub_blocks->add_asnum(30, 41);
2✔
1019
            sub_blocks->add_asnum(50, 60);
2✔
1020
         } else if(push_asnum_max_split_ranges) {
1021
            // change the range in the middle to be cut at 45 for case 11
1022
            // the left range is 30-44
1023
            // the right range is 46-51 (overlapping the issuer range to the right)
1024
            sub_blocks->add_asnum(10, 20);
2✔
1025
            sub_blocks->add_asnum(30, 44);
2✔
1026
            sub_blocks->add_asnum(46, 51);
2✔
1027
            sub_blocks->add_asnum(60, 70);
2✔
1028
         } else if(push_asnum_min_middle_ranges) {
1029
            // just change the test in the middle to overlap to the left for case 14
1030
            sub_blocks->add_asnum(10, 20);
2✔
1031
            sub_blocks->add_asnum(29, 40);
2✔
1032
            sub_blocks->add_asnum(50, 60);
2✔
1033
         } else if(push_asnum_min_split_ranges) {
1034
            // again split the range in the middle at 45 for case 17
1035
            // creating two ranges 29-44 and 46-50 (so overlapping to the left)
1036
            sub_blocks->add_asnum(10, 20);
2✔
1037
            sub_blocks->add_asnum(29, 44);
2✔
1038
            sub_blocks->add_asnum(46, 50);
2✔
1039
            sub_blocks->add_asnum(60, 70);
2✔
1040
         } else if(empty_issuer_non_empty_subject) {
1041
            sub_blocks->add_asnum(50);
1✔
1042
         }
1043
      } else {
1044
         sub_blocks->inherit_asnum();
1✔
1045
      }
1046

1047
      if(!inherit_all_rdis) {
21✔
1048
         // same values but for rdis
1049
         if(push_rdi_min_edge_ranges) {
1050
            sub_blocks->add_rdi(99, 200);
2✔
1051
         } else if(push_rdi_max_edge_ranges) {
1052
            sub_blocks->add_rdi(100, 201);
2✔
1053
         } else if(push_rdi_max_middle_ranges) {
1054
            sub_blocks->add_rdi(10, 20);
2✔
1055
            sub_blocks->add_rdi(30, 41);
2✔
1056
            sub_blocks->add_rdi(50, 60);
2✔
1057
         } else if(push_rdi_max_split_ranges) {
1058
            sub_blocks->add_rdi(10, 20);
2✔
1059
            sub_blocks->add_rdi(30, 44);
2✔
1060
            sub_blocks->add_rdi(46, 51);
2✔
1061
            sub_blocks->add_rdi(60, 70);
2✔
1062
         } else if(push_rdi_min_middle_ranges) {
1063
            sub_blocks->add_rdi(10, 20);
2✔
1064
            sub_blocks->add_rdi(29, 40);
2✔
1065
            sub_blocks->add_rdi(50, 60);
2✔
1066
         } else if(push_rdi_min_split_ranges) {
1067
            sub_blocks->add_rdi(10, 20);
2✔
1068
            sub_blocks->add_rdi(29, 44);
2✔
1069
            sub_blocks->add_rdi(46, 50);
2✔
1070
            sub_blocks->add_rdi(60, 70);
2✔
1071
         }
1072
      } else {
1073
         sub_blocks->inherit_rdi();
1✔
1074
      }
1075

1076
      Botan::X509_Cert_Options root_opts = ca_opts();
21✔
1077
      root_opts.extensions.add(std::move(root_blocks));
21✔
1078
      auto [root_cert, root_ca, sub_key, sig_algo, hash_fn] = make_ca(rng, root_opts);
21✔
1079
      auto [issu_cert, issu_ca] = make_and_sign_ca(std::move(issu_blocks), root_ca, rng);
42✔
1080

1081
      Botan::X509_Cert_Options sub_opts = req_opts(sig_algo);
21✔
1082
      sub_opts.extensions.add(std::move(sub_blocks));
21✔
1083
      Botan::PKCS10_Request sub_req = Botan::X509::create_cert_req(sub_opts, *sub_key, hash_fn, *rng);
21✔
1084
      Botan::X509_Certificate sub_cert =
21✔
1085
         issu_ca.sign_request(sub_req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01));
21✔
1086

1087
      Botan::Certificate_Store_In_Memory trusted;
21✔
1088
      trusted.add_certificate(root_cert);
21✔
1089

1090
      const Botan::Path_Validation_Restrictions restrictions(false, 80);
42✔
1091
      const std::vector<Botan::X509_Certificate> certs = {sub_cert, issu_cert};
63✔
1092

1093
      Botan::Path_Validation_Result path_result = Botan::x509_path_validate(certs, restrictions, trusted);
21✔
1094
      // in all cases, the validation should fail, since we are creating invalid scenarios
1095
      result.confirm("path validation fails at iteration " + std::to_string(i), !path_result.successful_validation());
42✔
1096
   }
42✔
1097

1098
   result.end_timer();
1✔
1099
   return result;
1✔
1100
}
22✔
1101

1102
class X509_RPKI_Tests final : public Test {
×
1103
   public:
1104
      std::vector<Test::Result> run() override {
1✔
1105
         std::vector<Test::Result> results;
1✔
1106

1107
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
1108
         results.push_back(test_x509_ip_addr_blocks_extension_decode());
2✔
1109
         results.push_back(test_x509_as_blocks_extension_decode());
2✔
1110
   #endif
1111
         results.push_back(test_x509_ip_addr_blocks_rfc3779_example());
2✔
1112
         results.push_back(test_x509_ip_addr_blocks_encoding());
2✔
1113
         results.push_back(test_x509_ip_addr_blocks_path_validation_success());
2✔
1114
         results.push_back(test_x509_ip_addr_blocks_path_validation_failure());
2✔
1115
         results.push_back(test_x509_as_blocks_rfc3779_example());
2✔
1116
         results.push_back(test_x509_as_blocks_encoding());
2✔
1117
         results.push_back(test_x509_as_blocks_path_validation_success());
2✔
1118
         results.push_back(test_x509_as_blocks_path_validation_extension_not_present());
2✔
1119
         results.push_back(test_x509_as_blocks_path_validation_failure());
2✔
1120
         return results;
1✔
1121
      }
×
1122
};
1123

1124
BOTAN_REGISTER_TEST("x509", "x509_rpki", X509_RPKI_Tests);
1125

1126
#endif
1127

1128
}  // namespace
1129

1130
}  // 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