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

randombit / botan / 16293079084

15 Jul 2025 12:20PM UTC coverage: 90.627% (+0.003%) from 90.624%
16293079084

push

github

web-flow
Merge pull request #4990 from randombit/jack/string-and-span

Improve string<->span conversions

99640 of 109945 relevant lines covered (90.63%)

12253617.72 hits per line

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

93.51
/src/lib/ffi/ffi_cert.cpp
1
/*
2
* (C) 2015,2017,2018 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include <botan/ffi.h>
8

9
#include <botan/internal/ffi_pkey.h>
10
#include <botan/internal/ffi_util.h>
11
#include <memory>
12

13
#if defined(BOTAN_HAS_X509_CERTIFICATES)
14
   #include <botan/data_src.h>
15
   #include <botan/x509_crl.h>
16
   #include <botan/x509cert.h>
17
   #include <botan/x509path.h>
18
#endif
19

20
extern "C" {
21

22
using namespace Botan_FFI;
23

24
#if defined(BOTAN_HAS_X509_CERTIFICATES)
25

26
BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937);
24✔
27

28
#endif
29

30
int botan_x509_cert_load_file(botan_x509_cert_t* cert_obj, const char* cert_path) {
23✔
31
   if(cert_obj == nullptr || cert_path == nullptr) {
23✔
32
      return BOTAN_FFI_ERROR_NULL_POINTER;
33
   }
34

35
#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
36

37
   return ffi_guard_thunk(__func__, [=]() -> int {
23✔
38
      auto c = std::make_unique<Botan::X509_Certificate>(cert_path);
23✔
39
      return ffi_new_object(cert_obj, std::move(c));
23✔
40
   });
46✔
41

42
#else
43
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
44
#endif
45
}
46

47
int botan_x509_cert_dup(botan_x509_cert_t* cert_obj, botan_x509_cert_t cert) {
1✔
48
   if(cert_obj == nullptr) {
1✔
49
      return BOTAN_FFI_ERROR_NULL_POINTER;
50
   }
51

52
#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
53

54
   return ffi_guard_thunk(__func__, [=]() -> int {
1✔
55
      auto c = std::make_unique<Botan::X509_Certificate>(safe_get(cert));
1✔
56
      return ffi_new_object(cert_obj, std::move(c));
1✔
57
   });
2✔
58

59
#else
60
   BOTAN_UNUSED(cert);
61
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
62
#endif
63
}
64

65
int botan_x509_cert_load(botan_x509_cert_t* cert_obj, const uint8_t cert_bits[], size_t cert_bits_len) {
×
66
   if(cert_obj == nullptr || cert_bits == nullptr) {
×
67
      return BOTAN_FFI_ERROR_NULL_POINTER;
68
   }
69

70
#if defined(BOTAN_HAS_X509_CERTIFICATES)
71
   return ffi_guard_thunk(__func__, [=]() -> int {
×
72
      Botan::DataSource_Memory bits(cert_bits, cert_bits_len);
×
73
      auto c = std::make_unique<Botan::X509_Certificate>(bits);
×
74
      return ffi_new_object(cert_obj, std::move(c));
×
75
   });
×
76
#else
77
   BOTAN_UNUSED(cert_bits_len);
78
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
79
#endif
80
}
81

82
int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key) {
2✔
83
   if(key == nullptr) {
2✔
84
      return BOTAN_FFI_ERROR_NULL_POINTER;
85
   }
86

87
   *key = nullptr;
2✔
88

89
#if defined(BOTAN_HAS_X509_CERTIFICATES)
90
   return ffi_guard_thunk(__func__, [=]() -> int {
2✔
91
      auto public_key = safe_get(cert).subject_public_key();
2✔
92
      return ffi_new_object(key, std::move(public_key));
2✔
93
   });
4✔
94
#else
95
   BOTAN_UNUSED(cert);
96
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
97
#endif
98
}
99

100
int botan_x509_cert_get_issuer_dn(
8✔
101
   botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len) {
102
#if defined(BOTAN_HAS_X509_CERTIFICATES)
103
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int {
16✔
104
      auto issuer_info = c.issuer_info(key);
105
      if(index < issuer_info.size()) {
106
         // TODO(Botan4) change the type of out and remove this cast
107
         return write_str_output(reinterpret_cast<char*>(out), out_len, c.issuer_info(key).at(index));
108
      } else {
109
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
110
      }
111
   });
112
#else
113
   BOTAN_UNUSED(cert, key, index, out, out_len);
114
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
115
#endif
116
}
117

118
int botan_x509_cert_get_subject_dn(
8✔
119
   botan_x509_cert_t cert, const char* key, size_t index, uint8_t out[], size_t* out_len) {
120
#if defined(BOTAN_HAS_X509_CERTIFICATES)
121
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int {
16✔
122
      auto subject_info = c.subject_info(key);
123
      if(index < subject_info.size()) {
124
         // TODO(Botan4) change the type of out and remove this cast
125
         return write_str_output(reinterpret_cast<char*>(out), out_len, c.subject_info(key).at(index));
126
      } else {
127
         return BOTAN_FFI_ERROR_BAD_PARAMETER;
128
      }
129
   });
130
#else
131
   BOTAN_UNUSED(cert, key, index, out, out_len);
132
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
133
#endif
134
}
135

136
int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len) {
2✔
137
   return copy_view_str(reinterpret_cast<uint8_t*>(out), out_len, botan_x509_cert_view_as_string, cert);
2✔
138
}
139

140
int botan_x509_cert_view_as_string(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_str_fn view) {
3✔
141
#if defined(BOTAN_HAS_X509_CERTIFICATES)
142
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) { return invoke_view_callback(view, ctx, c.to_string()); });
9✔
143
#else
144
   BOTAN_UNUSED(cert, ctx, view);
145
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
146
#endif
147
}
148

149
int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage) {
7✔
150
#if defined(BOTAN_HAS_X509_CERTIFICATES)
151
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) -> int {
14✔
152
      const Botan::Key_Constraints k = static_cast<Botan::Key_Constraints>(key_usage);
153
      if(c.allowed_usage(k)) {
154
         return BOTAN_FFI_SUCCESS;
155
      }
156
      return 1;
157
   });
158
#else
159
   BOTAN_UNUSED(cert, key_usage);
160
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
161
#endif
162
}
163

164
int botan_x509_cert_destroy(botan_x509_cert_t cert) {
24✔
165
#if defined(BOTAN_HAS_X509_CERTIFICATES)
166
   return BOTAN_FFI_CHECKED_DELETE(cert);
24✔
167
#else
168
   BOTAN_UNUSED(cert);
169
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
170
#endif
171
}
172

173
int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len) {
3✔
174
#if defined(BOTAN_HAS_X509_CERTIFICATES)
175
   return BOTAN_FFI_VISIT(cert,
6✔
176
                          [=](const auto& c) { return write_str_output(out, out_len, c.not_before().to_string()); });
177
#else
178
   BOTAN_UNUSED(cert, out, out_len);
179
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
180
#endif
181
}
182

183
int botan_x509_cert_get_time_expires(botan_x509_cert_t cert, char out[], size_t* out_len) {
2✔
184
#if defined(BOTAN_HAS_X509_CERTIFICATES)
185
   return BOTAN_FFI_VISIT(cert,
4✔
186
                          [=](const auto& c) { return write_str_output(out, out_len, c.not_after().to_string()); });
187
#else
188
   BOTAN_UNUSED(cert, out, out_len);
189
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
190
#endif
191
}
192

193
int botan_x509_cert_not_before(botan_x509_cert_t cert, uint64_t* time_since_epoch) {
2✔
194
#if defined(BOTAN_HAS_X509_CERTIFICATES)
195
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) { *time_since_epoch = c.not_before().time_since_epoch(); });
4✔
196
#else
197
   BOTAN_UNUSED(cert, time_since_epoch);
198
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
199
#endif
200
}
201

202
int botan_x509_cert_not_after(botan_x509_cert_t cert, uint64_t* time_since_epoch) {
2✔
203
#if defined(BOTAN_HAS_X509_CERTIFICATES)
204
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) { *time_since_epoch = c.not_after().time_since_epoch(); });
4✔
205
#else
206
   BOTAN_UNUSED(cert, time_since_epoch);
207
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
208
#endif
209
}
210

211
int botan_x509_cert_get_serial_number(botan_x509_cert_t cert, uint8_t out[], size_t* out_len) {
3✔
212
#if defined(BOTAN_HAS_X509_CERTIFICATES)
213
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) { return write_vec_output(out, out_len, c.serial_number()); });
6✔
214
#else
215
   BOTAN_UNUSED(cert, out, out_len);
216
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
217
#endif
218
}
219

220
int botan_x509_cert_get_fingerprint(botan_x509_cert_t cert, const char* hash, uint8_t out[], size_t* out_len) {
3✔
221
#if defined(BOTAN_HAS_X509_CERTIFICATES)
222
   // TODO(Botan4) change the type of out and remove this cast
223

224
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) {
6✔
225
      return write_str_output(reinterpret_cast<char*>(out), out_len, c.fingerprint(hash));
226
   });
227
#else
228
   BOTAN_UNUSED(cert, hash, out, out_len);
229
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
230
#endif
231
}
232

233
int botan_x509_cert_get_authority_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len) {
1✔
234
#if defined(BOTAN_HAS_X509_CERTIFICATES)
235
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) { return write_vec_output(out, out_len, c.authority_key_id()); });
2✔
236
#else
237
   BOTAN_UNUSED(cert, out, out_len);
238
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
239
#endif
240
}
241

242
int botan_x509_cert_get_subject_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len) {
3✔
243
#if defined(BOTAN_HAS_X509_CERTIFICATES)
244
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) { return write_vec_output(out, out_len, c.subject_key_id()); });
6✔
245
#else
246
   BOTAN_UNUSED(cert, out, out_len);
247
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
248
#endif
249
}
250

251
int botan_x509_cert_get_public_key_bits(botan_x509_cert_t cert, uint8_t out[], size_t* out_len) {
2✔
252
   return copy_view_bin(out, out_len, botan_x509_cert_view_public_key_bits, cert);
2✔
253
}
254

255
int botan_x509_cert_view_public_key_bits(botan_x509_cert_t cert, botan_view_ctx ctx, botan_view_bin_fn view) {
3✔
256
#if defined(BOTAN_HAS_X509_CERTIFICATES)
257
   return BOTAN_FFI_VISIT(cert,
6✔
258
                          [=](const auto& c) { return invoke_view_callback(view, ctx, c.subject_public_key_bits()); });
259
#else
260
   BOTAN_UNUSED(cert, ctx, view);
261
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
262
#endif
263
}
264

265
int botan_x509_cert_hostname_match(botan_x509_cert_t cert, const char* hostname) {
6✔
266
   if(hostname == nullptr) {
6✔
267
      return BOTAN_FFI_ERROR_NULL_POINTER;
268
   }
269

270
#if defined(BOTAN_HAS_X509_CERTIFICATES)
271
   return BOTAN_FFI_VISIT(cert, [=](const auto& c) { return c.matches_dns_name(hostname) ? 0 : -1; });
12✔
272
#else
273
   BOTAN_UNUSED(cert);
274
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
275
#endif
276
}
277

278
int botan_x509_cert_verify(int* result_code,
4✔
279
                           botan_x509_cert_t cert,
280
                           const botan_x509_cert_t* intermediates,
281
                           size_t intermediates_len,
282
                           const botan_x509_cert_t* trusted,
283
                           size_t trusted_len,
284
                           const char* trusted_path,
285
                           size_t required_strength,
286
                           const char* hostname_cstr,
287
                           uint64_t reference_time) {
288
   if(required_strength == 0) {
4✔
289
      required_strength = 110;
3✔
290
   }
291

292
#if defined(BOTAN_HAS_X509_CERTIFICATES)
293
   return ffi_guard_thunk(__func__, [=]() -> int {
4✔
294
      const std::string hostname((hostname_cstr == nullptr) ? "" : hostname_cstr);
4✔
295
      const Botan::Usage_Type usage = Botan::Usage_Type::UNSPECIFIED;
4✔
296
      const auto validation_time = reference_time == 0
4✔
297
                                      ? std::chrono::system_clock::now()
4✔
298
                                      : std::chrono::system_clock::from_time_t(static_cast<time_t>(reference_time));
×
299

300
      std::vector<Botan::X509_Certificate> end_certs;
4✔
301
      end_certs.push_back(safe_get(cert));
4✔
302
      for(size_t i = 0; i != intermediates_len; ++i) {
9✔
303
         end_certs.push_back(safe_get(intermediates[i]));
5✔
304
      }
305

306
      std::unique_ptr<Botan::Certificate_Store> trusted_from_path;
4✔
307
      std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_extra;
4✔
308
      std::vector<Botan::Certificate_Store*> trusted_roots;
4✔
309

310
      if(trusted_path && *trusted_path) {
4✔
311
         trusted_from_path = std::make_unique<Botan::Certificate_Store_In_Memory>(trusted_path);
×
312
         trusted_roots.push_back(trusted_from_path.get());
×
313
      }
314

315
      if(trusted_len > 0) {
4✔
316
         trusted_extra = std::make_unique<Botan::Certificate_Store_In_Memory>();
8✔
317
         for(size_t i = 0; i != trusted_len; ++i) {
8✔
318
            trusted_extra->add_certificate(safe_get(trusted[i]));
4✔
319
         }
320
         trusted_roots.push_back(trusted_extra.get());
4✔
321
      }
322

323
      Botan::Path_Validation_Restrictions restrictions(false, required_strength);
8✔
324

325
      auto validation_result =
4✔
326
         Botan::x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, validation_time);
4✔
327

328
      if(result_code) {
4✔
329
         *result_code = static_cast<int>(validation_result.result());
4✔
330
      }
331

332
      if(validation_result.successful_validation()) {
4✔
333
         return 0;
334
      } else {
335
         return 1;
3✔
336
      }
337
   });
4✔
338
#else
339
   BOTAN_UNUSED(result_code, cert, intermediates, intermediates_len, trusted);
340
   BOTAN_UNUSED(trusted_len, trusted_path, hostname_cstr, reference_time);
341
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
342
#endif
343
}
344

345
const char* botan_x509_cert_validation_status(int code) {
11✔
346
   if(code < 0) {
11✔
347
      return nullptr;
348
   }
349

350
#if defined(BOTAN_HAS_X509_CERTIFICATES)
351
   Botan::Certificate_Status_Code sc = static_cast<Botan::Certificate_Status_Code>(code);
11✔
352
   return Botan::to_string(sc);
11✔
353
#else
354
   return nullptr;
355
#endif
356
}
357

358
#if defined(BOTAN_HAS_X509_CERTIFICATES)
359

360
BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910);
7✔
361

362
#endif
363

364
int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, const char* crl_path) {
6✔
365
   if(crl_obj == nullptr || crl_path == nullptr) {
6✔
366
      return BOTAN_FFI_ERROR_NULL_POINTER;
367
   }
368

369
#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
370

371
   return ffi_guard_thunk(__func__, [=]() -> int {
6✔
372
      auto c = std::make_unique<Botan::X509_CRL>(crl_path);
6✔
373
      return ffi_new_object(crl_obj, std::move(c));
12✔
374
   });
12✔
375

376
#else
377
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
378
#endif
379
}
380

381
int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], size_t crl_bits_len) {
1✔
382
   if(crl_obj == nullptr || crl_bits == nullptr) {
1✔
383
      return BOTAN_FFI_ERROR_NULL_POINTER;
384
   }
385

386
#if defined(BOTAN_HAS_X509_CERTIFICATES)
387
   return ffi_guard_thunk(__func__, [=]() -> int {
1✔
388
      Botan::DataSource_Memory bits(crl_bits, crl_bits_len);
1✔
389
      auto c = std::make_unique<Botan::X509_CRL>(bits);
1✔
390
      return ffi_new_object(crl_obj, std::move(c));
1✔
391
   });
3✔
392
#else
393
   BOTAN_UNUSED(crl_bits_len);
394
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
395
#endif
396
}
397

398
int botan_x509_crl_destroy(botan_x509_crl_t crl) {
7✔
399
#if defined(BOTAN_HAS_X509_CERTIFICATES)
400
   return BOTAN_FFI_CHECKED_DELETE(crl);
7✔
401
#else
402
   BOTAN_UNUSED(crl);
403
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
404
#endif
405
}
406

407
int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert) {
6✔
408
#if defined(BOTAN_HAS_X509_CERTIFICATES)
409
   return BOTAN_FFI_VISIT(crl, [=](const auto& c) { return c.is_revoked(safe_get(cert)) ? 0 : -1; });
12✔
410
#else
411
   BOTAN_UNUSED(cert);
412
   BOTAN_UNUSED(crl);
413
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
414
#endif
415
}
416

417
int botan_x509_cert_verify_with_crl(int* result_code,
12✔
418
                                    botan_x509_cert_t cert,
419
                                    const botan_x509_cert_t* intermediates,
420
                                    size_t intermediates_len,
421
                                    const botan_x509_cert_t* trusted,
422
                                    size_t trusted_len,
423
                                    const botan_x509_crl_t* crls,
424
                                    size_t crls_len,
425
                                    const char* trusted_path,
426
                                    size_t required_strength,
427
                                    const char* hostname_cstr,
428
                                    uint64_t reference_time) {
429
   if(required_strength == 0) {
12✔
430
      required_strength = 110;
2✔
431
   }
432

433
#if defined(BOTAN_HAS_X509_CERTIFICATES)
434
   return ffi_guard_thunk(__func__, [=]() -> int {
12✔
435
      const std::string hostname((hostname_cstr == nullptr) ? "" : hostname_cstr);
14✔
436
      const Botan::Usage_Type usage = Botan::Usage_Type::UNSPECIFIED;
12✔
437
      const auto validation_time = reference_time == 0
12✔
438
                                      ? std::chrono::system_clock::now()
12✔
439
                                      : std::chrono::system_clock::from_time_t(static_cast<time_t>(reference_time));
1✔
440

441
      std::vector<Botan::X509_Certificate> end_certs;
12✔
442
      end_certs.push_back(safe_get(cert));
12✔
443
      for(size_t i = 0; i != intermediates_len; ++i) {
30✔
444
         end_certs.push_back(safe_get(intermediates[i]));
18✔
445
      }
446

447
      std::unique_ptr<Botan::Certificate_Store> trusted_from_path;
12✔
448
      std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_extra;
12✔
449
      std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_crls;
12✔
450
      std::vector<Botan::Certificate_Store*> trusted_roots;
12✔
451

452
      if(trusted_path && *trusted_path) {
12✔
453
         trusted_from_path = std::make_unique<Botan::Certificate_Store_In_Memory>(trusted_path);
2✔
454
         trusted_roots.push_back(trusted_from_path.get());
2✔
455
      }
456

457
      if(trusted_len > 0) {
12✔
458
         trusted_extra = std::make_unique<Botan::Certificate_Store_In_Memory>();
18✔
459
         for(size_t i = 0; i != trusted_len; ++i) {
18✔
460
            trusted_extra->add_certificate(safe_get(trusted[i]));
9✔
461
         }
462
         trusted_roots.push_back(trusted_extra.get());
9✔
463
      }
464

465
      if(crls_len > 0) {
12✔
466
         trusted_crls = std::make_unique<Botan::Certificate_Store_In_Memory>();
10✔
467
         for(size_t i = 0; i != crls_len; ++i) {
13✔
468
            trusted_crls->add_crl(safe_get(crls[i]));
8✔
469
         }
470
         trusted_roots.push_back(trusted_crls.get());
5✔
471
      }
472

473
      Botan::Path_Validation_Restrictions restrictions(false, required_strength);
24✔
474

475
      auto validation_result =
12✔
476
         Botan::x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, validation_time);
12✔
477

478
      if(result_code) {
12✔
479
         *result_code = static_cast<int>(validation_result.result());
12✔
480
      }
481

482
      if(validation_result.successful_validation()) {
12✔
483
         return 0;
484
      } else {
485
         return 1;
8✔
486
      }
487
   });
14✔
488
#else
489
   BOTAN_UNUSED(result_code, cert, intermediates, intermediates_len, trusted);
490
   BOTAN_UNUSED(trusted_len, trusted_path, hostname_cstr, reference_time, crls, crls_len);
491
   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
492
#endif
493
}
494
}
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