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

zhaozg / lua-openssl / 25776463823

13 May 2026 03:28AM UTC coverage: 91.231% (-2.6%) from 93.832%
25776463823

Pull #408

travis-ci

zhaozg
feat(pqc): Phase 2.4 - Provider Management for PQC

Add PQC provider management capabilities to the provider module:

- Add `provider.query_pqc_algorithms()` to probe and list available PQC
  algorithms by attempting key generation for known PQC algorithm names
- Add `provider.load_pqc_providers()` to auto-detect and load common
  PQC providers (oqsprovider, liboqs, oqs, oqs-provider)
- Auto-load common PQC providers on module initialization (best-effort)
- Support both old OQS names (DILITHIUM2, KYBER768, etc.) and
  standardized NIST names (ML-DSA-44, ML-KEM-768, SLH-DSA-SHA2-*, etc.)
- Add comprehensive LDoc documentation for all new functions
- Add test suite covering query, load, and combined scenarios

This completes Phase 2.4 of the PQC implementation roadmap.
Pull Request #408: Feat/pqc

913 of 1124 new or added lines in 10 files covered. (81.23%)

45 existing lines in 10 files now uncovered.

9519 of 10434 relevant lines covered (91.23%)

1598.73 hits per line

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

0.0
/src/provider.c
1
/***
2
Provider module for OpenSSL 3.0+ provider API support
3

4
This module provides Lua bindings for OpenSSL 3.0+ provider functionality,
5
allowing loading, unloading, and querying of cryptographic providers.
6

7
Key features:
8
- Load and unload providers (default, fips, legacy, base, null)
9
- Query provider parameters (name, version, buildinfo)
10
- Self-test providers (especially important for FIPS)
11
- List available providers
12
- Query available PQC (Post-Quantum Cryptography) algorithms
13
- Auto-load common PQC providers (oqsprovider, liboqs)
14

15
Note: This module is only available with OpenSSL 3.0 or later.
16
LibreSSL does not support the provider API, so it is excluded.
17

18
@module provider
19
@usage
20
  local provider = require('openssl').provider
21
  local default_provider = provider.load('default')
22

23
  -- Query available PQC algorithms
24
  local pqc_algs = provider.query_pqc_algorithms()
25
  for _, alg in ipairs(pqc_algs) do
26
    print(alg)
27
  end
28

29
  -- Auto-load common PQC providers
30
  local loaded = provider.load_pqc_providers()
31
  for name, prov in pairs(loaded) do
32
    print("Loaded:", name)
33
  end
34
*/
35

36
#include "openssl.h"
37
#include "private.h"
38
#include "pkey/pkey.h"
39

40
/* Provider API is only available in OpenSSL 3.0+, not in LibreSSL */
41
#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) && !defined(LIBRESSL_VERSION_NUMBER)
42
#include <openssl/provider.h>
43

44
/***
45
Load a provider by name
46

47
@function load
48
@tparam string name the name of the provider to load (e.g., 'default', 'fips', 'legacy')
49
@tparam[opt] boolean retain if true, the provider will be retained even after all references are released
50
@treturn openssl.provider loaded provider object or nil on failure
51
@treturn string error message if failed
52
@usage
53
  local default = provider.load('default')
54
  local fips = provider.load('fips', true)
55
*/
56
static int openssl_provider_load(lua_State *L)
57
{
58
  const char *name = luaL_checkstring(L, 1);
59
  int retain = lua_isnone(L, 2) ? 0 : lua_toboolean(L, 2);
60
  OSSL_PROVIDER *prov = NULL;
61

62
  if (retain) {
63
    prov = OSSL_PROVIDER_load(NULL, name);
64
  } else {
65
    prov = OSSL_PROVIDER_try_load(NULL, name, 1);
66
  }
67

68
  if (prov != NULL) {
69
    PUSH_OBJECT(prov, "openssl.provider");
70
    return 1;
71
  }
72

73
  return openssl_pushresult(L, 0);
74
}
75

76
/***
77
Get the name of a loaded provider
78

79
@function name
80
@treturn string provider name
81
@usage
82
  local prov = provider.load('default')
83
  print(prov:name())  -- prints "default"
84
*/
85
static int openssl_provider_get_name(lua_State *L)
86
{
87
  OSSL_PROVIDER *prov = CHECK_OBJECT(1, OSSL_PROVIDER, "openssl.provider");
88
  const char *name = OSSL_PROVIDER_get0_name(prov);
89

90
  if (name != NULL) {
91
    lua_pushstring(L, name);
92
    return 1;
93
  }
94

95
  return 0;
96
}
97

98
/***
99
Check if a provider is available
100

101
@function available
102
@treturn boolean true if provider is available and active
103
@usage
104
  local prov = provider.load('default')
105
  if prov:available() then
106
    print("Provider is active")
107
  end
108
*/
109
static int openssl_provider_available(lua_State *L)
110
{
111
  OSSL_PROVIDER *prov = CHECK_OBJECT(1, OSSL_PROVIDER, "openssl.provider");
112
  int available = OSSL_PROVIDER_available(NULL, OSSL_PROVIDER_get0_name(prov));
113

114
  lua_pushboolean(L, available);
115
  return 1;
116
}
117

118
/***
119
Get provider parameters
120

121
@function get_params
122
@tparam table params table of parameter names to query
123
@treturn table table of parameter values
124
@usage
125
  local prov = provider.load('default')
126
  local params = prov:get_params({'name', 'version', 'buildinfo'})
127
  for k, v in pairs(params) do
128
    print(k, v)
129
  end
130
*/
131
static int openssl_provider_get_params(lua_State *L)
132
{
133
  OSSL_PROVIDER *prov = CHECK_OBJECT(1, OSSL_PROVIDER, "openssl.provider");
134
  luaL_checktype(L, 2, LUA_TTABLE);
135

136
  /* Count parameters */
137
  int param_count = 0;
138
  lua_pushnil(L);
139
  while (lua_next(L, 2) != 0) {
140
    param_count++;
141
    lua_pop(L, 1);
142
  }
143

144
  if (param_count == 0) {
145
    lua_newtable(L);
146
    return 1;
147
  }
148

149
  /* Allocate OSSL_PARAM array */
150
  OSSL_PARAM *params = OPENSSL_malloc((param_count + 1) * sizeof(OSSL_PARAM));
151
  if (params == NULL) {
152
    return luaL_error(L, "out of memory");
153
  }
154

155
  /* Build OSSL_PARAM array */
156
  int i = 0;
157
  lua_pushnil(L);
158
  while (lua_next(L, 2) != 0) {
159
    const char *key = lua_tostring(L, -1);
160
    if (key != NULL) {
161
      params[i] = OSSL_PARAM_construct_utf8_ptr(key, NULL, 0);
162
      i++;
163
    }
164
    lua_pop(L, 1);
165
  }
166
  params[i] = OSSL_PARAM_construct_end();
167

168
  /* Get parameters */
169
  int ret = OSSL_PROVIDER_get_params(prov, params);
170

171
  /* Build result table */
172
  lua_newtable(L);
173
  if (ret == 1) {
174
    for (i = 0; params[i].key != NULL; i++) {
175
      if (params[i].data_type == OSSL_PARAM_UTF8_PTR &&
176
          params[i].data != NULL &&
177
          *(char **)params[i].data != NULL) {
178
        lua_pushstring(L, *(char **)params[i].data);
179
        lua_setfield(L, -2, params[i].key);
180
      }
181
    }
182
  }
183

184
  OPENSSL_free(params);
185
  return 1;
186
}
187

188
/***
189
Unload a provider
190

191
@function unload
192
@treturn boolean true on success
193
@usage
194
  local prov = provider.load('legacy')
195
  -- ... use provider
196
  prov:unload()
197
*/
198
static int openssl_provider_unload(lua_State *L)
199
{
200
  OSSL_PROVIDER *prov = CHECK_OBJECT(1, OSSL_PROVIDER, "openssl.provider");
201
  int ret = OSSL_PROVIDER_unload(prov);
202

203
  lua_pushboolean(L, ret);
204
  return 1;
205
}
206

207
/***
208
Self test a provider
209

210
@function self_test
211
@treturn boolean true if self test passes
212
@usage
213
  local prov = provider.load('fips')
214
  if prov:self_test() then
215
    print("FIPS provider self-test passed")
216
  end
217
*/
218
static int openssl_provider_self_test(lua_State *L)
219
{
220
  OSSL_PROVIDER *prov = CHECK_OBJECT(1, OSSL_PROVIDER, "openssl.provider");
221
  int ret = OSSL_PROVIDER_self_test(prov);
222

223
  lua_pushboolean(L, ret);
224
  return 1;
225
}
226

227
/***
228
List all available providers
229

230
@function list
231
@treturn table array of provider names
232
@usage
233
  local providers = provider.list()
234
  for i, name in ipairs(providers) do
235
    print(name)
236
  end
237
*/
238
static int openssl_provider_list_all(lua_State *L)
239
{
240
  /* This is a simplified version - OpenSSL 3.0 doesn't have a direct API to list all
241
     available providers, so we'll try loading common ones */
242
  const char *common_providers[] = {
243
    "default",
244
    "fips",
245
    "legacy",
246
    "base",
247
    "null",
248
    NULL
249
  };
250

251
  lua_newtable(L);
252
  int idx = 1;
253

254
  for (int i = 0; common_providers[i] != NULL; i++) {
255
    if (OSSL_PROVIDER_available(NULL, common_providers[i])) {
256
      lua_pushstring(L, common_providers[i]);
257
      lua_rawseti(L, -2, idx++);
258
    }
259
  }
260

261
  return 1;
262
}
263

264
/***
265
Get provider by name without loading
266

267
@function get
268
@tparam string name the name of the provider
269
@treturn openssl.provider provider object if already loaded, nil otherwise
270
@usage
271
  local prov = provider.get('default')
272
  if prov then
273
    print("Default provider is loaded")
274
  end
275
*/
276
static int openssl_provider_get(lua_State *L)
277
{
278
  const char *name = luaL_checkstring(L, 1);
279

280
  if (OSSL_PROVIDER_available(NULL, name)) {
281
    /* Try to get the provider without incrementing refcount */
282
    OSSL_PROVIDER *prov = OSSL_PROVIDER_load(NULL, name);
283
    if (prov != NULL) {
284
      PUSH_OBJECT(prov, "openssl.provider");
285
      return 1;
286
    }
287
  }
288

289
  lua_pushnil(L);
290
  return 1;
291
}
292

293
/***
294
Provider object garbage collection
295

296
@function __gc
297
@treturn nil always returns nil
298
*/
299
static int openssl_provider_gc(lua_State *L)
300
{
301
  OSSL_PROVIDER *prov = CHECK_OBJECT(1, OSSL_PROVIDER, "openssl.provider");
302
  /* Note: We don't automatically unload here as the provider might be in use
303
     Users should explicitly call unload() if needed */
304
  (void)prov;
305
  return 0;
306
}
307

308
/***
309
Provider object string representation
310

311
@function __tostring
312
@treturn string string representation of provider
313
*/
314
static int openssl_provider_tostring(lua_State *L)
315
{
316
  OSSL_PROVIDER *prov = CHECK_OBJECT(1, OSSL_PROVIDER, "openssl.provider");
317
  const char *name = OSSL_PROVIDER_get0_name(prov);
318

319
  lua_pushfstring(L, "openssl.provider: %s (%p)", name, prov);
320
  return 1;
321
}
322

323
static luaL_Reg provider_funs[] = {
324
  {"name",        openssl_provider_get_name},
325
  {"available",   openssl_provider_available},
326
  {"get_params",  openssl_provider_get_params},
327
  {"unload",      openssl_provider_unload},
328
  {"self_test",   openssl_provider_self_test},
329

330
  {"__gc",        openssl_provider_gc},
331
  {"__tostring",  openssl_provider_tostring},
332

333
  {NULL, NULL}
334
};
335

336
/***
337
Query available PQC (Post-Quantum Cryptography) algorithms
338

339
Probes the current OpenSSL provider configuration for available PQC
340
algorithms by attempting key generation for known PQC algorithm names.
341
Supports both old OQS provider names and standardized NIST names.
342

343
@function query_pqc_algorithms
344
@tparam[opt] table providers optional list of provider names to load first
345
@treturn table array of available PQC algorithm name strings
346
@usage
347
  -- Query all available PQC algorithms
348
  local algs = provider.query_pqc_algorithms()
349
  for _, alg in ipairs(algs) do
350
    print(alg)
351
  end
352

353
  -- Try loading OQS provider first, then query
354
  local algs = provider.query_pqc_algorithms({'oqsprovider', 'liboqs'})
355
*/
356
static int openssl_provider_query_pqc(lua_State *L)
357
{
358
  /* Known PQC algorithm names to probe */
359
  const char *pqc_algorithms[] = {
360
    /* ML-DSA (FIPS 204) - Standardized NIST names */
361
    "ML-DSA-44",
362
    "ML-DSA-65",
363
    "ML-DSA-87",
364
    /* ML-DSA - Old OQS provider names */
365
    "DILITHIUM2",
366
    "DILITHIUM3",
367
    "DILITHIUM5",
368
    /* ML-KEM (FIPS 203) - Standardized NIST names */
369
    "ML-KEM-512",
370
    "ML-KEM-768",
371
    "ML-KEM-1024",
372
    /* ML-KEM - Old OQS provider names */
373
    "KYBER512",
374
    "KYBER768",
375
    "KYBER1024",
376
    /* Falcon */
377
    "FALCON512",
378
    "FALCON1024",
379
    /* SLH-DSA (FIPS 205) - Standardized NIST names */
380
    "SLH-DSA-SHA2-128S",
381
    "SLH-DSA-SHA2-128F",
382
    "SLH-DSA-SHA2-192S",
383
    "SLH-DSA-SHA2-192F",
384
    "SLH-DSA-SHA2-256S",
385
    "SLH-DSA-SHA2-256F",
386
    "SLH-DSA-SHAKE-128S",
387
    "SLH-DSA-SHAKE-128F",
388
    "SLH-DSA-SHAKE-192S",
389
    "SLH-DSA-SHAKE-192F",
390
    "SLH-DSA-SHAKE-256S",
391
    "SLH-DSA-SHAKE-256F",
392
    /* SLH-DSA - Old OQS provider names */
393
    "SPHINCS+-SHA256-128S",
394
    "SPHINCS+-SHA256-128F",
395
    "SPHINCS+-SHA256-192S",
396
    "SPHINCS+-SHA256-192F",
397
    "SPHINCS+-SHA256-256S",
398
    "SPHINCS+-SHA256-256F",
399
    "SPHINCS+-SHAKE256-128S",
400
    "SPHINCS+-SHAKE256-128F",
401
    "SPHINCS+-SHAKE256-192S",
402
    "SPHINCS+-SHAKE256-192F",
403
    "SPHINCS+-SHAKE256-256S",
404
    "SPHINCS+-SHAKE256-256F",
405
    NULL
406
  };
407

408
  /* Optional: try loading providers from argument list first */
409
  if (!lua_isnone(L, 1)) {
410
    luaL_checktype(L, 1, LUA_TTABLE);
411
    lua_pushnil(L);
412
    while (lua_next(L, 1) != 0) {
413
      if (lua_type(L, -1) == LUA_TSTRING) {
414
        const char *prov_name = lua_tostring(L, -1);
415
        /* Try to load the provider, don't retain */
416
        OSSL_PROVIDER_try_load(NULL, prov_name, 1);
417
      }
418
      lua_pop(L, 1);
419
    }
420
  }
421

422
  lua_newtable(L);
423
  int idx = 1;
424

425
  for (int i = 0; pqc_algorithms[i] != NULL; i++) {
426
    const char *alg_name = pqc_algorithms[i];
427
    int nid = OBJ_txt2nid(alg_name);
428
    if (nid == NID_undef) {
429
      /* Try case-insensitive lookup via evp_pkey_name2type */
430
      nid = evp_pkey_name2type(alg_name);
431
    }
432
    if (nid == NID_undef)
433
      continue;
434

435
    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(nid, NULL);
436
    if (ctx == NULL)
437
      continue;
438

439
    if (EVP_PKEY_keygen_init(ctx) == 1) {
440
      lua_pushstring(L, alg_name);
441
      lua_rawseti(L, -2, idx++);
442
    }
443
    EVP_PKEY_CTX_free(ctx);
444
  }
445

446
  return 1;
447
}
448

449
/***
450
Auto-load common PQC (Post-Quantum Cryptography) providers
451

452
Attempts to load well-known PQC providers in order of preference.
453
Common providers tried: oqsprovider, liboqs, oqs, oqs-provider.
454
Returns a table mapping provider names to loaded provider objects.
455

456
@function load_pqc_providers
457
@treturn table table mapping provider names to loaded openssl.provider objects
458
@usage
459
  local loaded = provider.load_pqc_providers()
460
  if next(loaded) then
461
    for name, prov in pairs(loaded) do
462
      print("Loaded PQC provider:", name)
463
    end
464
  else
465
    print("No PQC providers available")
466
  end
467
*/
468
static int openssl_provider_load_pqc(lua_State *L)
469
{
470
  /* Common PQC provider names to try, in order of preference */
471
  const char *pqc_providers[] = {
472
    "oqsprovider",
473
    "liboqs",
474
    "oqs",
475
    "oqs-provider",
476
    NULL
477
  };
478

479
  lua_newtable(L);
480

481
  for (int i = 0; pqc_providers[i] != NULL; i++) {
482
    const char *name = pqc_providers[i];
483

484
    /* Skip if already loaded */
485
    if (OSSL_PROVIDER_available(NULL, name))
486
      continue;
487

488
    /* Try to load (non-retained, so it can be unloaded if no algorithms found) */
489
    OSSL_PROVIDER *prov = OSSL_PROVIDER_try_load(NULL, name, 1);
490
    if (prov != NULL) {
491
      /* Verify it actually provides PQC algorithms by probing */
492
      int has_pqc = 0;
493
      const char *probe_algs[] = {
494
        "ML-DSA-44", "DILITHIUM2", "ML-KEM-768", "KYBER768", NULL
495
      };
496
      for (int j = 0; probe_algs[j] != NULL; j++) {
497
        int nid = OBJ_txt2nid(probe_algs[j]);
498
        if (nid == NID_undef)
499
          nid = evp_pkey_name2type(probe_algs[j]);
500
        if (nid != NID_undef) {
501
          EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(nid, NULL);
502
          if (ctx) {
503
            if (EVP_PKEY_keygen_init(ctx) == 1) {
504
              has_pqc = 1;
505
              EVP_PKEY_CTX_free(ctx);
506
              break;
507
            }
508
            EVP_PKEY_CTX_free(ctx);
509
          }
510
        }
511
      }
512

513
      if (has_pqc) {
514
        /* Provider has PQC algorithms, push as userdata */
515
        PUSH_OBJECT(prov, "openssl.provider");
516
        lua_setfield(L, -2, name);
517
      } else {
518
        /* No PQC algorithms, unload it */
519
        OSSL_PROVIDER_unload(prov);
520
      }
521
    }
522
  }
523

524
  return 1;
525
}
526

527
static luaL_Reg provider_funcs[] = {
528
  {"load",  openssl_provider_load},
529
  {"list",  openssl_provider_list_all},
530
  {"get",   openssl_provider_get},
531
  {"query_pqc_algorithms", openssl_provider_query_pqc},
532
  {"load_pqc_providers", openssl_provider_load_pqc},
533

534
  {NULL, NULL}
535
};
536

537
int luaopen_provider(lua_State *L)
538
{
539
  auxiliar_newclass(L, "openssl.provider", provider_funs);
540

541
  lua_newtable(L);
542
  luaL_setfuncs(L, provider_funcs, 0);
543

544
  /* Auto-load common PQC providers on initialization.
545
   * This is a best-effort attempt - if no PQC providers are installed,
546
   * it silently continues. Users can call load_pqc_providers() later
547
   * to retry or query_pqc_algorithms() to check availability.
548
   */
549
  const char *auto_pqc_providers[] = {
550
    "oqsprovider",
551
    "liboqs",
552
    "oqs",
553
    NULL
554
  };
555
  for (int i = 0; auto_pqc_providers[i] != NULL; i++) {
556
    if (!OSSL_PROVIDER_available(NULL, auto_pqc_providers[i])) {
557
      OSSL_PROVIDER_try_load(NULL, auto_pqc_providers[i], 1);
558
    }
559
  }
560

561
  return 1;
562
}
563

564
#else
565

566
/* For OpenSSL < 3.0, provide stub module */
UNCOV
567
int luaopen_provider(lua_State *L)
×
568
{
UNCOV
569
  lua_newtable(L);
×
UNCOV
570
  lua_pushstring(L, "Provider API requires OpenSSL 3.0 or later");
×
UNCOV
571
  lua_setfield(L, -2, "_error");
×
UNCOV
572
  return 1;
×
573
}
574

575
#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