Skip to content

Commit

Permalink
feat(bn) add from_mpi, to_mpi and set API
Browse files Browse the repository at this point in the history
  • Loading branch information
fffonion committed Dec 20, 2023
1 parent 253d11c commit 073c943
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 268 deletions.
57 changes: 52 additions & 5 deletions README.md
Expand Up @@ -74,7 +74,9 @@ Table of Contents
+ [bn.new](#bnnew)
+ [bn.dup](#bndup)
+ [bn.istype](#bnistype)
+ [bn:set](#bnset)
+ [bn.from_binary, bn:to_binary](#bnfrom_binary-bnto_binary)
+ [bn.from_mpi, bn:to_mpi](#bnfrom_mpi-bnto_mpi)
+ [bn.from_hex, bn:to_hex](#bnfrom_hex-bnto_hex)
+ [bn.from_dec, bn:to_dec](#bnfrom_dec-bnto_dec)
+ [bn:to_number](#bnto_number)
Expand Down Expand Up @@ -1380,8 +1382,20 @@ Module to expose BIGNUM structure. Note bignum is a big integer, no float operat

**syntax**: *b, err = bn.new(number?)*

Creates a `bn` instance. The first argument can be a Lua number or `nil` to
creates an empty instance.
**syntax**: *b, err = bn.new(string?, base?)*

Creates a `bn` instance. The first argument can be:

- `nil` to creates an empty bn instance.
- A Lua number to initialize the bn instance.
- A string to initialize the bn instance. The second argument `base` specifies the base of the string,
and can take value from (compatible with Ruby OpenSSL.BN API):
- `10` or omitted, for decimal string (`"23333"`)
- `16`, for hex encoded string (`"5b25"`)
- `2`, for binary string (`"\x5b\x25"`)
- `0`, for MPI formated string (`"\x00\x00\x00\x02\x5b\x25"`)

MPI is a format that consists of the number's length in bytes represented as a 4-byte big-endian number, and the number itself in big-endian format, where the most significant bit signals a negative number (the representation of numbers with the MSB set is prefixed with null byte).

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

Expand All @@ -1401,6 +1415,17 @@ Returns `true` if table is an instance of `bn`. Returns `false` otherwise.

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

### bn.set

**syntax**: *b, err = bn:set(number)*

**syntax**: *b, err = bn:set(string, base?)*

Reuse the existing bn instance and reset its value with given number or string.
Refer to [bn.new](#bnnew) for the type of arguments supported.

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

### bn.from_binary, bn:to_binary

**syntax**: *bn, err = bn.from_binary(bin)*
Expand All @@ -1415,10 +1440,32 @@ Exports the BIGNUM value in binary string.
used to pad leading zeros to the output to a specific length.

```lua
local b, err = require("resty.openssl.bn").from_binary(ngx.decode_base64("WyU="))
local to_hex = require "resty.string".to_hex
local b, err = require("resty.openssl.bn").from_binary("\x5b\x25")
local bin, err = b:to_binary()
ngx.say(ngx.encode_base64(bin))
-- outputs "WyU="
ngx.say(to_hex(bin))
-- outputs "5b25
```

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

### bn.from_mpi, bn:to_mpi

**syntax**: *bn, err = bn.from_mpi(bin)*

**syntax**: *bin, err = bn:to_mpi()*

Creates a `bn` instance from MPI formatted binary string.

Exports the BIGNUM value in MPI formatted binary string.


```lua
local to_hex = require "resty.string".to_hex
local b, err = require("resty.openssl.bn").from_mpi("\x00\x00\x00\x02\x5b\x25")
local bin, err = b:to_mpi()
ngx.say(to_hex(bin))
-- outputs "000000025b25
```

[Back to TOC](#table-of-contents)
Expand Down
167 changes: 115 additions & 52 deletions lib/resty/openssl/bn.lua
Expand Up @@ -9,24 +9,80 @@ require "resty.openssl.include.bn"
local crypto_macro = require("resty.openssl.include.crypto")
local ctypes = require "resty.openssl.auxiliary.ctypes"
local format_error = require("resty.openssl.err").format_error
local OPENSSL_3X = require("resty.openssl.version").OPENSSL_3X

local _M = {}
local mt = {__index = _M}

local bn_ptr_ct = ffi.typeof('BIGNUM*')
local bn_ptrptr_ct = ffi.typeof('BIGNUM*[1]')

function _M.new(bn)
local function set_binary(ctx, s)
local ctx = C.BN_bin2bn(s, #s, ctx)
if ctx == nil then
return nil, format_error("set_binary")
end
return ctx
end

local function set_mpi(ctx, s)
local ctx = C.BN_mpi2bn(s, #s, ctx)
if ctx == nil then
return nil, format_error("set_mpi")
end
return ctx
end

local function set_hex(ctx, s)
local p = ffi_new(bn_ptrptr_ct)
p[0] = ctx

if C.BN_hex2bn(p, s) == 0 then
return nil, format_error("set_hex")
end
return p[0]
end

local function set_dec(ctx, s)
local p = ffi_new(bn_ptrptr_ct)
p[0] = ctx

if C.BN_dec2bn(p, s) == 0 then
return nil, format_error("set_dec")
end
return p[0]
end

local function set_bn(ctx, s, base)
if type(s) == 'number' then
if C.BN_set_word(ctx, s) ~= 1 then
return nil, format_error("set_bn")
end
elseif type(s) == 'string' then
if not base or base == 10 then
return set_dec(ctx, s)
elseif base == 16 then
return set_hex(ctx, s)
elseif base == 2 then
return set_binary(ctx, s)
elseif base == 0 then
ctx = set_mpi(ctx, s)
else
return nil, "set_bn: unsupported base: " .. base
end
elseif s then
return nil, "set_bn: expect nil, a number or a string at #1"
end

return ctx
end

function _M.new(some, base)
local ctx = C.BN_new()
ffi_gc(ctx, C.BN_free)

if type(bn) == 'number' then
if C.BN_set_word(ctx, bn) ~= 1 then
return nil, format_error("bn.new")
end
elseif bn then
return nil, "bn.new: expect nil or a number at #1"
local ctx, err = set_bn(ctx, some, base)
if err then
return nil, "bn.new: " .. err
end

return setmetatable( { ctx = ctx }, mt), nil
Expand All @@ -50,6 +106,19 @@ function _M.dup(ctx)
return self
end

function _M:set(some, base)
if not some then
return nil, "expect a number or a string at #1"
end

local _, err = set_bn(self.ctx, some, base)
if err then
return nil, "bn:set: " .. err
end

return self
end

function _M:to_binary(pad)
if pad then
if type(pad) ~= "number" then
Expand Down Expand Up @@ -80,17 +149,22 @@ function _M:to_binary(pad)
return ffi_str(buf, sz)
end

function _M.from_binary(s)
if type(s) ~= "string" then
return nil, "bn.from_binary: expect a string at #1"
function _M:to_mpi(no_header)
local length = C.BN_bn2mpi(self.ctx, nil)
if length <= 0 then
return nil, format_error("bn:to_mpi")
end

local ctx = C.BN_bin2bn(s, #s, nil)
if ctx == nil then
return nil, format_error("bn.from_binary")
local buf = ctypes.uchar_array(length)

local sz = C.BN_bn2mpi(self.ctx, buf)
if sz <= 0 then
return nil, format_error("bn:to_mpi")
end
ffi_gc(ctx, C.BN_free)
return setmetatable( { ctx = ctx }, mt), nil

local ret = ffi_str(buf, sz)

return no_header and ret:sub(4) or ret
end

function _M:to_hex()
Expand All @@ -103,21 +177,6 @@ function _M:to_hex()
return s
end

function _M.from_hex(s)
if type(s) ~= "string" then
return nil, "bn.from_hex: expect a string at #1"
end

local p = ffi_new(bn_ptrptr_ct)

if C.BN_hex2bn(p, s) == 0 then
return nil, format_error("bn.from_hex")
end
local ctx = p[0]
ffi_gc(ctx, C.BN_free)
return setmetatable( { ctx = ctx }, mt), nil
end

function _M:to_dec()
local buf = C.BN_bn2dec(self.ctx)
if buf == nil then
Expand All @@ -129,26 +188,35 @@ function _M:to_dec()
end
mt.__tostring = _M.to_dec

function _M.from_dec(s)
if type(s) ~= "string" then
return nil, "bn.from_dec: expect a string at #1"
end

local p = ffi_new(bn_ptrptr_ct)

if C.BN_dec2bn(p, s) == 0 then
return nil, format_error("bn.from_dec")
end
local ctx = p[0]
ffi_gc(ctx, C.BN_free)
return setmetatable( { ctx = ctx }, mt), nil
end

function _M:to_number()
return tonumber(C.BN_get_word(self.ctx))
end
_M.tonumber = _M.to_number

local from_funcs = {
binary = set_binary,
mpi = set_mpi,
hex = set_hex,
dec = set_dec,
}

for typ, func in pairs(from_funcs) do
local sig = "from_" .. typ
_M[sig] = function(s)
if type(s) ~= "string" then
return nil, "bn." .. sig .. ": expect a string at #1"
end

local ctx, err = func(nil, s)
if not ctx then
return nil, "bn." .. sig .. ": " .. err
end

ffi_gc(ctx, C.BN_free)
return setmetatable( { ctx = ctx }, mt), nil
end
end

function _M.generate_prime(bits, safe)
local ctx = C.BN_new()
ffi_gc(ctx, C.BN_free)
Expand Down Expand Up @@ -373,12 +441,7 @@ function _M:is_prime(nchecks)
end
-- if nchecks is not defined, set to BN_prime_checks:
-- select number of iterations based on the size of the number
local code
if OPENSSL_3X then
code = C.BN_check_prime(self.ctx, bn_ctx_tmp, nil)
else
code = C.BN_is_prime_ex(self.ctx, nchecks or 0, bn_ctx_tmp, nil)
end
local code = C.BN_is_prime_ex(self.ctx, nchecks or 0, bn_ctx_tmp, nil)
if code == -1 then
return nil, format_error("bn.is_prime")
end
Expand Down
13 changes: 4 additions & 9 deletions lib/resty/openssl/include/bn.lua
@@ -1,7 +1,6 @@
local ffi = require "ffi"

require "resty.openssl.include.ossl_typ"
local OPENSSL_3X = require("resty.openssl.version").OPENSSL_3X

local BN_ULONG
if ffi.abi('64bit') then
Expand Down Expand Up @@ -33,6 +32,9 @@ ffi.cdef(
char *BN_bn2hex(const BIGNUM *a);
char *BN_bn2dec(const BIGNUM *a);
int BN_bn2mpi(const BIGNUM *a, unsigned char *to);
BIGNUM *BN_mpi2bn(const unsigned char *s, int len, BIGNUM *ret);
void BN_set_negative(BIGNUM *a, int n);
int BN_is_negative(const BIGNUM *a);
Expand Down Expand Up @@ -60,7 +62,6 @@ ffi.cdef(
int BN_cmp(BIGNUM *a, BIGNUM *b);
int BN_ucmp(BIGNUM *a, BIGNUM *b);
// openssl >= 1.1 only
int BN_is_zero(BIGNUM *a);
int BN_is_one(BIGNUM *a);
int BN_is_word(BIGNUM *a, ]] .. BN_ULONG ..[[ w);
Expand All @@ -70,10 +71,4 @@ ffi.cdef(
int BN_generate_prime_ex(BIGNUM *ret,int bits,int safe, const BIGNUM *add,
const BIGNUM *rem, BN_GENCB *cb);
]]
)

if OPENSSL_3X then
ffi.cdef [[
int BN_check_prime(const BIGNUM *p, BN_CTX *ctx, BN_GENCB *cb);
]]
end
)

0 comments on commit 073c943

Please sign in to comment.