diff --git a/test/tool/net/lcrypto_test.lua b/test/tool/net/lcrypto_test.lua index 5b6c6a640..fe8335cfd 100644 --- a/test/tool/net/lcrypto_test.lua +++ b/test/tool/net/lcrypto_test.lua @@ -1,7 +1,7 @@ -- Helper function to print test results local function assert_equal(actual, expected, message) if actual ~= expected then - error(message .. ": expected " .. tostring(expected) .. ", got " .. tostring(actual)) + error("FAIL: " .. message .. ": expected " .. tostring(expected) .. ", got " .. tostring(actual)) else print("PASS: " .. message) end @@ -19,146 +19,153 @@ end local function test_rsa_keypair_generation() print('\27[1;7mTest RSA key pair generation \27[0m') local priv_key, pub_key = crypto.generatekeypair("rsa", 2048) - assert_equal(type(priv_key), "string", "RSA private key generation") - assert_equal(type(pub_key), "string", "RSA public key generation") + assert_equal(type(priv_key), "string", "Private key type") + assert_equal(type(pub_key), "string", "Public key type") end -- Test ECDSA key pair generation local function test_ecdsa_keypair_generation() print('\n\27[1;7mTest ECDSA key pair generation \27[0m') local priv_key, pub_key = crypto.generatekeypair("ecdsa", "secp256r1") - assert_equal(type(priv_key), "string", "ECDSA private key generation") - assert_equal(type(pub_key), "string", "ECDSA public key generation") + assert_equal(type(priv_key), "string", "Private key type") + assert_equal(type(pub_key), "string", "Public key type") end -- Test RSA encryption and decryption local function test_rsa_encryption_decryption() print('\n\27[1;7mTest RSA encryption and decryption \27[0m') local priv_key, pub_key = crypto.generatekeypair("rsa", 2048) + assert(type(priv_key) == "string", "Private key type") + assert(type(pub_key) == "string", "Public key type") local plaintext = "Hello, RSA!" local encrypted = crypto.encrypt("rsa", pub_key, plaintext) - assert_equal(type(encrypted), "string", "RSA encryption") + assert_equal(type(encrypted), "string", "Ciphertext type") local decrypted = crypto.decrypt("rsa", priv_key, encrypted) - assert_equal(decrypted, plaintext, "RSA decryption") + assert_equal(decrypted, plaintext, "Decrypted ciphertext matches plaintext") end -- Test RSA signing and verification local function test_rsa_signing_verification() print('\n\27[1;7mTest RSA signing and verification \27[0m') local priv_key, pub_key = crypto.generatekeypair("rsa", 2048) + assert(type(priv_key) == "string", "Private key type") + assert(type(pub_key) == "string", "Public key type") local message = "Sign this message" local signature = crypto.sign("rsa", priv_key, message, "sha256") - assert_equal(type(signature), "string", "RSA signing") + assert_equal(type(signature), "string", "Signature type") local is_valid = crypto.verify("rsa", pub_key, message, signature, "sha256") - assert_equal(is_valid, true, "RSA signature verification") + assert_equal(is_valid, true, "Signature verification") end -- Test ECDSA signing and verification local function test_ecdsa_signing_verification() print('\n\27[1;7mTest ECDSA signing and verification \27[0m') local priv_key, pub_key = crypto.generatekeypair("ecdsa", "secp256r1") + assert(type(priv_key) == "string", "Private key type") + assert(type(pub_key) == "string", "Public key type") local message = "Sign this message with ECDSA" local signature = crypto.sign("ecdsa", priv_key, message, "sha256") - assert_equal(type(signature), "string", "ECDSA signing") + assert_equal(type(signature), "string", "Signature type") local is_valid = crypto.verify("ecdsa", pub_key, message, signature, "sha256") - assert_equal(is_valid, true, "ECDSA signature verification") + assert_equal(is_valid, true, "Signature verification") end -- Test AES key generation local function test_aes_key_generation() print('\n\27[1;7mTest AES key generation \27[0m') local key = crypto.generatekeypair('aes', 256) -- 256-bit key - assert_equal(type(key), "string", "AES key generation") - assert_equal(#key, 32, "AES key length (256 bits)") + assert_equal(type(key), "string", "Key type") + assert_equal(#key, 32, "Key length (256 bits)") end -- Test AES encryption and decryption (CBC mode) local function test_aes_encryption_decryption() print('\n\27[1;7mTest AES encryption and decryption (CBC mode) \27[0m') - local key = crypto.generatekeypair('aes',256) -- 256-bit key + local key = crypto.generatekeypair('aes', 256) -- 256-bit key local plaintext = "Hello, AES CBC!" -- Encrypt without providing IV (should auto-generate IV) print('\27[1mAES encryption (auto IV)\27[0m') local encrypted, iv = crypto.encrypt("aes", key, plaintext, nil) - assert_equal(type(encrypted), "string", "AES encryption (CBC, auto IV)") - assert_equal(type(iv), "string", "AES IV (auto-generated)") + assert_equal(type(encrypted), "string", "Ciphertext type") + assert_equal(type(iv), "string", "IV type") -- Decrypt print('\n\27[1mAES decryption (auto IV)\27[0m') local decrypted = crypto.decrypt("aes", key, encrypted, iv) - assert_equal(decrypted, plaintext, "AES decryption (CBC, auto IV)") + assert_equal(decrypted, plaintext, "Decrypted ciphertext matches plaintext") -- Encrypt with explicit IV print('\n\27[1mAES encryption (explicit IV)\27[0m') local iv2 = GetRandomBytes(16) local encrypted2, iv_used = crypto.encrypt("aes", key, plaintext, iv2) - assert_equal(type(encrypted2), "string", "AES encryption (CBC, explicit IV)") - assert_equal(iv_used, iv2, "AES IV (explicit)") + assert_equal(type(encrypted2), "string", "Ciphertext type") + assert_equal(iv_used, iv2, "IV match") print('\n\27[1mAES decryption (explicit IV)\27[0m') local decrypted2 = crypto.decrypt("aes", key, encrypted2, iv2) - assert_equal(decrypted2, plaintext, "AES decryption (CBC, explicit IV)") + assert_equal(decrypted2, plaintext, "Decrypted ciphertext matches plaintext") end -- Test AES encryption and decryption (CTR mode) local function test_aes_encryption_decryption_ctr() print('\n\27[1;7mTest AES encryption and decryption (CTR mode) \27[0m') - local key = crypto.generatekeypair('aes',256) + local key = crypto.generatekeypair('aes', 256) local plaintext = "Hello, AES CTR!" -- Encrypt without providing IV (should auto-generate IV) print('\27[1mAES encryption (auto IV)\27[0m') local encrypted, iv = crypto.encrypt("aes", key, plaintext, nil, "ctr") - assert_equal(type(encrypted), "string", "AES encryption (CTR, auto IV)") - assert_equal(type(iv), "string", "AES IV (auto-generated, CTR)") + assert_equal(type(encrypted), "string", "Ciphertext type") + assert_equal(type(iv), "string", "IV type") -- Decrypt print('\n\27[1mAES decryption (auto IV)\27[0m') local decrypted = crypto.decrypt("aes", key, encrypted, iv, "ctr") - assert_equal(decrypted, plaintext, "AES decryption (CTR, auto IV)") + assert_equal(decrypted, plaintext, "Decrypted ciphertext matches plaintext") -- Encrypt with explicit IV print('\n\27[1mAES encryption (explicit IV)\27[0m') local iv2 = GetRandomBytes(16) local encrypted2, iv_used = crypto.encrypt("aes", key, plaintext, iv2, "ctr") - assert_equal(type(encrypted2), "string", "AES encryption (CTR, explicit IV)") - assert_equal(iv_used, iv2, "AES IV (explicit, CTR)") + assert_equal(type(encrypted2), "string", "Ciphertext type") + assert_equal(iv_used, iv2, "IV match") print('\n\27[1mAES decryption (explicit IV)\27[0m') local decrypted2 = crypto.decrypt("aes", key, encrypted2, iv2, "ctr") - assert_equal(decrypted2, plaintext, "AES decryption (CTR, explicit IV)") + assert_equal(decrypted2, plaintext, "Decrypted ciphertext matches plaintext") end -- Test AES encryption and decryption (GCM mode) local function test_aes_encryption_decryption_gcm() print('\n\27[1;7mTest AES encryption and decryption (GCM mode) \27[0m') - local key = crypto.generatekeypair('aes',256) + local key = crypto.generatekeypair('aes', 256) local plaintext = "Hello, AES GCM!" -- Encrypt without providing IV (should auto-generate IV) print('\27[1mAES encryption (auto IV)\27[0m') local encrypted, iv, tag = crypto.encrypt("aes", key, plaintext, nil, "gcm") - assert_equal(type(encrypted), "string", "AES encryption (GCM, auto IV)") - assert_equal(type(iv), "string", "AES IV (auto-generated, GCM)") - assert_equal(type(tag), "string", "AES GCM tag (auto IV)") + assert_equal(#plaintext, #encrypted, "Ciphertext length matches plaintext") + assert_equal(type(encrypted), "string", "Ciphertext type") + assert_equal(type(iv), "string", "IV type") + assert_equal(type(tag), "string", "Tag type") -- Decrypt print('\n\27[1mAES decryption (auto IV)\27[0m') local decrypted = crypto.decrypt("aes", key, encrypted, iv, "gcm", nil, tag) - assert_equal(decrypted, plaintext, "AES decryption (GCM, auto IV)") + assert_equal(decrypted, plaintext, "Decrypted ciphertext matches plaintext") -- Encrypt with explicit IV print('\n\27[1mAES encryption (explicit IV)\27[0m') local iv2 = GetRandomBytes(13) -- GCM IV/nonce can be 12-16 bytes, 12 is standard local encrypted2, iv_used, tag2 = crypto.encrypt("aes", key, plaintext, iv2, "gcm") - assert_equal(type(encrypted2), "string", "AES encryption (GCM, explicit IV)") - assert_equal(iv_used, iv2, "AES IV (explicit, GCM)") - assert_equal(type(tag2), "string", "AES GCM tag (explicit IV)") + assert_equal(type(encrypted2), "string", "Ciphertext type") + assert_equal(iv_used, iv2, "IV match") + assert_equal(type(tag2), "string", "Tag type") print('\n\27[1mAES decryption (explicit IV)\27[0m') local decrypted2 = crypto.decrypt("aes", key, encrypted2, iv2, "gcm", nil, tag2) - assert_equal(decrypted2, plaintext, "AES decryption (GCM, explicit IV)") + assert_equal(decrypted2, plaintext, "Decrypted ciphertext matches plaintext") end -- Test PemToJwk conversion @@ -167,25 +174,25 @@ local function test_pem_to_jwk() local priv_key, pub_key = crypto.generatekeypair() print('\27[1mRSA Private key to JWK conversion\27[0m') local priv_jwk = crypto.convertPemToJwk(priv_key) - assert_equal(type(priv_jwk), "table", "PEM to JWK conversion") - assert_equal(priv_jwk.kty, "RSA", "JWK key type") + assert_equal(type(priv_jwk), "table", "JWK type") + assert_equal(priv_jwk.kty, "RSA", "kty is correct") print('\n\27[1mRSA Public key to JWK conversion\27[0m') local pub_jwk = crypto.convertPemToJwk(pub_key) - assert_equal(type(pub_jwk), "table", "PEM to JWK conversion") - assert_equal(pub_jwk.kty, "RSA", "JWK key type") + assert_equal(type(pub_jwk), "table", "JWK type") + assert_equal(pub_jwk.kty, "RSA", "kty is correct") -- Test ECDSA keys local priv_key, pub_key = crypto.generatekeypair('ecdsa') print('\n\27[1mECDSA Private key to JWK conversion\27[0m') local priv_jwk = crypto.convertPemToJwk(priv_key) - assert_equal(type(priv_jwk), "table", "PEM to JWK conversion") - assert_equal(priv_jwk.kty, "EC", "JWK key type") + assert_equal(type(priv_jwk), "table", "JWK type") + assert_equal(priv_jwk.kty, "EC", "kty is correct") print('\n\27[1mECDSA Public key to JWK conversion\27[0m') local pub_jwk = crypto.convertPemToJwk(pub_key) - assert_equal(type(pub_jwk), "table", "PEM to JWK conversion") - assert_equal(pub_jwk.kty, "EC", "JWK key type") + assert_equal(type(pub_jwk), "table", "JWK type") + assert_equal(pub_jwk.kty, "EC", "kty is correct") end -- Test CSR generation @@ -194,24 +201,25 @@ local function test_csr_generation() local priv_key, _ = crypto.generatekeypair() local subject_name = "CN=example.com,O=Example Org,C=US" local san = "DNS:example.com, DNS:www.example.com, IP:192.168.1.1" + assert(type(priv_key) == "string", "Private key type") - local csr = crypto.GenerateCsr(priv_key, subject_name) + local csr = crypto.generateCsr(priv_key, subject_name) assert_equal(type(csr), "string", "CSR generation with subject name") - csr = crypto.GenerateCsr(priv_key, subject_name, san) + csr = crypto.generateCsr(priv_key, subject_name, san) assert_equal(type(csr), "string", "CSR generation with subject name and san") - csr = crypto.GenerateCsr(priv_key, nil, san) + csr = crypto.generateCsr(priv_key, nil, san) assert_equal(type(csr), "string", "CSR generation with nil subject name and san") - csr = crypto.GenerateCsr(priv_key, '', san) + csr = crypto.generateCsr(priv_key, '', san) assert_equal(type(csr), "string", "CSR generation with empty subject name and san") -- These should fail - csr = crypto.GenerateCsr(priv_key, '') + csr = crypto.generateCsr(priv_key, '') assert_not_equal(type(csr), "string", "CSR generation with empty subject name and no san is rejected") - csr = crypto.GenerateCsr(priv_key) + csr = crypto.generateCsr(priv_key) assert_not_equal(type(csr), "string", "CSR generation with nil subject name and no san is rejected") end @@ -231,9 +239,9 @@ local function run_tests() test_csr_generation() print('') print("All tests passed!") - EXIT=0 + EXIT = 0 return EXIT end -EXIT=70 +EXIT = 70 os.exit(run_tests()) diff --git a/tool/net/definitions.lua b/tool/net/definitions.lua index 3732416b0..661e0ea27 100644 --- a/tool/net/definitions.lua +++ b/tool/net/definitions.lua @@ -8048,6 +8048,73 @@ kUrlPlus = nil ---@type integer to transcode ISO-8859-1 input into UTF-8. See `ParseUrl`. kUrlLatin1 = nil + +--- This module provides cryptographic operations. + +--- The crypto module for cryptographic operations +crypto = {} + +--- Converts a PEM-encoded key to JWK format +---@param pem string PEM-encoded key +---@return table?, string? JWK table or nil on error +---@return string? error message +function crypto.convertPemToJwk(pem) end + +--- Generates a Certificate Signing Request (CSR) +---@param key_pem string PEM-encoded private key +---@param subject_name string? X.509 subject name +---@param san_list string? Subject Alternative Names +---@return string?, string? CSR in PEM format or nil on error and error message +function crypto.generateCsr(key_pem, subject_name, san_list) end + +--- Signs data using a private key +---@param key_type string "rsa" or "ecdsa" +---@param private_key string PEM-encoded private key +---@param message string Data to sign +---@param hash_algo string? Hash algorithm (default: SHA-256) +---@return string?, string? Signature or nil on error and error message +function crypto.sign(key_type, private_key, message, hash_algo) end + +--- Verifies a signature +---@param key_type string "rsa" or "ecdsa" +---@param public_key string PEM-encoded public key +---@param message string Original message +---@param signature string Signature to verify +---@param hash_algo string? Hash algorithm (default: SHA-256) +---@return boolean?, string? True if valid or nil on error and error message +function crypto.verify(key_type, public_key, message, signature, hash_algo) end + +--- Encrypts data +---@param cipher_type string "rsa" or "aes" +---@param key string Public key or symmetric key +---@param plaintext string Data to encrypt +---@param mode string? AES mode: "cbc", "gcm", "ctr" (default: "cbc") +---@param iv string? Initialization Vector for AES +---@param aad string? Additional data for AES-GCM +---@return string? Encrypted data or nil on error +---@return string? IV or error message +---@return string? Authentication tag for GCM mode +function crypto.encrypt(cipher_type, key, plaintext, mode, iv, aad) end + +--- Decrypts data +---@param cipher_type string "rsa" or "aes" +---@param key string Private key or symmetric key +---@param ciphertext string Data to decrypt +---@param iv string? Initialization Vector for AES +---@param mode string? AES mode: "cbc", "gcm", "ctr" (default: "cbc") +---@param tag string? Authentication tag for AES-GCM +---@param aad string? Additional data for AES-GCM +---@return string?, string? Decrypted data or nil on error and error message +function crypto.decrypt(cipher_type, key, ciphertext, iv, mode, tag, aad) end + +--- Generates cryptographic keys +---@param key_type string? "rsa", "ecdsa", or "aes" +---@param key_size_or_curve number|string? Key size or curve name +---@return string? Private key or nil on error +---@return string? Public key (nil for AES) or error message +function crypto.generatekeypair(key_type, key_size_or_curve) end + + --[[ ──────────────────────────────────────────────────────────────────────────────── LEGAL diff --git a/tool/net/lcrypto.c b/tool/net/lcrypto.c index d5ca2890f..c473df4cc 100644 --- a/tool/net/lcrypto.c +++ b/tool/net/lcrypto.c @@ -1595,7 +1595,7 @@ static const luaL_Reg kLuaCrypto[] = { {"decrypt", LuaCryptoDecrypt}, // {"generatekeypair", LuaCryptoGenerateKeyPair}, // {"convertPemToJwk", LuaConvertPemToJwk}, // - {"GenerateCsr", LuaGenerateCSR}, // + {"generateCsr", LuaGenerateCSR}, // {0}, // };