Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ vectors). The runner invokes `require("crypto.<module>").selftest()` per module:
make test-matrix # across Lua 5.1–5.4 + LuaJIT
```

`crypto.selftest()` fails any module whose selftest leaves the acceleration
flag changed: a driver that enabled acceleration and then ran it would be left
unaccelerated, which on Control4 makes SRP unusable. A selftest that toggles the
flag restores what `openssl_wrapper.enabled()` returned, not the
`CRYPTO_USE_OPENSSL` default. The runner's lpack pass flips the flag, so every
run covers both settings.

## Building

`amalg` produces two single-file distributions from `src/crypto/init.lua`:
Expand Down
7 changes: 5 additions & 2 deletions run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,13 @@ run_selftest "Ed25519" "ed25519" "crypto.ed25519"
run_selftest "OpenSSL gating" "openssl_wrapper" "crypto.openssl_wrapper"

# Control4's LuaJIT has string.pack/unpack as lpack, a different dialect: every
# selftest again with that shape installed before bitn loads.
# selftest again with that shape installed before bitn loads, and with the
# acceleration flag flipped so every run covers both settings.
run_test "All selftests with lpack-shaped string.pack" "lpack" "
dofile('$script_dir/test/lpack_stub.lua')
if not require('crypto').selftest() then
local crypto = require('crypto')
crypto.use_openssl(not crypto.openssl_wrapper.enabled())
if not crypto.selftest() then
os.exit(1)
end
"
Expand Down
21 changes: 19 additions & 2 deletions src/crypto/bignum.lua
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ end
--- @param b BigNum Subtrahend (must not exceed a)
--- @return BigNum difference
local function sub_raw(a, b)
-- The loop reads only #a limbs of b, and with canonical operands a wider b is a larger b.
if #b > #a then
error("bignum: subtraction would be negative")
end
local r = {}
local borrow = 0
local na = #a
Expand Down Expand Up @@ -1204,6 +1208,7 @@ function bignum.selftest()
-- Snapshot the OpenSSL state so the accelerator tests cannot leak.
local saved_loaded = package.loaded["openssl"]
local saved_preload = package.preload["openssl"]
local saved_use = openssl_wrapper.enabled()

--- Build a stand-in lua-openssl binding. Real lua-openssl is not installed
--- here, so this validates the *routing* and the bytes-in/hex-out conversion,
Expand Down Expand Up @@ -1437,7 +1442,19 @@ function bignum.selftest()
{
name = "sub - rejects a negative result",
test = function()
-- Equal widths too: only the final borrow can catch those.
return pcall(bignum.sub, Y, X) == false
and pcall(bignum.sub, from_number(5), from_number(7)) == false
and pcall(bignum.sub, Y, bignum.add(Y, bignum.one())) == false
end,
},
{
name = "sub - rejects a wider subtrahend whose low limbs do not borrow",
test = function()
return pcall(bignum.sub, from_number(5), from_hex("1000000")) == false
and pcall(bignum.sub, from_hex("ffffff"), from_hex("1000000")) == false
and pcall(bignum.sub, X, bignum.add(X, from_hex("1" .. string_rep("0", 66)))) == false
and to_hex(bignum.sub(from_hex("1000000"), from_hex("ffffff"))) == "1"
end,
},
{
Expand Down Expand Up @@ -1756,10 +1773,10 @@ function bignum.selftest()
end
end

-- Restore the module-load default so later tests in this process are unaffected.
-- Restore the caller's state, acceleration choice included, so later tests in this process are unaffected.
package.loaded["openssl"] = saved_loaded
package.preload["openssl"] = saved_preload
openssl_wrapper.use(os.getenv("CRYPTO_USE_OPENSSL") == "1" or os.getenv("CRYPTO_USE_OPENSSL") == "true")
openssl_wrapper.use(saved_use)

print(string_format("\nBignum result: %d/%d tests passed\n", passed, #tests))
return passed == #tests
Expand Down
6 changes: 6 additions & 0 deletions src/crypto/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,15 @@ function crypto.selftest()
for _, name in ipairs(modules) do
local mod = crypto[name]
if type(mod.selftest) == "function" then
local accelerated = openssl_wrapper.enabled()
if mod.selftest() == false then
ok = false
end
-- A host that enabled acceleration and then ran this must still have it.
if openssl_wrapper.enabled() ~= accelerated then
print("crypto." .. name .. ".selftest() did not restore the OpenSSL acceleration flag")
ok = false
end
end
end
return ok
Expand Down
17 changes: 17 additions & 0 deletions src/crypto/openssl_wrapper.lua
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ function openssl_wrapper.use(use)
_openssl_unavailable = false
end

--- Whether acceleration is requested, as last set by `use`. Snapshot it before toggling the flag temporarily.
--- @return boolean enabled
function openssl_wrapper.enabled()
-- Truthy like `get`, not `== true`, or restoring this answer could switch acceleration off.
return _use_openssl and true or false
end

--- Parse semantic version string into comparable components
--- @param version_str string Version string like "0.9.1" or "1.0.0-rc1"
--- @return number major Major version number
Expand Down Expand Up @@ -493,6 +500,16 @@ function openssl_wrapper.selftest()
return openssl_wrapper.get(OpenSSLFeature.AAD) == nil
end,
},
{
name = "enabled reports what use last set",
test = function()
openssl_wrapper.use(true)
local on = openssl_wrapper.enabled()
openssl_wrapper.use(false)
local off = openssl_wrapper.enabled()
return on == true and off == false
end,
},
{
name = "absent binding falls back gracefully",
test = function()
Expand Down
5 changes: 3 additions & 2 deletions src/crypto/random.lua
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ function random.selftest()
local saved_path = random._urandom_path
local saved_preload = package.preload["openssl"]
local saved_loaded = package.loaded["openssl"]
local saved_use = openssl_wrapper.enabled()

--- Force both built-in sources to be unavailable.
local function starve()
Expand All @@ -233,13 +234,13 @@ function random.selftest()
random._urandom_path = "/nonexistent/crypto-random-selftest"
end

--- Restore real detection, including the wrapper's initial opt-in state, so
--- Restore real detection, including the caller's acceleration choice, so
--- running this inside `crypto.selftest()` cannot disturb a later module.
local function unstarve()
random.reset()
package.loaded["openssl"] = saved_loaded
package.preload["openssl"] = saved_preload
openssl_wrapper.use(os.getenv("CRYPTO_USE_OPENSSL") == "1" or os.getenv("CRYPTO_USE_OPENSSL") == "true")
openssl_wrapper.use(saved_use)
random._urandom_path = saved_path
end

Expand Down
Loading