From c278a4007f362c26801bd43033bc3f64889cf98c Mon Sep 17 00:00:00 2001 From: Lee Date: Wed, 18 Feb 2026 10:17:27 -0800 Subject: [PATCH] Fix targetURI derivation --- CLAUDE.md | 5 +++++ base.go | 2 +- verify.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6c7d82a..6131250 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co This is an implementation of HTTP Message Signatures per RFC 9421. The library provides functionality for signing and verifying HTTP requests and responses using various cryptographic algorithms. +**Module:** `github.com/remitly-oss/httpsig-go` +**Go Version:** 1.22+ + ## Development Commands ### Testing @@ -34,6 +37,8 @@ This is an implementation of HTTP Message Signatures per RFC 9421. The library p - `Signer` struct orchestrates request/response signing - `SigningProfile` defines what fields and metadata to include - `SigningKey` holds cryptographic material and metadata + - `SigningKeyOpts` supports custom `crypto.Signer` implementations (e.g., TPM, HSM) + - Supports multiple signatures on a single message - Supports asymmetric (RSA, ECDSA, Ed25519) and symmetric (HMAC) algorithms 2. **Accept-Signature Support** (`accept.go`) diff --git a/base.go b/base.go index c6b2698..a9e371a 100644 --- a/base.go +++ b/base.go @@ -244,5 +244,5 @@ func deriveTargetURI(req *http.Request) string { scheme = "http" } - return fmt.Sprintf("%s://%s%s%s", scheme, req.Host, req.URL.RawPath, req.URL.RawQuery) + return fmt.Sprintf("%s://%s%s", scheme, req.Host, req.URL.RequestURI()) } diff --git a/verify.go b/verify.go index 3059a41..1553c91 100644 --- a/verify.go +++ b/verify.go @@ -13,8 +13,10 @@ import ( "fmt" "math/big" "net/http" + "net/http/httptest" "slices" "strings" + "testing" "time" sfv "github.com/dunglas/httpsfv" @@ -553,6 +555,57 @@ func (vp VerifyProfile) validateTiming(sig extractedSignature, currentTime time. return nil } +func TestDeriveTargetURI(t *testing.T) { + tests := []struct { + name string + url string + expected string + }{ + { + name: "simple path no query", + url: "https://example.com/path", + expected: "https://example.com/path", + }, + { + name: "path with query string", + url: "https://example.com/path?foo=bar", + expected: "https://example.com/path?foo=bar", + }, + { + name: "path with multiple query params", + url: "https://example.com/data?name=value&other=123", + expected: "https://example.com/data?name=value&other=123", + }, + { + name: "root path with query", + url: "https://example.com/?query=test", + expected: "https://example.com/?query=test", + }, + { + name: "nested path no query", + url: "https://example.com/api/v1/users", + expected: "https://example.com/api/v1/users", + }, + { + name: "empty query string preserved", + url: "https://example.com/path?", + expected: "https://example.com/path?", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // httptest.NewRequest sets req.TLS for https URLs + req := httptest.NewRequest("GET", tc.url, nil) + + got := deriveTargetURI(req) + if got != tc.expected { + t.Errorf("deriveTargetURI() = %q, want %q", got, tc.expected) + } + }) + } +} + type metadataProviderFromParams struct { Params *sfv.Params }