string, table, crypto and util library changes

This commit is contained in:
Sky Johnson 2025-05-05 14:14:36 -05:00
parent 861ab73d83
commit 9ea06eb1b4
7 changed files with 1658 additions and 27 deletions

404
core/runner/crypto.go Normal file
View File

@ -0,0 +1,404 @@
package runner
import (
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"encoding/hex"
"fmt"
"hash"
"math"
mrand "math/rand/v2"
"sync"
"time"
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
)
var (
// Map to store state-specific RNGs
stateRngs = make(map[*luajit.State]*mrand.PCG)
stateRngsMu sync.Mutex
)
// RegisterCryptoFunctions registers all crypto functions with the Lua state
func RegisterCryptoFunctions(state *luajit.State) error {
// Create a state-specific RNG
stateRngsMu.Lock()
stateRngs[state] = mrand.NewPCG(uint64(time.Now().UnixNano()), uint64(time.Now().UnixNano()>>32))
stateRngsMu.Unlock()
// Register hash functions
if err := state.RegisterGoFunction("__crypto_hash", cryptoHash); err != nil {
return err
}
// Register HMAC functions
if err := state.RegisterGoFunction("__crypto_hmac", cryptoHmac); err != nil {
return err
}
// Register UUID generation
if err := state.RegisterGoFunction("__crypto_uuid", cryptoUuid); err != nil {
return err
}
// Register random functions
if err := state.RegisterGoFunction("__crypto_random", cryptoRandom); err != nil {
return err
}
if err := state.RegisterGoFunction("__crypto_random_bytes", cryptoRandomBytes); err != nil {
return err
}
if err := state.RegisterGoFunction("__crypto_random_int", cryptoRandomInt); err != nil {
return err
}
if err := state.RegisterGoFunction("__crypto_random_seed", cryptoRandomSeed); err != nil {
return err
}
// Override Lua's math.random
if err := OverrideLuaRandom(state); err != nil {
return err
}
return nil
}
// CleanupCrypto cleans up resources when a state is closed
func CleanupCrypto(state *luajit.State) {
stateRngsMu.Lock()
delete(stateRngs, state)
stateRngsMu.Unlock()
}
// cryptoHash generates hash digests using various algorithms
func cryptoHash(state *luajit.State) int {
if !state.IsString(1) || !state.IsString(2) {
state.PushString("hash: expected (string data, string algorithm)")
return 1
}
data := state.ToString(1)
algorithm := state.ToString(2)
var h hash.Hash
switch algorithm {
case "md5":
h = md5.New()
case "sha1":
h = sha1.New()
case "sha256":
h = sha256.New()
case "sha512":
h = sha512.New()
default:
state.PushString(fmt.Sprintf("unsupported algorithm: %s", algorithm))
return 1
}
h.Write([]byte(data))
hashBytes := h.Sum(nil)
// Output format
outputFormat := "hex"
if state.GetTop() >= 3 && state.IsString(3) {
outputFormat = state.ToString(3)
}
switch outputFormat {
case "hex":
state.PushString(hex.EncodeToString(hashBytes))
case "binary":
state.PushString(string(hashBytes))
default:
state.PushString(hex.EncodeToString(hashBytes))
}
return 1
}
// cryptoHmac generates HMAC using various hash algorithms
func cryptoHmac(state *luajit.State) int {
if !state.IsString(1) || !state.IsString(2) || !state.IsString(3) {
state.PushString("hmac: expected (string data, string key, string algorithm)")
return 1
}
data := state.ToString(1)
key := state.ToString(2)
algorithm := state.ToString(3)
var h func() hash.Hash
switch algorithm {
case "md5":
h = md5.New
case "sha1":
h = sha1.New
case "sha256":
h = sha256.New
case "sha512":
h = sha512.New
default:
state.PushString(fmt.Sprintf("unsupported algorithm: %s", algorithm))
return 1
}
mac := hmac.New(h, []byte(key))
mac.Write([]byte(data))
macBytes := mac.Sum(nil)
// Output format
outputFormat := "hex"
if state.GetTop() >= 4 && state.IsString(4) {
outputFormat = state.ToString(4)
}
switch outputFormat {
case "hex":
state.PushString(hex.EncodeToString(macBytes))
case "binary":
state.PushString(string(macBytes))
default:
state.PushString(hex.EncodeToString(macBytes))
}
return 1
}
// cryptoUuid generates a random UUID v4
func cryptoUuid(state *luajit.State) int {
uuid := make([]byte, 16)
_, err := rand.Read(uuid)
if err != nil {
state.PushString(fmt.Sprintf("uuid: generation error: %v", err))
return 1
}
// Set version (4) and variant (RFC 4122)
uuid[6] = (uuid[6] & 0x0F) | 0x40
uuid[8] = (uuid[8] & 0x3F) | 0x80
uuidStr := fmt.Sprintf("%x-%x-%x-%x-%x",
uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
state.PushString(uuidStr)
return 1
}
// cryptoRandomBytes generates random bytes
func cryptoRandomBytes(state *luajit.State) int {
if !state.IsNumber(1) {
state.PushString("random_bytes: expected (number length)")
return 1
}
length := int(state.ToNumber(1))
if length <= 0 {
state.PushString("random_bytes: length must be positive")
return 1
}
// Check if secure
secure := true
if state.GetTop() >= 2 && state.IsBoolean(2) {
secure = state.ToBoolean(2)
}
bytes := make([]byte, length)
if secure {
_, err := rand.Read(bytes)
if err != nil {
state.PushString(fmt.Sprintf("random_bytes: error: %v", err))
return 1
}
} else {
stateRngsMu.Lock()
stateRng, ok := stateRngs[state]
stateRngsMu.Unlock()
if !ok {
state.PushString("random_bytes: RNG not initialized")
return 1
}
for i := range bytes {
bytes[i] = byte(stateRng.Uint64() & 0xFF)
}
}
// Output format
outputFormat := "binary"
if state.GetTop() >= 3 && state.IsString(3) {
outputFormat = state.ToString(3)
}
switch outputFormat {
case "binary":
state.PushString(string(bytes))
case "hex":
state.PushString(hex.EncodeToString(bytes))
default:
state.PushString(string(bytes))
}
return 1
}
// cryptoRandomInt generates a random integer in range [min, max]
func cryptoRandomInt(state *luajit.State) int {
if !state.IsNumber(1) || !state.IsNumber(2) {
state.PushString("random_int: expected (number min, number max)")
return 1
}
min := int64(state.ToNumber(1))
max := int64(state.ToNumber(2))
if max <= min {
state.PushString("random_int: max must be greater than min")
return 1
}
// Check if secure
secure := true
if state.GetTop() >= 3 && state.IsBoolean(3) {
secure = state.ToBoolean(3)
}
range_size := max - min + 1
var result int64
if secure {
bytes := make([]byte, 8)
_, err := rand.Read(bytes)
if err != nil {
state.PushString(fmt.Sprintf("random_int: error: %v", err))
return 1
}
val := binary.BigEndian.Uint64(bytes)
result = min + int64(val%uint64(range_size))
} else {
stateRngsMu.Lock()
stateRng, ok := stateRngs[state]
stateRngsMu.Unlock()
if !ok {
state.PushString("random_int: RNG not initialized")
return 1
}
result = min + int64(stateRng.Uint64()%uint64(range_size))
}
state.PushNumber(float64(result))
return 1
}
// cryptoRandom implements math.random functionality
func cryptoRandom(state *luajit.State) int {
numArgs := state.GetTop()
// Check if secure
secure := false
// math.random() - return [0,1)
if numArgs == 0 {
if secure {
bytes := make([]byte, 8)
_, err := rand.Read(bytes)
if err != nil {
state.PushString(fmt.Sprintf("random: error: %v", err))
return 1
}
val := binary.BigEndian.Uint64(bytes)
state.PushNumber(float64(val) / float64(math.MaxUint64))
} else {
stateRngsMu.Lock()
stateRng, ok := stateRngs[state]
stateRngsMu.Unlock()
if !ok {
state.PushString("random: RNG not initialized")
return 1
}
state.PushNumber(float64(stateRng.Uint64()) / float64(math.MaxUint64))
}
return 1
}
// math.random(n) - return integer [1,n]
if numArgs == 1 && state.IsNumber(1) {
n := int64(state.ToNumber(1))
if n < 1 {
state.PushString("random: upper bound must be >= 1")
return 1
}
state.PushNumber(1) // min
state.PushNumber(float64(n)) // max
state.PushBoolean(secure) // secure flag
return cryptoRandomInt(state)
}
// math.random(m, n) - return integer [m,n]
if numArgs >= 2 && state.IsNumber(1) && state.IsNumber(2) {
state.PushBoolean(secure) // secure flag
return cryptoRandomInt(state)
}
state.PushString("random: invalid arguments")
return 1
}
// cryptoRandomSeed sets seed for non-secure RNG
func cryptoRandomSeed(state *luajit.State) int {
if !state.IsNumber(1) {
state.PushString("randomseed: expected (number seed)")
return 1
}
seed := uint64(state.ToNumber(1))
stateRngsMu.Lock()
stateRngs[state] = mrand.NewPCG(seed, seed>>32)
stateRngsMu.Unlock()
return 0
}
// OverrideLuaRandom replaces Lua's math.random with Go implementation
func OverrideLuaRandom(state *luajit.State) error {
if err := state.RegisterGoFunction("go_math_random", cryptoRandom); err != nil {
return err
}
if err := state.RegisterGoFunction("go_math_randomseed", cryptoRandomSeed); err != nil {
return err
}
// Replace original functions
return state.DoString(`
-- Save original functions
_G._original_math_random = math.random
_G._original_math_randomseed = math.randomseed
-- Replace with Go implementations
math.random = go_math_random
math.randomseed = go_math_randomseed
-- Clean up global namespace
go_math_random = nil
go_math_randomseed = nil
`)
}

148
core/runner/crypto.lua Normal file
View File

@ -0,0 +1,148 @@
--[[
crypto.lua - Cryptographic functions powered by Go
]]--
local crypto = {}
-- ======================================================================
-- HASHING FUNCTIONS
-- ======================================================================
-- Generate hash digest using various algorithms
-- Algorithms: md5, sha1, sha256, sha512
-- Formats: hex (default), binary
function crypto.hash(data, algorithm, format)
if type(data) ~= "string" then
error("crypto.hash: data must be a string", 2)
end
algorithm = algorithm or "sha256"
format = format or "hex"
return __crypto_hash(data, algorithm, format)
end
-- Convenience functions for common hash algorithms
function crypto.md5(data, format)
return crypto.hash(data, "md5", format)
end
function crypto.sha1(data, format)
return crypto.hash(data, "sha1", format)
end
function crypto.sha256(data, format)
return crypto.hash(data, "sha256", format)
end
function crypto.sha512(data, format)
return crypto.hash(data, "sha512", format)
end
-- ======================================================================
-- HMAC FUNCTIONS
-- ======================================================================
-- Generate HMAC using various algorithms
-- Algorithms: md5, sha1, sha256, sha512
-- Formats: hex (default), binary
function crypto.hmac(data, key, algorithm, format)
if type(data) ~= "string" then
error("crypto.hmac: data must be a string", 2)
end
if type(key) ~= "string" then
error("crypto.hmac: key must be a string", 2)
end
algorithm = algorithm or "sha256"
format = format or "hex"
return __crypto_hmac(data, key, algorithm, format)
end
-- Convenience functions for common HMAC algorithms
function crypto.hmac_md5(data, key, format)
return crypto.hmac(data, key, "md5", format)
end
function crypto.hmac_sha1(data, key, format)
return crypto.hmac(data, key, "sha1", format)
end
function crypto.hmac_sha256(data, key, format)
return crypto.hmac(data, key, "sha256", format)
end
function crypto.hmac_sha512(data, key, format)
return crypto.hmac(data, key, "sha512", format)
end
-- ======================================================================
-- RANDOM FUNCTIONS
-- ======================================================================
-- Generate random bytes
-- Formats: binary (default), hex
function crypto.random_bytes(length, secure, format)
if type(length) ~= "number" or length <= 0 then
error("crypto.random_bytes: length must be positive", 2)
end
secure = secure ~= false -- Default to secure
format = format or "binary"
return __crypto_random_bytes(length, secure, format)
end
-- Generate random integer in range [min, max]
function crypto.random_int(min, max, secure)
if type(min) ~= "number" or type(max) ~= "number" then
error("crypto.random_int: min and max must be numbers", 2)
end
if max <= min then
error("crypto.random_int: max must be greater than min", 2)
end
secure = secure ~= false -- Default to secure
return __crypto_random_int(min, max, secure)
end
-- Generate random string of specified length
function crypto.random_string(length, charset, secure)
if type(length) ~= "number" or length <= 0 then
error("crypto.random_string: length must be positive", 2)
end
secure = secure ~= false -- Default to secure
-- Default character set: alphanumeric
charset = charset or "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
if type(charset) ~= "string" or #charset == 0 then
error("crypto.random_string: charset must be non-empty", 2)
end
local result = ""
local charset_length = #charset
for i = 1, length do
local index = crypto.random_int(1, charset_length, secure)
result = result .. charset:sub(index, index)
end
return result
end
-- ======================================================================
-- UUID FUNCTIONS
-- ======================================================================
-- Generate random UUID (v4)
function crypto.uuid()
return __crypto_uuid()
end
return crypto

View File

@ -28,6 +28,12 @@ var utilLuaCode string
//go:embed string.lua
var stringLuaCode string
//go:embed table.lua
var tableLuaCode string
//go:embed crypto.lua
var cryptoLuaCode string
// ModuleInfo holds information about an embeddable Lua module
type ModuleInfo struct {
Name string // Module name
@ -44,6 +50,8 @@ var (
{Name: "fs", Code: fsLuaCode},
{Name: "util", Code: utilLuaCode},
{Name: "string", Code: stringLuaCode},
{Name: "table", Code: tableLuaCode},
{Name: "crypto", Code: cryptoLuaCode},
}
)

View File

@ -117,6 +117,10 @@ func (s *Sandbox) registerCoreFunctions(state *luajit.State) error {
return err
}
if err := RegisterCryptoFunctions(state); err != nil {
return err
}
return nil
}

View File

@ -190,8 +190,8 @@ end
-- INSTALL EXTENSIONS INTO STRING LIBRARY
-- ======================================================================
for name, func in pairs(string_ext) do
string[name] = func
for name, func in pairs(string) do
string_ext[name] = func
end
return string_ext

1092
core/runner/table.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,31 +14,6 @@ function util.generate_token(length)
return __generate_token(length or 32)
end
-- Deep copy of tables
function util.deep_copy(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do res[k] = util.deep_copy(v) end
return res
end
-- Merge tables
function util.merge_tables(t1, t2)
if type(t1) ~= 'table' or type(t2) ~= 'table' then
error("Both arguments must be tables", 2)
end
local result = util.deep_copy(t1)
for k, v in pairs(t2) do
if type(v) == 'table' and type(result[k]) == 'table' then
result[k] = util.merge_tables(result[k], v)
else
result[k] = v
end
end
return result
end
-- ======================================================================
-- HTML ENTITY FUNCTIONS
-- ======================================================================