Class: Portage::Ucp::Rack::SignatureVerification

Inherits:
Object
  • Object
show all
Defined in:
lib/portage/ucp/rack/signature_verification.rb

Overview

Generic Rack middleware wrapping an inbound MCP/UCP endpoint (however the consumer mounts it, §7.3 — this gem stays deployment-agnostic): verifies the RFC 9421 HTTP Message Signature over the request (Security::Signature, §22) before the wrapped app ever sees it.

Verify-before-parse (same posture as Rack::WebhookEndpoint): the body is read and handed to Signature.verify! as raw bytes, never parsed as JSON here. Only on success is it rewound for the wrapped app to read and parse (as JSON-RPC) itself.

Instance Method Summary collapse

Constructor Details

#initialize(app, trusted_keys:, required_components: %w[@method @authority @path idempotency-key],, max_age: 300, logger: Portage::Ucp.configuration.logger) ⇒ SignatureVerification

Returns a new instance of SignatureVerification.

Parameters:

  • app (#call) —

    the wrapped Rack app (e.g. the MCP transport's own Rack endpoint)

  • trusted_keys (Array<Hash>, #call) —

    see Security::Signature

  • required_components (Array<String>) (defaults to: %w[@method @authority @path idempotency-key],) —

    forwarded to Security::Signature.verify!

  • max_age (Integer, nil) (defaults to: 300) —

    forwarded to Security::Signature.verify!

  • logger (Logger) (defaults to: Portage::Ucp.configuration.logger)


24
25
26
27
28
29
30
31
# File 'lib/portage/ucp/rack/signature_verification.rb', line 24

def initialize(app, trusted_keys:, required_components: %w[@method @authority @path idempotency-key],
               max_age: 300, logger: Portage::Ucp.configuration.logger)
  @app = app
  @trusted_keys = trusted_keys
  @required_components = required_components
  @max_age = max_age
  @logger = logger
end

Instance Method Details

#call(env) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/portage/ucp/rack/signature_verification.rb', line 33

def call(env)
  request = ::Rack::Request.new(env)
  body = request.body.read
  request.body.rewind

  Portage::Ucp::Security::Signature.verify!(
    method: request.request_method, authority: request.host_with_port, path: request.path,
    query: request.query_string.empty? ? nil : "?#{request.query_string}",
    headers: headers_from(env), body: body, trusted_keys: @trusted_keys,
    required_components: @required_components, max_age: @max_age
  )

  @app.call(env)
rescue Portage::Ucp::Security::SignatureError => e
  reject(e)
end