Repository navigation
Expand file tree
/
Copy pathhandler.lua
More file actions
95 lines (76 loc) · 3.49 KB
/
Copy pathhandler.lua
File metadata and controls
95 lines (76 loc) · 3.49 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
local kong = kong
local ed25519 = require "kong.plugins.chatgpt.ed25519"
local ChatGPTHandler = {
PRIORITY = 1000,
VERSION = "0.0.1"
}
local function verify_signature(message, signature_b64, keyid, expected_keyid, public_key_x)
local success, error_msg = ed25519.verify_chatgpt_signature(
message, signature_b64, keyid, expected_keyid, public_key_x
)
if not success then
kong.log.warn("ChatGPT - Signature verification failed: " .. (error_msg or "unknown error"))
end
return success
end
function ChatGPTHandler:access(config)
local headers = kong.request.get_headers()
local signature = headers["signature"]
local signature_agent = headers["signature-agent"]
-- Skip validation if not a ChatGPT request
-- IMPORTANT: If this logic is changed, it MUST also be reflected in Fastly as well.
-- Fastly will not send traffic to DataDome which matches the pattern described above.
if not signature or signature_agent ~= '"https://chatgpt.com"' then
return
end
local signature_input = headers["signature-input"]
kong.service.request.clear_header("X-ChatGPT-Verified")
-- Parse signature header (format: sig1=:base64signature:)
local sig_value = signature:match("sig1=:([^:]+):")
if not sig_value then
return kong.response.exit(403, { error = "Invalid signature format" })
end
-- Extract signature parameters from Signature-Input header
if not signature_input then
return kong.response.exit(403, { error = "Missing signature parameters" })
end
local signature_params = signature_input:match("sig1=(.+)")
if not signature_params then
return kong.response.exit(403, { error = "Missing signature parameters" })
end
-- Extract keyid for validation
local keyid = signature_params:match('keyid="([^"]+)"')
-- Parse components from signature input (RFC 9421)
local components_str = signature_params:match('^%(([^)]+)%)')
if not components_str then
return kong.response.exit(403, { error = "Invalid signature format" })
end
-- Extract component names
local components = {}
for component in components_str:gmatch('"([^"]+)"') do
table.insert(components, component)
end
-- Build signature message according to RFC 9421 (only include specified components)
local message_parts = {}
for _, component in ipairs(components) do
if component == "@authority" then
local authority_value = headers["host"] or kong.request.get_host()
table.insert(message_parts, '"@authority": ' .. authority_value)
elseif component == "@method" then
table.insert(message_parts, '"@method": ' .. kong.request.get_method())
elseif component == "@path" then
table.insert(message_parts, '"@path": ' .. kong.request.get_path())
elseif component == "signature-agent" then
table.insert(message_parts, '"signature-agent": ' .. signature_agent)
end
end
-- Always add @signature-params at the end (RFC 9421 requirement)
table.insert(message_parts, '"@signature-params": ' .. signature_params)
local message = table.concat(message_parts, "\n")
-- Verify signature
if not verify_signature(message, sig_value, keyid, config.keyid, config.public_key_x) then
return kong.response.exit(403, { error = "Invalid ChatGPT signature" })
end
kong.service.request.set_header("X-ChatGPT-Verified", "true")
end
return ChatGPTHandler