Skip to content

Commit 69a078e

Browse files
Copilotzhaozg
andauthored
Implement OpenSSL 3.0 OSSL_PARAM API binding for RSA key parameters (#377)
* Implement OSSL_PARAM API binding for RSA key parameters - Enhanced param.c to support RSA key parameters (n, e, d, p, q, exponents, coefficients) - Updated RSA parse function to use OSSL_PARAM API in OpenSSL 3.0+ - Added fallback to legacy RSA_get0_* functions for backward compatibility - Exported RSA parameter definitions in param module - Created comprehensive test suite for OSSL_PARAM functionality - All tests passing (7/7) Co-authored-by: zhaozg <[email protected]> * Add OSSL_PARAM API usage documentation --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: zhaozg <[email protected]>
1 parent 8289c8b commit 69a078e

File tree

4 files changed

+407
-20
lines changed

4 files changed

+407
-20
lines changed

docs/OSSL_PARAM_USAGE.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# OSSL_PARAM API Usage Guide
2+
3+
## Overview
4+
5+
Starting with OpenSSL 3.0, the OSSL_PARAM API is the modern way to access cryptographic key parameters. lua-openssl now supports this API while maintaining backward compatibility with OpenSSL 1.x.
6+
7+
## What is OSSL_PARAM?
8+
9+
OSSL_PARAM is OpenSSL 3.0's unified parameter system that replaces many legacy key access functions. It provides a consistent interface for accessing parameters across different key types (RSA, EC, DH, etc.).
10+
11+
## Supported Parameters
12+
13+
### RSA Key Parameters
14+
15+
The following RSA parameters are accessible via the param module:
16+
17+
```lua
18+
local param = require("openssl").param
19+
20+
-- Available RSA parameters:
21+
-- param.rsa.n - Modulus (public)
22+
-- param.rsa.e - Public exponent
23+
-- param.rsa.d - Private exponent
24+
-- param.rsa["rsa-factor1"] - Prime p
25+
-- param.rsa["rsa-factor2"] - Prime q
26+
-- param.rsa["rsa-exponent1"] - dmp1 (d mod (p-1))
27+
-- param.rsa["rsa-exponent2"] - dmq1 (d mod (q-1))
28+
-- param.rsa["rsa-coefficient1"] - iqmp (q^-1 mod p)
29+
```
30+
31+
### KDF Parameters
32+
33+
KDF parameters continue to work as before. See `test/2.kdf.lua` for examples.
34+
35+
## Usage Examples
36+
37+
### Parsing RSA Key Parameters
38+
39+
```lua
40+
local openssl = require("openssl")
41+
local rsa = openssl.rsa
42+
43+
-- Generate an RSA key
44+
local key = rsa.generate_key(2048)
45+
46+
-- Parse the key to access parameters
47+
local params = key:parse()
48+
49+
-- Access parameters
50+
print("Key size:", params.size, "bytes")
51+
print("Key bits:", params.bits)
52+
53+
-- Access public parameters (always available)
54+
print("Modulus n:", params.n) -- BIGNUM object
55+
print("Exponent e:", params.e) -- BIGNUM object
56+
57+
-- Access private parameters (only for private keys)
58+
if params.d then
59+
print("Private exponent d:", params.d)
60+
print("Prime p:", params.p)
61+
print("Prime q:", params.q)
62+
print("CRT exponent dmp1:", params.dmp1)
63+
print("CRT exponent dmq1:", params.dmq1)
64+
print("CRT coefficient iqmp:", params.iqmp)
65+
end
66+
```
67+
68+
### Checking Parameter Availability
69+
70+
```lua
71+
local param = require("openssl").param
72+
73+
-- Check what RSA parameters are defined
74+
for name, info in pairs(param.rsa) do
75+
print(string.format("Parameter: %s, Type: %d", name, info.type))
76+
if info.number_type then
77+
print(string.format(" Number type: %s", info.number_type))
78+
end
79+
end
80+
```
81+
82+
## Implementation Details
83+
84+
### OpenSSL 3.0+ Path
85+
86+
When running on OpenSSL 3.0 or later, `rsa:parse()` uses the modern `EVP_PKEY_get_bn_param()` API to extract parameters. This is the recommended approach for new code.
87+
88+
### OpenSSL 1.x Path
89+
90+
On OpenSSL 1.x, or when the PARAM API fails (e.g., for legacy keys), the implementation automatically falls back to the traditional `RSA_get0_key()`, `RSA_get0_factors()`, and `RSA_get0_crt_params()` functions.
91+
92+
### Compatibility
93+
94+
The dual-path implementation ensures:
95+
- ✅ Works with OpenSSL 1.x (using legacy API)
96+
- ✅ Works with OpenSSL 3.0+ (using PARAM API)
97+
- ✅ Handles keys created with either API version
98+
- ✅ No changes required to existing code
99+
- ✅ Same behavior across versions
100+
101+
## Testing
102+
103+
The implementation includes comprehensive tests in `test/2.param.lua`:
104+
105+
```bash
106+
# Run param tests
107+
cd test
108+
lua 2.param.lua
109+
```
110+
111+
## Future Enhancements
112+
113+
The current implementation focuses on RSA parameters. Future updates may include:
114+
- EC (Elliptic Curve) key parameters
115+
- DH (Diffie-Hellman) parameters
116+
- DSA parameters
117+
- General-purpose OSSL_PARAM array creation from Lua
118+
119+
## References
120+
121+
- [OpenSSL 3.0 Migration Guide](https://www.openssl.org/docs/man3.0/man7/migration_guide.html)
122+
- [OSSL_PARAM Documentation](https://www.openssl.org/docs/man3.0/man3/OSSL_PARAM.html)
123+
- [lua-openssl ROADMAP](ROADMAP.md)
124+
- [Task 2.5 Details](ROADMAP_CN.md#25-openssl-30-ossl_param-api-绑定)
125+
126+
## Contributing
127+
128+
To extend OSSL_PARAM support to other key types:
129+
130+
1. Add parameter definitions to `src/param.c` (similar to `rsa_params[]`)
131+
2. Update `get_param_type()` to recognize the new parameters
132+
3. Export parameters in `luaopen_param()`
133+
4. Implement parameter access in the relevant module (e.g., `src/ec.c` for EC keys)
134+
5. Add tests to verify functionality
135+
136+
See the RSA implementation as a reference example.

src/param.c

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,42 @@ static struct param_info kdf_params[] = {
8686
{ NULL, 0, 0 }
8787
};
8888

89+
/* RSA key parameters */
90+
static struct param_info rsa_params[] = {
91+
{ OSSL_PKEY_PARAM_RSA_N, OSSL_PARAM_UNSIGNED_INTEGER, PARAM_T_BN },
92+
{ OSSL_PKEY_PARAM_RSA_E, OSSL_PARAM_UNSIGNED_INTEGER, PARAM_T_BN },
93+
{ OSSL_PKEY_PARAM_RSA_D, OSSL_PARAM_UNSIGNED_INTEGER, PARAM_T_BN },
94+
{ OSSL_PKEY_PARAM_RSA_FACTOR1, OSSL_PARAM_UNSIGNED_INTEGER, PARAM_T_BN },
95+
{ OSSL_PKEY_PARAM_RSA_FACTOR2, OSSL_PARAM_UNSIGNED_INTEGER, PARAM_T_BN },
96+
{ OSSL_PKEY_PARAM_RSA_EXPONENT1, OSSL_PARAM_UNSIGNED_INTEGER, PARAM_T_BN },
97+
{ OSSL_PKEY_PARAM_RSA_EXPONENT2, OSSL_PARAM_UNSIGNED_INTEGER, PARAM_T_BN },
98+
{ OSSL_PKEY_PARAM_RSA_COEFFICIENT1, OSSL_PARAM_UNSIGNED_INTEGER, PARAM_T_BN },
99+
{ NULL, 0, 0 }
100+
};
101+
89102
static int
90103
get_param_type(const char *name, PARAM_NUMBER_TYPE *nt)
91104
{
92105
int i;
93106

107+
/* Try KDF parameters first */
94108
for (i = 0; i < sizeof(kdf_params) / sizeof(kdf_params[0]); i++) {
95109
struct param_info *p = &kdf_params[i];
96110
if (p->name && strcmp(p->name, name) == 0) {
97111
*nt = p->number_type;
98112
return p->data_type;
99113
}
100114
}
115+
116+
/* Try RSA parameters */
117+
for (i = 0; i < sizeof(rsa_params) / sizeof(rsa_params[0]); i++) {
118+
struct param_info *p = &rsa_params[i];
119+
if (p->name && strcmp(p->name, name) == 0) {
120+
*nt = p->number_type;
121+
return p->data_type;
122+
}
123+
}
124+
101125
return 0;
102126
}
103127

@@ -282,6 +306,7 @@ luaopen_param(lua_State *L)
282306

283307
lua_newtable(L);
284308

309+
/* Export KDF parameters */
285310
lua_pushliteral(L, "kdf");
286311
lua_newtable(L);
287312

@@ -343,6 +368,68 @@ luaopen_param(lua_State *L)
343368

344369
lua_rawset(L, -3);
345370

371+
/* Export RSA parameters */
372+
lua_pushliteral(L, "rsa");
373+
lua_newtable(L);
374+
375+
for (i = 0; i < sizeof(rsa_params) / sizeof(rsa_params[0]); i++) {
376+
struct param_info *p = &rsa_params[i];
377+
if (p->name) {
378+
lua_pushstring(L, p->name);
379+
lua_newtable(L);
380+
lua_pushliteral(L, "type");
381+
lua_pushinteger(L, p->data_type);
382+
lua_rawset(L, -3);
383+
if (p->number_type) {
384+
lua_pushliteral(L, "number_type");
385+
switch ((int)p->number_type) {
386+
case PARAM_T_INT:
387+
lua_pushliteral(L, "int");
388+
break;
389+
case PARAM_T_UINT:
390+
lua_pushliteral(L, "unsinged int");
391+
break;
392+
case PARAM_T_LONG:
393+
lua_pushliteral(L, "long");
394+
break;
395+
case PARAM_T_ULONG:
396+
lua_pushliteral(L, "unsinged long");
397+
break;
398+
case PARAM_T_INT32:
399+
lua_pushliteral(L, "int32");
400+
break;
401+
case PARAM_T_UINT32:
402+
lua_pushliteral(L, "uint32");
403+
break;
404+
case PARAM_T_INT64:
405+
lua_pushliteral(L, "int64");
406+
break;
407+
case PARAM_T_UINT64:
408+
lua_pushliteral(L, "uint64");
409+
break;
410+
case PARAM_T_SIZE_T:
411+
lua_pushliteral(L, "size_t");
412+
break;
413+
case PARAM_T_TIME_T:
414+
lua_pushliteral(L, "time_t");
415+
break;
416+
case PARAM_T_BN:
417+
lua_pushliteral(L, "BIGNUM");
418+
break;
419+
case PARAM_T_DOUBLE:
420+
lua_pushliteral(L, "double");
421+
break;
422+
default:
423+
lua_pushliteral(L, "unknown");
424+
}
425+
lua_rawset(L, -3);
426+
}
427+
lua_rawset(L, -3);
428+
}
429+
}
430+
431+
lua_rawset(L, -3);
432+
346433
return 1;
347434
}
348435

src/rsa.c

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ RSA key generation, encryption, decryption, signing and signature verification.
2222
#include "openssl.h"
2323
#include "private.h"
2424

25+
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
26+
#include <openssl/core_names.h>
27+
#include <openssl/params.h>
28+
#endif
29+
2530
#if !defined(OPENSSL_NO_RSA)
2631
static int openssl_rsa_free(lua_State *L)
2732
{
@@ -174,28 +179,90 @@ parse RSA key components and parameters
174179
*/
175180
static int openssl_rsa_parse(lua_State *L)
176181
{
177-
const BIGNUM *n = NULL, *e = NULL, *d = NULL;
178-
const BIGNUM *p = NULL, *q = NULL;
179-
const BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
180-
181182
RSA *rsa = CHECK_OBJECT(1, RSA, "openssl.rsa");
182-
RSA_get0_key(rsa, &n, &e, &d);
183-
RSA_get0_factors(rsa, &p, &q);
184-
RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
185-
183+
186184
lua_newtable(L);
187-
lua_pushinteger(L, RSA_size(rsa));
188-
lua_setfield(L, -2, "size");
189-
lua_pushinteger(L, RSA_bits(rsa));
190-
lua_setfield(L, -2, "bits");
191-
OPENSSL_PKEY_GET_BN(n, n);
192-
OPENSSL_PKEY_GET_BN(e, e);
193-
OPENSSL_PKEY_GET_BN(d, d);
194-
OPENSSL_PKEY_GET_BN(p, p);
195-
OPENSSL_PKEY_GET_BN(q, q);
196-
OPENSSL_PKEY_GET_BN(dmp1, dmp1);
197-
OPENSSL_PKEY_GET_BN(dmq1, dmq1);
198-
OPENSSL_PKEY_GET_BN(iqmp, iqmp);
185+
186+
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
187+
/* Try OpenSSL 3.0+ PARAM API first for keys created with new API */
188+
EVP_PKEY *pkey = EVP_PKEY_new();
189+
if (pkey && EVP_PKEY_set1_RSA(pkey, rsa)) {
190+
BIGNUM *n = NULL, *e = NULL, *d = NULL;
191+
BIGNUM *p = NULL, *q = NULL;
192+
BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
193+
int use_legacy = 0;
194+
195+
/* Try to get parameters using PARAM API */
196+
if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n)) {
197+
use_legacy = 1;
198+
} else {
199+
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e);
200+
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_D, &d);
201+
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, &p);
202+
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, &q);
203+
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dmp1);
204+
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dmq1);
205+
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &iqmp);
206+
207+
lua_pushinteger(L, RSA_size(rsa));
208+
lua_setfield(L, -2, "size");
209+
lua_pushinteger(L, RSA_bits(rsa));
210+
lua_setfield(L, -2, "bits");
211+
212+
OPENSSL_PKEY_GET_BN(n, n);
213+
OPENSSL_PKEY_GET_BN(e, e);
214+
OPENSSL_PKEY_GET_BN(d, d);
215+
OPENSSL_PKEY_GET_BN(p, p);
216+
OPENSSL_PKEY_GET_BN(q, q);
217+
OPENSSL_PKEY_GET_BN(dmp1, dmp1);
218+
OPENSSL_PKEY_GET_BN(dmq1, dmq1);
219+
OPENSSL_PKEY_GET_BN(iqmp, iqmp);
220+
221+
/* Clean up allocated BIGNUMs */
222+
BN_free(n);
223+
BN_free(e);
224+
BN_free(d);
225+
BN_free(p);
226+
BN_free(q);
227+
BN_free(dmp1);
228+
BN_free(dmq1);
229+
BN_free(iqmp);
230+
}
231+
232+
EVP_PKEY_free(pkey);
233+
234+
if (!use_legacy) {
235+
return 1;
236+
}
237+
}
238+
239+
/* Fallback to legacy API if PARAM API fails or EVP_PKEY creation fails */
240+
#endif
241+
242+
/* Legacy OpenSSL 1.x / 3.x compatibility path */
243+
{
244+
const BIGNUM *n = NULL, *e = NULL, *d = NULL;
245+
const BIGNUM *p = NULL, *q = NULL;
246+
const BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
247+
248+
RSA_get0_key(rsa, &n, &e, &d);
249+
RSA_get0_factors(rsa, &p, &q);
250+
RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
251+
252+
lua_pushinteger(L, RSA_size(rsa));
253+
lua_setfield(L, -2, "size");
254+
lua_pushinteger(L, RSA_bits(rsa));
255+
lua_setfield(L, -2, "bits");
256+
OPENSSL_PKEY_GET_BN(n, n);
257+
OPENSSL_PKEY_GET_BN(e, e);
258+
OPENSSL_PKEY_GET_BN(d, d);
259+
OPENSSL_PKEY_GET_BN(p, p);
260+
OPENSSL_PKEY_GET_BN(q, q);
261+
OPENSSL_PKEY_GET_BN(dmp1, dmp1);
262+
OPENSSL_PKEY_GET_BN(dmq1, dmq1);
263+
OPENSSL_PKEY_GET_BN(iqmp, iqmp);
264+
}
265+
199266
return 1;
200267
}
201268

0 commit comments

Comments
 (0)