Skip to content

Commit

Permalink
feat(pkey) support pass in ctrl str options
Browse files Browse the repository at this point in the history
  • Loading branch information
fffonion committed Dec 15, 2023
1 parent 12f5209 commit 852e755
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 82 deletions.
114 changes: 88 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ A module to provide error messages.
### err.format_error

**syntax**: *msg = err.format_error(ctx_msg?, return_code?, all_errors?)*

**syntax**: *msg = err.format_all_errors(ctx_msg?, return_code?)*

Return the latest error message from the last error code. Errors are formatted as:
Expand Down Expand Up @@ -905,6 +906,26 @@ local key, err = pkey.new({
})
```

It's also possible to pass raw pkeyopt control strings in `config` table as used in the `genpkey` CLI program.
See [openssl-genpkey(1)](https://www.openssl.org/docs/man3.0/man1/openssl-genpkey.html) for a list of options.

For example:

```lua
pkey.new({
type = 'RSA',
bits = 2048,
exp = 65537,
})
-- is same as
pkey.new({
type = 'RSA',
exp = 65537,
"rsa_keygen_bits:4096",
})

```


[Back to TOC](#table-of-contents)

Expand Down Expand Up @@ -956,6 +977,9 @@ local pem, err = pkey.paramgen({
})
```

It's also possible to pass raw pkeyopt control strings in `config` table as used in the `genpkey` CLI program.
See [openssl-genpkey(1)](https://www.openssl.org/docs/man3.0/man1/openssl-genpkey.html) for a list of options.

[Back to TOC](#table-of-contents)

### pkey:get_provider_name
Expand Down Expand Up @@ -1110,10 +1134,20 @@ to use when signing. When `md_alg` is undefined, for RSA and EC keys, this funct
by default. For Ed25519 or Ed448 keys, this function does a PureEdDSA signing,
no message digest should be specified and will not be used.

`opts` is a table that accepts additional parameters.
For RSA key, it's also possible to specify `padding` scheme with following choices:

For RSA key, it's also possible to specify `padding` scheme. The choice of values are same
as those in [pkey:encrypt](#pkeyencrypt). When `padding` is `RSA_PKCS1_PSS_PADDING`, it's
```lua
pkey.PADDINGS = {
RSA_PKCS1_PADDING = 1,
RSA_SSLV23_PADDING = 2,
RSA_NO_PADDING = 3,
RSA_PKCS1_OAEP_PADDING = 4,
RSA_X931_PADDING = 5, -- sign only
RSA_PKCS1_PSS_PADDING = 6, -- sign and verify only
}
```

When `padding` is `RSA_PKCS1_PSS_PADDING`, it's
possible to specify PSS salt length by setting `opts.pss_saltlen`.

For EC key, this function does a ECDSA signing.
Expand All @@ -1125,6 +1159,32 @@ is encoded in ASN.1 DER format. If the `opts` table contains a `ecdsa_use_raw` f
a true value, a binary with just the concatenation of binary representation `pr` and `ps` is returned.
This is useful for example to send the signature as JWS.

`opts` is a table that accepts additional parameters with following choices:

```
{
pss_saltlen, -- For PSS mode only this option specifies the salt length.
mgf1_md, -- For PSS and OAEP padding sets the MGF1 digest. If the MGF1 digest is not explicitly set in PSS mode then the signing digest is used.
oaep_md, -- The digest used for the OAEP hash function. If not explicitly set then SHA1 is used.
}
```

It's also possible to pass raw pkeyopt control strings as used in the `pkeyutl` CLI program. This lets user pass in options that
are not explictly supported as parameters above.
See [openssl-pkeyutl(1)](https://www.openssl.org/docs/manmaster/man1/openssl-pkeyutl.html) for a list of options.

```lua
pk:sign(message, nil, pk.PADDINGS.RSA_PKCS1_OAEP_PADDING, {
oaep_md = "sha256",
})
-- is same as
pk:sign(message, nil, nil, {
"rsa_padding_mode:oaep",
"rsa_oaep_md:sha256",
})
-- in pkeyutl CLI the above is equivalent to: `openssl pkeyutl -sign -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256
```

[Back to TOC](#table-of-contents)

### pkey:verify
Expand All @@ -1147,17 +1207,19 @@ to use when verifying. When `md_alg` is undefined, for RSA and EC keys, this fun
by default. For Ed25519 or Ed448 keys, this function does a PureEdDSA verification,
no message digest should be specified and will not be used.

`opts` is a table that accepts additional parameters.

For RSA key, it's also possible to specify `padding` scheme. The choice of values are same
as those in [pkey:encrypt](#pkeyencrypt). When `padding` is `RSA_PKCS1_PSS_PADDING`, it's
When key is a RSA key, the function accepts an optional argument `padding` which choices
of values are same as those in [pkey:sign](#pkeysign). When `padding` is `RSA_PKCS1_PSS_PADDING`, it's
possible to specify PSS salt length by setting `opts.pss_saltlen`.

For EC key, this function does a ECDSA verification. Normally, the ECDSA signature
should be encoded in ASN.1 DER format. If the `opts` table contains a `ecdsa_use_raw` field with
a true value, this library treat `signature` as concatenation of binary representation `pr` and `ps`.
This is useful for example to verify the signature as JWS.

`opts` is a table that accepts additional parameters which choices
of values are same as those in [pkey:sign](#pkeysign).


```lua
-- RSA and EC keys
local pk, err = require("resty.openssl.pkey").new()
Expand Down Expand Up @@ -1193,34 +1255,28 @@ ngx.say(ngx.encode_base64(signature))

### pkey:encrypt

**syntax**: *cipher_txt, err = pk:encrypt(txt, padding?)*
**syntax**: *cipher_txt, err = pk:encrypt(txt, padding?, opts?)*

Encrypts plain text `txt` with `pkey` instance, which must loaded a public key.

When key is a RSA key, the function accepts an optional second argument `padding` which can be:
The optional second argument `padding` has same meaning as in [pkey:sign](#pkeysign).
If omitted, `padding` is default to `pkey.PADDINGS.RSA_PKCS1_PADDING`.

```lua
pkey.PADDINGS = {
RSA_PKCS1_PADDING = 1,
RSA_SSLV23_PADDING = 2,
RSA_NO_PADDING = 3,
RSA_PKCS1_OAEP_PADDING = 4,
RSA_X931_PADDING = 5,
RSA_PKCS1_PSS_PADDING = 6,
}
```
The third optional argument `opts` has same meaning as in [pkey:sign](#pkeysign).

If omitted, `padding` is default to `pkey.PADDINGS.RSA_PKCS1_PADDING`.

[Back to TOC](#table-of-contents)

### pkey:decrypt

**syntax**: *txt, err = pk:decrypt(cipher_txt, padding?)*
**syntax**: *txt, err = pk:decrypt(cipher_txt, padding?, opts?)*

Decrypts cipher text `cipher_txt` with pkey instance, which must loaded a private key.

The optional second argument `padding` has same meaning in [pkey:encrypt](#pkeyencrypt).
The optional second argument `padding` has same meaning as in [pkey:sign](#pkeysign).
If omitted, `padding` is default to `pkey.PADDINGS.RSA_PKCS1_PADDING`.

The third optional argument `opts` has same meaning as in [pkey:sign](#pkeysign).

```lua
local pkey = require("resty.openssl.pkey")
Expand All @@ -1239,11 +1295,14 @@ ngx.say(decrypted)

### pkey:sign_raw

**syntax**: *signature, err = pk:sign_raw(txt, padding?)*
**syntax**: *signature, err = pk:sign_raw(txt, padding?, opts?)*

Signs the cipher text `cipher_txt` with pkey instance, which must loaded a private key.

The optional second argument `padding` has same meaning in [pkey:encrypt](#pkeyencrypt).
The optional second argument `padding` has same meaning as in [pkey:sign](#pkeysign).
If omitted, `padding` is default to `pkey.PADDINGS.RSA_PKCS1_PADDING`.

The third optional argument `opts` has same meaning as in [pkey:sign](#pkeysign).

This function may also be called "private encrypt" in some implementations like NodeJS or PHP.
Do note as the function names suggested, this function is not secure to be regarded as an encryption.
Expand All @@ -1257,12 +1316,15 @@ for an example.

### pkey:verify_recover

**syntax**: *txt, err = pk:verify_recover(signature, padding?)*
**syntax**: *txt, err = pk:verify_recover(signature, padding?, opts?)*

Verify the cipher text `signature` with pkey instance, which must loaded a public key, and also
returns the original text being signed. This operation is only supported by RSA key.

The optional second argument `padding` has same meaning in [pkey:encrypt](#pkeyencrypt).
The optional second argument `padding` has same meaning as in [pkey:sign](#pkeysign).
If omitted, `padding` is default to `pkey.PADDINGS.RSA_PKCS1_PADDING`.

The third optional argument `opts` has same meaning as in [pkey:sign](#pkeysign).

This function may also be called "public decrypt" in some implementations like NodeJS or PHP.

Expand Down
33 changes: 23 additions & 10 deletions lib/resty/openssl/include/evp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,23 @@ local _M = {
EVP_PKEY_X448 = C.OBJ_txt2nid("X448"),
EVP_PKEY_ED448 = C.OBJ_txt2nid("ED448"),

EVP_PKEY_OP_PARAMGEN = bit.lshift(1, 1),
EVP_PKEY_OP_KEYGEN = bit.lshift(1, 2),
EVP_PKEY_OP_SIGN = bit.lshift(1, 3),
EVP_PKEY_OP_VERIFY = bit.lshift(1, 4),
EVP_PKEY_OP_DERIVE = OPENSSL_3X and bit.lshift(1, 12) or bit.lshift(1, 10),
EVP_CTRL_AEAD_SET_IVLEN = 0x9,
EVP_CTRL_AEAD_GET_TAG = 0x10,
EVP_CTRL_AEAD_SET_TAG = 0x11,

EVP_PKEY_ALG_CTRL = EVP_PKEY_ALG_CTRL,
-- remove EVP_PKEY_OP_* and EVP_PKEY_CTRL_* after openssl 1.1.1 support is dropped
EVP_PKEY_OP_PARAMGEN = not OPENSSL_3X and bit.lshift(1, 1) or nil,
EVP_PKEY_OP_KEYGEN = not OPENSSL_3X and bit.lshift(1, 2) or nil,
EVP_PKEY_OP_SIGN = not OPENSSL_3X and bit.lshift(1, 3) or nil,
EVP_PKEY_OP_VERIFY = not OPENSSL_3X and bit.lshift(1, 4) or nil,
EVP_PKEY_OP_VERIFYRECOVER = not OPENSSL_3X and bit.lshift(1, 5) or nil,
EVP_PKEY_OP_SIGNCTX = not OPENSSL_3X and bit.lshift(1, 6) or nil,
EVP_PKEY_OP_VERIFYCTX = not OPENSSL_3X and bit.lshift(1, 7) or nil,
EVP_PKEY_OP_ENCRYPT = not OPENSSL_3X and bit.lshift(1, 8) or nil,
EVP_PKEY_OP_DECRYPT = not OPENSSL_3X and bit.lshift(1, 9) or nil,
EVP_PKEY_OP_DERIVE = not OPENSSL_3X and bit.lshift(1, 10) or nil,

EVP_PKEY_ALG_CTRL = EVP_PKEY_ALG_CTRL,

EVP_PKEY_CTRL_DH_PARAMGEN_PRIME_LEN = EVP_PKEY_ALG_CTRL + 1,
EVP_PKEY_CTRL_EC_PARAMGEN_CURVE_NID = EVP_PKEY_ALG_CTRL + 1,
Expand All @@ -60,10 +69,8 @@ local _M = {
EVP_PKEY_CTRL_RSA_KEYGEN_PUBEXP = EVP_PKEY_ALG_CTRL + 4,
EVP_PKEY_CTRL_RSA_PADDING = EVP_PKEY_ALG_CTRL + 1,
EVP_PKEY_CTRL_RSA_PSS_SALTLEN = EVP_PKEY_ALG_CTRL + 2,

EVP_CTRL_AEAD_SET_IVLEN = 0x9,
EVP_CTRL_AEAD_GET_TAG = 0x10,
EVP_CTRL_AEAD_SET_TAG = 0x11,
EVP_PKEY_CTRL_RSA_MGF1_MD = EVP_PKEY_ALG_CTRL + 5,
EVP_PKEY_CTRL_RSA_OAEP_MD = EVP_PKEY_ALG_CTRL + 9,

EVP_PKEY_CTRL_TLS_MD = EVP_PKEY_ALG_CTRL,
EVP_PKEY_CTRL_TLS_SECRET = EVP_PKEY_ALG_CTRL + 1,
Expand All @@ -81,6 +88,12 @@ local _M = {
EVP_PKEY_CTRL_SCRYPT_MAXMEM_BYTES = EVP_PKEY_ALG_CTRL + 13,
}

if not OPENSSL_3X then
_M.EVP_PKEY_OP_CRYPT = _M.EVP_PKEY_OP_ENCRYPT + _M.EVP_PKEY_OP_DECRYPT
_M.EVP_PKEY_OP_SIG = _M.EVP_PKEY_OP_SIGN + _M.EVP_PKEY_OP_VERIFY + _M.EVP_PKEY_OP_VERIFYRECOVER +
_M.EVP_PKEY_OP_SIGNCTX + _M.EVP_PKEY_OP_VERIFYCTX
end

-- clean up error occurs during OBJ_txt2*
C.ERR_clear_error()

Expand Down
49 changes: 43 additions & 6 deletions lib/resty/openssl/include/evp/pkey.lua
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ ffi.cdef [[
int EVP_PKEY_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey);
int EVP_PKEY_paramgen_init(EVP_PKEY_CTX *ctx);
int EVP_PKEY_paramgen(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey);

int EVP_PKEY_CTX_ctrl_str(EVP_PKEY_CTX *ctx, const char *type,
const char *value);
]]

local _M = {}
Expand All @@ -102,6 +105,10 @@ if OPENSSL_3X then

int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *ctx, int pad);
int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX *ctx, int len);
int EVP_PKEY_CTX_set_rsa_mgf1_md_name(EVP_PKEY_CTX *ctx, const char *mdname,
const char *mdprops);
int EVP_PKEY_CTX_set_rsa_oaep_md_name(EVP_PKEY_CTX *ctx, const char *mdname,
const char *mdprops);

int EVP_PKEY_CTX_set_dh_paramgen_prime_len(EVP_PKEY_CTX *ctx, int pbits);

Expand All @@ -121,6 +128,10 @@ if OPENSSL_3X then
return C.EVP_PKEY_CTX_set_ec_param_enc(pctx, param_enc)
end

_M.EVP_PKEY_CTX_set_dh_paramgen_prime_len = function(pctx, pbits)
return C.EVP_PKEY_CTX_set_dh_paramgen_prime_len(pctx, pbits)
end

_M.EVP_PKEY_CTX_set_rsa_keygen_bits = function(pctx, mbits)
return C.EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, mbits)
end
Expand All @@ -134,8 +145,11 @@ if OPENSSL_3X then
_M.EVP_PKEY_CTX_set_rsa_pss_saltlen = function(pctx, len)
return C.EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, len)
end
_M.EVP_PKEY_CTX_set_dh_paramgen_prime_len = function(pctx, pbits)
return C.EVP_PKEY_CTX_set_dh_paramgen_prime_len(pctx, pbits)
_M.EVP_PKEY_CTX_set_rsa_mgf1_md_name = function(pctx, name ,props)
return C.EVP_PKEY_CTX_set_rsa_mgf1_md_name(pctx, name, props)
end
_M.EVP_PKEY_CTX_set_rsa_oaep_md_name = function(pctx, name, props)
return C.EVP_PKEY_CTX_set_rsa_oaep_md_name(pctx, name, props)
end

else
Expand All @@ -154,6 +168,13 @@ else
param_enc, nil)
end

_M.EVP_PKEY_CTX_set_dh_paramgen_prime_len = function(pctx, pbits)
return C.EVP_PKEY_CTX_ctrl(pctx,
evp.EVP_PKEY_DH, evp.EVP_PKEY_OP_PARAMGEN,
evp.EVP_PKEY_CTRL_DH_PARAMGEN_PRIME_LEN,
pbits, nil)
end

_M.EVP_PKEY_CTX_set_rsa_keygen_bits = function(pctx, mbits)
return C.EVP_PKEY_CTX_ctrl(pctx,
evp.EVP_PKEY_RSA,
Expand Down Expand Up @@ -183,11 +204,27 @@ else
len, nil)
end

_M.EVP_PKEY_CTX_set_dh_paramgen_prime_len = function(pctx, pbits)
_M.EVP_PKEY_CTX_set_rsa_mgf1_md_name = function(pctx, name, _)
local md = C.EVP_get_digestbyname(name)
if not md then
return -1, "unknown digest: " .. name
end
return C.EVP_PKEY_CTX_ctrl(pctx,
evp.EVP_PKEY_DH, evp.EVP_PKEY_OP_PARAMGEN,
evp.EVP_PKEY_CTRL_DH_PARAMGEN_PRIME_LEN,
pbits, nil)
evp.EVP_PKEY_RSA,
evp.EVP_PKEY_OP_SIG + evp.EVP_PKEY_OP_TYPE_CRYPT,
evp.EVP_PKEY_CTRL_RSA_MGF1_MD,
0, ffi.cast("void *", md))
end
_M.EVP_PKEY_CTX_set_rsa_oaep_md_name = function(pctx, name, _)
local md = C.EVP_get_digestbyname(name)
if not md then
return -1, "unknown digest: " .. name
end
return C.EVP_PKEY_CTX_ctrl(pctx,
evp.EVP_PKEY_RSA,
evp.EVP_PKEY_OP_CRYPT,
evp.EVP_PKEY_CTRL_RSA_OAEP_MD,
0, ffi.cast("void *", md))
end
end

Expand Down
Loading

0 comments on commit 852e755

Please sign in to comment.