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

zhaozg / lua-openssl / 22761433978

06 Mar 2026 11:26AM UTC coverage: 93.893% (-0.008%) from 93.901%
22761433978

push

travis-ci

zhaozg
fix: openssl v1.1.1 initialize

1 of 1 new or added line in 1 file covered. (100.0%)

54 existing lines in 3 files now uncovered.

10547 of 11233 relevant lines covered (93.89%)

2273.73 hits per line

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

91.18
/src/engine.c
1
/*=========================================================================*\
2
* engine.c
3
* engine object for lua-openssl binding
4
*
5
* Author:  george zhao <zhaozg(at)gmail.com>
6
\*=========================================================================*/
7

8
/***
9
engine module for lua-openssl binding
10

11
OpenSSL engine support allows the use of alternative implementations
12
of cryptographic algorithms. Engines can provide hardware acceleration
13
or alternative software implementations of cryptographic operations.
14

15
@module engine
16
@usage
17
  engine = require('openssl').engine
18
*/
19

20
#include <openssl/engine.h>
21
#include <openssl/ssl.h>
22

23
#include "openssl.h"
24
#include "private.h"
25

26
#ifndef OPENSSL_NO_ENGINE
27

28
/* Suppress deprecation warnings for ENGINE API in OpenSSL 3.0+
29
 * The ENGINE API is deprecated in favor of the Provider API, but we continue
30
 * to use it to maintain backward compatibility. The module may be migrated
31
 * to the Provider API in a future major version. */
32
#if defined(__GNUC__) || defined(__clang__)
33
#pragma GCC diagnostic push
34
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
35
#endif
36
enum
37
{
38
  TYPE_RSA,
39
  TYPE_DSA,
40
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
41
  TYPE_ECDH,
42
  TYPE_ECDSA,
43
#else
44
  TYPE_EC,
45
#endif
46
  TYPE_DH,
47
  TYPE_RAND,
48
  TYPE_CIPHERS,
49
  TYPE_DIGESTS,
50
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
51
  TYPE_STORE,
52
#else
53
  TYPE_PKEY_METHODS,
54
  TYPE_PKEY_ASN1_METHODS,
55
#endif
56
  TYPE_COMPLETE
57
};
58

59
static const char *const list[] = { "RSA", /* 0 */
60
                                    "DSA",
61
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
62
                                    "ECDH", /* 2 */
63
                                    "ECDSA",
64
#else
65
                                    "EC",
66
#endif
67
                                    "DH", /* 4 */
68
                                    "RAND",     "ciphers", "digests", /* 8 */
69
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
70
                                    "STORE", /* 6 */
71
#else
72
                                    "PKEY",     "ASN1",
73
#endif
74
                                    "complete", /* 9 */
75

76
                                    NULL };
77

78
int
79
openssl_engine(lua_State *L)
56✔
80
{
81
  const ENGINE *eng = NULL;
56✔
82
  if (lua_isstring(L, 1)) {
56✔
83
    const char *id = luaL_checkstring(L, 1);
48✔
84
    eng = ENGINE_by_id(id);
48✔
85
  } else if (lua_isboolean(L, 1)) {
8✔
86
    int first = lua_toboolean(L, 1);
8✔
87
    if (first)
8✔
88
      eng = ENGINE_get_first();
4✔
89
    else
90
      eng = ENGINE_get_last();
4✔
91
  } else
92
    luaL_error(L,
×
93
               "#1 may be string or boolean\n"
94
               "\tstring for an engine id to load\n"
95
               "\ttrue for first engine, false or last engine\n"
96
               "\tbut we get %s:%s",
97
               lua_typename(L, lua_type(L, 1)),
98
               lua_tostring(L, 1));
99
  if (eng) {
56✔
100
    PUSH_OBJECT((void *)eng, "openssl.engine");
56✔
101
  } else
UNCOV
102
    lua_pushnil(L);
×
103
  return 1;
56✔
104
}
105

106
/***
107
get next engine in the list
108
@function next
109
@treturn engine|nil next engine object or nil if none
110
*/
111
static int
112
openssl_engine_next(lua_State *L)
10✔
113
{
114
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
10✔
115
  ENGINE_up_ref(eng);
10✔
116
  eng = ENGINE_get_next(eng);
10✔
117
  if (eng) {
10✔
118
    PUSH_OBJECT(eng, "openssl.engine");
6✔
119
  } else
120
    lua_pushnil(L);
4✔
121
  return 1;
10✔
122
}
123

124
/***
125
get previous engine in the internal list
126
@function prev
127
@treturn openssl.engine previous engine object or nil if none
128
*/
129
static int
130
openssl_engine_prev(lua_State *L)
10✔
131
{
132
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
10✔
133
  ENGINE_up_ref(eng);
10✔
134
  eng = ENGINE_get_prev(eng);
10✔
135
  if (eng) {
10✔
136
    PUSH_OBJECT(eng, "openssl.engine");
6✔
137
  } else
138
    lua_pushnil(L);
4✔
139
  return 1;
10✔
140
}
141

142
/***
143
add engine to the internal list for lookup by id or name
144
@function add
145
@treturn boolean true on success, false on failure
146
*/
147
static int
148
openssl_engine_add(lua_State *L)
4✔
149
{
150
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
4✔
151
  int     ret = ENGINE_add(eng);
4✔
152
  lua_pushboolean(L, ret);
4✔
153
  return 1;
4✔
154
}
155

156
/***
157
remove engine from the internal list
158
@function remove
159
@treturn boolean true on success, false on failure
160
*/
161
static int
162
openssl_engine_remove(lua_State *L)
4✔
163
{
164
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
4✔
165
  int     ret = ENGINE_remove(eng);
4✔
166
  lua_pushboolean(L, ret);
4✔
167
  return 1;
4✔
168
}
169

170
/***
171
register engine for specific algorithms
172
@function register
173
@tparam[opt=false] boolean unregister true to unregister, false to register
174
@tparam[opt] string algorithms algorithm types to register for (e.g., "ALL", "RSA", "DSA")
175
@treturn boolean true on success, false on failure
176
*/
177
static int
178
openssl_engine_register(lua_State *L)
80✔
179
{
180
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
80✔
181
  int     unregister = 0;
80✔
182
  int     first = 2;
80✔
183
  int     top = lua_gettop(L);
80✔
184
  if (lua_isboolean(L, 2)) {
80✔
185
    unregister = lua_toboolean(L, 2);
80✔
186
    first = 3;
80✔
187
  };
188
  while (first <= top) {
152✔
189
    int c = luaL_checkoption(L, first, "RSA", list);
80✔
190
    switch (c) {
80✔
191
    case TYPE_RSA:
8✔
192
      if (unregister)
8✔
193
        ENGINE_unregister_RSA(eng);
4✔
194
      else
195
        ENGINE_register_RSA(eng);
4✔
196
      break;
8✔
197
    case TYPE_DSA:
8✔
198
      if (unregister)
8✔
199
        ENGINE_unregister_DSA(eng);
4✔
200
      else
201
        ENGINE_register_DSA(eng);
4✔
202
      break;
8✔
203
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
204
    case TYPE_ECDH:
2✔
205
      if (unregister)
2✔
206
        ENGINE_unregister_ECDH(eng);
1✔
207
      else
208
        ENGINE_register_ECDH(eng);
1✔
209
      break;
2✔
210
    case TYPE_ECDSA:
2✔
211
      if (unregister)
2✔
212
        ENGINE_unregister_ECDSA(eng);
1✔
213
      else
214
        ENGINE_register_ECDSA(eng);
1✔
215
      break;
2✔
216
#else
217
    case TYPE_EC:
6✔
218
      if (unregister)
6✔
219
        ENGINE_unregister_EC(eng);
3✔
220
      else
221
        ENGINE_register_EC(eng);
3✔
222
      break;
6✔
223
#endif
224
    case TYPE_DH:
8✔
225
      if (unregister)
8✔
226
        ENGINE_unregister_DH(eng);
4✔
227
      else
228
        ENGINE_register_DH(eng);
4✔
229
      break;
8✔
230
    case TYPE_RAND:
8✔
231
      if (unregister)
8✔
232
        ENGINE_unregister_RAND(eng);
4✔
233
      else
234
        ENGINE_register_RAND(eng);
4✔
235
      break;
8✔
236
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
237
    case TYPE_STORE:
2✔
238
      if (unregister)
2✔
239
        ENGINE_unregister_STORE(eng);
1✔
240
      else
241
        ENGINE_register_STORE(eng);
1✔
242
      break;
2✔
243
#else
244
    case TYPE_PKEY_METHODS:
6✔
245
      if (unregister)
6✔
246
        ENGINE_unregister_pkey_meths(eng);
3✔
247
      else
248
        ENGINE_register_pkey_meths(eng);
3✔
249
      break;
6✔
250
    case TYPE_PKEY_ASN1_METHODS:
6✔
251
      if (unregister)
6✔
252
        ENGINE_unregister_pkey_asn1_meths(eng);
3✔
253
      else
254
        ENGINE_register_pkey_asn1_meths(eng);
3✔
255
      break;
6✔
256
#endif
257
    case TYPE_CIPHERS:
8✔
258
      if (unregister)
8✔
259
        ENGINE_unregister_ciphers(eng);
4✔
260
      else
261
        ENGINE_register_ciphers(eng);
4✔
262
      break;
8✔
263
    case TYPE_DIGESTS:
8✔
264
      if (unregister)
8✔
265
        ENGINE_unregister_digests(eng);
4✔
266
      else
267
        ENGINE_register_digests(eng);
4✔
268
      break;
8✔
269
    case TYPE_COMPLETE: {
8✔
270
      int ret = ENGINE_register_complete(eng);
8✔
271
      lua_pushboolean(L, ret);
8✔
272
      return 1;
8✔
273
    }
274
    default:
×
275
      luaL_error(L, "not support %d for %s", c, list[c]);
×
276
      break;
×
277
    }
278
    first++;
72✔
279
  }
280
  return 0;
72✔
281
};
282

283
/***
284
control engine operations and settings
285
@function ctrl
286
@tparam number|string cmd control command (number) or command string
287
@tparam[opt] any arg command argument (varies by command)
288
@treturn boolean|any result depends on command type
289
*/
290
static int
291
openssl_engine_ctrl(lua_State *L)
20✔
292
{
293
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
20✔
294
  int     ret = 0;
20✔
295
  if (lua_isnumber(L, 2)) {
20✔
296
    int cmd = luaL_checkint(L, 2);
8✔
297
    if (lua_isnone(L, 3)) {
8✔
298
      ret = ENGINE_cmd_is_executable(eng, cmd);
4✔
299
      lua_pushboolean(L, ret);
4✔
300
      return 1;
4✔
301
    } else {
302
      long  i = (long)luaL_checknumber(L, 3);
4✔
303
      void *p = lua_touserdata(L, 4);
4✔
304
      void *arg = lua_isnoneornil(L, 5) ? NULL : lua_touserdata(L, 5);
4✔
305
      ret = ENGINE_ctrl(eng, cmd, i, p, arg);
4✔
306
    }
307
  } else {
308
    const char *cmd = luaL_checkstring(L, 2);
12✔
309
    if (lua_type(L, 3) == LUA_TNUMBER) {
12✔
310
      long i = (long)luaL_checknumber(L, 3);
8✔
311
      if (lua_isstring(L, 4)) {
8✔
312
        const char *s = lua_tostring(L, 4);
×
313
        void       *arg = lua_isnoneornil(L, 5) ? NULL : lua_touserdata(L, 5);
×
314
        int         opt = luaL_optint(L, 6, 0);
×
315
        ret = ENGINE_ctrl_cmd(eng, cmd, i, (void *)s, arg, opt);
×
316
      } else {
317
        void *p = lua_touserdata(L, 4);
8✔
318
        void *arg = lua_isnoneornil(L, 5) ? NULL : lua_touserdata(L, 5);
8✔
319
        int   opt = luaL_optint(L, 6, 0);
8✔
320
        ret = ENGINE_ctrl_cmd(eng, cmd, i, p, arg, opt);
8✔
321
      }
322
    } else {
323
      const char *arg = luaL_optstring(L, 3, NULL);
4✔
324
      int         opt = luaL_optint(L, 4, 0);
4✔
325
      ret = ENGINE_ctrl_cmd_string(eng, cmd, arg, opt);
4✔
326
    }
327
  }
328
  return openssl_pushresult(L, ret);
16✔
329
}
330

331
static int
332
openssl_engine_gc(lua_State *L)
68✔
333
{
334
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
68✔
335
  ENGINE_free(eng);
68✔
336
  return 0;
68✔
337
}
338

339
/***
340
get or set engine identifier
341
@function id
342
@tparam[opt] string id new engine ID to set
343
@treturn string|boolean engine ID (if getting) or success status (if setting)
344
*/
345
static int
346
openssl_engine_id(lua_State *L)
12✔
347
{
348
  ENGINE     *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
12✔
349
  const char *id = NULL;
12✔
350
  if (lua_isstring(L, 2)) {
12✔
351
    int ret;
352
    id = luaL_checkstring(L, 2);
4✔
353
    ret = ENGINE_set_id(eng, id);
4✔
354
    lua_pushboolean(L, ret);
4✔
355
  } else
356
    lua_pushstring(L, ENGINE_get_id(eng));
8✔
357
  return 1;
12✔
358
}
359

360
/***
361
get or set engine name
362
@function name
363
@tparam[opt] string name new engine name to set
364
@treturn string|boolean engine name (if getting) or success status (if setting)
365
*/
366
static int
367
openssl_engine_name(lua_State *L)
8✔
368
{
369
  ENGINE     *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
8✔
370
  const char *id = NULL;
8✔
371
  if (lua_isstring(L, 2)) {
8✔
372
    int ret;
373
    id = luaL_checkstring(L, 2);
4✔
374
    ret = ENGINE_set_name(eng, id);
4✔
375
    lua_pushboolean(L, ret);
4✔
376
  } else
377
    lua_pushstring(L, ENGINE_get_name(eng));
4✔
378
  return 1;
8✔
379
}
380

381
/***
382
get or set engine flags
383
@function flags
384
@tparam[opt] number flags new engine flags to set
385
@treturn number|boolean engine flags (if getting) or success status (if setting)
386
*/
387
static int
388
openssl_engine_flags(lua_State *L)
8✔
389
{
390
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
8✔
391
  if (!lua_isnone(L, 2)) {
8✔
392
    int flags = luaL_checkint(L, 2);
4✔
393
    int ret = ENGINE_set_flags(eng, flags);
4✔
394
    lua_pushboolean(L, ret);
4✔
395
  } else
396
    lua_pushinteger(L, ENGINE_get_flags(eng));
4✔
397
  return 1;
8✔
398
}
399

400
/*
401
int ENGINE_set_ex_data(ENGINE *e, int idx, void *arg);
402
void *ENGINE_get_ex_data(const ENGINE *e, int idx);
403
*/
404
/***
405
initialize an engine for use
406
@function init
407
@treturn boolean true on success, false on failure
408
*/
409
static int
410
openssl_engine_init(lua_State *L)
4✔
411
{
412
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
4✔
413
  int     ret = ENGINE_init(eng);
4✔
414
  lua_pushboolean(L, ret);
4✔
415
  return 1;
4✔
416
}
417

418
/***
419
release an initialized engine
420
@function finish
421
@treturn boolean true on success, false on failure
422
*/
423
static int
424
openssl_engine_finish(lua_State *L)
4✔
425
{
426
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
4✔
427
  int     ret = ENGINE_finish(eng);
4✔
428
  lua_pushboolean(L, ret);
4✔
429
  return 1;
4✔
430
}
431

432
/***
433
set engine as default for specified algorithm types
434
@function set_default
435
@tparam string ... algorithm types ("RSA", "DSA", "DH", "RAND", "ECDH", "ECDSA", "CIPHERS", "DIGESTS", "STORE", "complete")
436
@treturn boolean true on success, false on failure
437
*/
438
static int
439
openssl_engine_set_default(lua_State *L)
12✔
440
{
441
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
12✔
442
  int     ret = 0;
12✔
443
  int     first = 2;
12✔
444
  int     top = lua_gettop(L);
12✔
445

446
  while (first <= top) {
49✔
447
    int c = luaL_checkoption(L, first, "RSA", list);
37✔
448
    switch (c) {
37✔
449
    case TYPE_RSA:
8✔
450
      ret = ENGINE_set_default_RSA(eng);
8✔
451
      break;
8✔
452
    case TYPE_DSA:
4✔
453
      ret = ENGINE_set_default_DSA(eng);
4✔
454
      break;
4✔
455
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
456
    case TYPE_ECDH:
1✔
457
      ret = ENGINE_set_default_ECDH(eng);
1✔
458
      break;
1✔
459
    case TYPE_ECDSA:
2✔
460
      ret = ENGINE_set_default_ECDSA(eng);
2✔
461
      break;
2✔
462
#else
463
    case TYPE_EC:
6✔
464
      ret = ENGINE_set_default_EC(eng);
6✔
465
      break;
6✔
466
#endif
467
    case TYPE_DH:
4✔
468
      ret = ENGINE_set_default_DH(eng);
4✔
469
      break;
4✔
470
    case TYPE_RAND:
4✔
471
      ret = ENGINE_set_default_RAND(eng);
4✔
472
      break;
4✔
473
    case TYPE_CIPHERS:
4✔
474
      ret = ENGINE_set_default_ciphers(eng);
4✔
475
      break;
4✔
476
    case TYPE_DIGESTS:
4✔
477
      ret = ENGINE_set_default_digests(eng);
4✔
478
      break;
4✔
479
    default:
×
480
      luaL_error(L, "not support '%s' to set default", c, list[c]);
×
481
      break;
×
482
    }
483
    first++;
37✔
484
    if (ret != 1) break;
37✔
485
  }
486
  return openssl_pushresult(L, ret);
12✔
487
};
488

489
/***
490
set random number generator engine
491
@function set_rand_engine
492
@treturn boolean result true for success
493
*/
494
static int
495
openssl_engine_set_rand_engine(lua_State *L)
4✔
496
{
497
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
4✔
498
  int     ret = RAND_set_rand_engine(eng);
4✔
499
  return openssl_pushresult(L, ret);
4✔
500
}
501

502
/***
503
load private key from engine
504
@function load_private_key
505
@tparam string key_id key identifier
506
@treturn openssl.evp_pkey private key object or nil if failed
507
*/
508
static int
509
openssl_engine_load_private_key(lua_State *L)
4✔
510
{
511
  ENGINE     *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
4✔
512
  const char *key_id = luaL_checkstring(L, 2);
4✔
513
  EVP_PKEY   *pkey = ENGINE_load_private_key(eng, key_id, NULL, NULL);
4✔
514
  if (pkey != NULL) {
4✔
515
    PUSH_OBJECT(pkey, "openssl.evp_pkey");
×
516
    return 1;
×
517
  }
518
  return openssl_pushresult(L, 0);
4✔
519
}
520

521
/***
522
load public key from engine
523
@function load_public_key
524
@tparam string key_id key identifier
525
@treturn openssl.evp_pkey public key object or nil if failed
526
*/
527
static int
528
openssl_engine_load_public_key(lua_State *L)
4✔
529
{
530
  ENGINE     *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
4✔
531
  const char *key_id = luaL_checkstring(L, 2);
4✔
532
  EVP_PKEY   *pkey = ENGINE_load_public_key(eng, key_id, NULL, NULL);
4✔
533
  if (pkey != NULL) {
4✔
534
    PUSH_OBJECT(pkey, "openssl.evp_pkey");
×
535
    return 1;
×
536
  }
537
  return openssl_pushresult(L, 0);
4✔
538
}
539

540
/***
541
load SSL client certificate from engine
542
@function load_ssl_client_cert
543
@tparam openssl.ssl ssl SSL connection object
544
@treturn boolean result true for success
545
*/
546
static int
547
openssl_engine_load_ssl_client_cert(lua_State *L)
4✔
548
{
549
  ENGINE *eng = CHECK_OBJECT(1, ENGINE, "openssl.engine");
4✔
550
  SSL    *s = CHECK_OBJECT(2, SSL, "openssl.ssl");
4✔
551
  STACK_OF(X509_NAME) *ca_dn = SSL_get_client_CA_list(s);
4✔
552

553
  X509     *pcert = NULL;
4✔
554
  EVP_PKEY *pkey = NULL;
4✔
555
  STACK_OF(X509) *pothers = NULL;
4✔
556

557
  int ret = ENGINE_load_ssl_client_cert(eng, s, ca_dn, &pcert, &pkey, &pothers, NULL, NULL);
4✔
558
  if (ret == 1) {
4✔
559
    PUSH_OBJECT(pcert, "openssl.x509");
×
560
    if (pkey != NULL) {
×
561
      PUSH_OBJECT(pkey, "openssl.pkey");
×
562
      ret++;
×
563
    }
564
    if (pothers != NULL) {
×
565
      openssl_sk_x509_totable(L, pothers);
×
566
      ret++;
×
567
    }
568
    return ret;
×
569
  }
570
  return openssl_pushresult(L, 0);
4✔
571
}
572

573
static luaL_Reg eng_funcs[] = {
574
  { "next",                 openssl_engine_next                 },
575
  { "prev",                 openssl_engine_prev                 },
576
  { "add",                  openssl_engine_add                  },
577
  { "remove",               openssl_engine_remove               },
578
  { "register",             openssl_engine_register             },
579
  { "ctrl",                 openssl_engine_ctrl                 },
580
  { "id",                   openssl_engine_id                   },
581
  { "name",                 openssl_engine_name                 },
582
  { "flags",                openssl_engine_flags                },
583

584
  { "set_rand_engine",      openssl_engine_set_rand_engine      },
585
  { "load_private_key",     openssl_engine_load_private_key     },
586
  { "load_public_key",      openssl_engine_load_public_key      },
587
  { "load_ssl_client_cert", openssl_engine_load_ssl_client_cert },
588

589
  { "init",                 openssl_engine_init                 },
590
  { "finish",               openssl_engine_finish               },
591
  { "set_default",          openssl_engine_set_default          },
592

593
  { "__gc",                 openssl_engine_gc                   },
594
  { "__tostring",           auxiliar_tostring                   },
595

596
  { NULL,                   NULL                                },
597
};
598

599
int
600
openssl_register_engine(lua_State *L)
43✔
601
{
602
  auxiliar_newclass(L, "openssl.engine", eng_funcs);
43✔
603
  return 0;
43✔
604
}
605

606
#if defined(__GNUC__) || defined(__clang__)
607
#pragma GCC diagnostic pop
608
#endif
609

610
#endif
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