-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathed25519.lua
More file actions
142 lines (116 loc) · 4.72 KB
/
Copy pathed25519.lua
File metadata and controls
142 lines (116 loc) · 4.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
-- Ed25519 signature verification module for Kong plugin
-- Uses OpenSSL via FFI for reliable cryptographic operations
local ffi = require("ffi")
-- Load ngx.base64 (always available in Kong/OpenResty)
local base64 = require("ngx.base64")
local _M = {}
-- Cache for public key binary (avoid converting JWK on every request)
local _cached_public_key_binary = nil
local _cached_keyid = nil
-- Define OpenSSL Ed25519 functions via FFI
ffi.cdef[[
typedef struct evp_pkey_ctx_st EVP_PKEY_CTX;
typedef struct evp_pkey_st EVP_PKEY;
typedef struct evp_md_ctx_st EVP_MD_CTX;
EVP_PKEY* EVP_PKEY_new_raw_public_key(int type, void* e, const unsigned char* pub, size_t len);
void EVP_PKEY_free(EVP_PKEY* pkey);
EVP_MD_CTX* EVP_MD_CTX_new(void);
void EVP_MD_CTX_free(EVP_MD_CTX* ctx);
int EVP_DigestVerifyInit(EVP_MD_CTX* ctx, EVP_PKEY_CTX** pctx, void* type, void* e, EVP_PKEY* pkey);
int EVP_DigestVerify(EVP_MD_CTX* ctx, const unsigned char* sig, size_t siglen, const unsigned char* tbs, size_t tbslen);
enum {
EVP_PKEY_ED25519 = 1087
};
]]
-- Load OpenSSL library (simple, single approach)
local libssl = nil
local openssl_available = false
local ok, ssl = pcall(ffi.load, "ssl")
if ok then
libssl = ssl
openssl_available = true
else
kong.log.err("ChatGPT: CRITICAL - Failed to load OpenSSL: " .. ssl)
end
-- Convert JWK x coordinate (base64url) to binary public key
function _M.jwk_to_public_key(x_coordinate)
if not x_coordinate then
return nil, "Missing x coordinate"
end
-- Decode base64url to binary (32 bytes for Ed25519)
local public_key_binary = base64.decode_base64url(x_coordinate)
if not public_key_binary or #public_key_binary ~= 32 then
return nil, "Invalid public key length"
end
return public_key_binary, nil
end
-- Verify signature using OpenSSL via FFI
function _M.verify_signature(message, signature_base64, public_key_binary)
if not libssl then
kong.log.err("ChatGPT: CRITICAL - OpenSSL not available, signature verification FAILED")
return false -- SECURITY: Never allow without real crypto
end
-- ChatGPT uses regular base64, but ngx.base64 only has base64url decoder
-- Convert regular base64 to base64url format
local signature_base64url = signature_base64:gsub("+", "-"):gsub("/", "_"):gsub("=", "")
local signature_binary = base64.decode_base64url(signature_base64url)
if not signature_binary then
return false
end
if #signature_binary ~= 64 then
return false
end
-- Create Ed25519 public key
local pkey = libssl.EVP_PKEY_new_raw_public_key(1087, nil, public_key_binary, 32)
if pkey == nil then
kong.log.err("ChatGPT: Failed to create Ed25519 public key")
return false
end
-- Create verification context
local ctx = libssl.EVP_MD_CTX_new()
if ctx == nil then
libssl.EVP_PKEY_free(pkey)
kong.log.err("ChatGPT: Failed to create verification context")
return false
end
-- Initialize verification (Ed25519 doesn't use a digest)
local init_result = libssl.EVP_DigestVerifyInit(ctx, nil, nil, nil, pkey)
if init_result ~= 1 then
libssl.EVP_MD_CTX_free(ctx)
libssl.EVP_PKEY_free(pkey)
kong.log.err("ChatGPT: Failed to initialize verification")
return false
end
-- Verify signature
local verify_result = libssl.EVP_DigestVerify(ctx, signature_binary, 64, message, #message)
-- Cleanup
libssl.EVP_MD_CTX_free(ctx)
libssl.EVP_PKEY_free(pkey)
return verify_result == 1
end
-- High-level verification function used by the handler
function _M.verify_chatgpt_signature(message, signature_b64, received_keyid, expected_keyid, public_key_x)
-- Check keyid
if received_keyid ~= expected_keyid then
return false, "Invalid keyid: expected " .. expected_keyid .. ", got " .. (received_keyid or "nil")
end
-- Use cached public key binary if available for this keyid
local public_key_binary
if _cached_keyid == expected_keyid and _cached_public_key_binary then
public_key_binary = _cached_public_key_binary
else
-- Convert and cache public key
local err
public_key_binary, err = _M.jwk_to_public_key(public_key_x)
if not public_key_binary then
return false, "Public key conversion failed: " .. err
end
-- Cache for future requests
_cached_keyid = expected_keyid
_cached_public_key_binary = public_key_binary
end
-- Verify signature
local is_valid = _M.verify_signature(message, signature_b64, public_key_binary)
return is_valid, is_valid and "success" or "signature verification failed"
end
return _M