@@ -12,235 +12,37 @@ go get github.com/lestrrat-go/htmsig
1212
1313## Quick Start
1414
15- ### Basic Request Signing
15+ ### Client/Server Example
1616
17- <!-- INCLUDE(example_test.go) -->
18- ``` go
19- package htmsig_test
20-
21- import (
22- " context"
23- " crypto/rand"
24- " crypto/rsa"
25- " fmt"
26- " net/http"
27-
28- " github.com/lestrrat-go/htmsig"
29- " github.com/lestrrat-go/htmsig/component"
30- " github.com/lestrrat-go/htmsig/input"
31- )
32-
33- // ExampleSign demonstrates how to sign an HTTP request
34- func ExampleSign () {
35- // Generate an RSA key pair
36- privateKey , err := rsa.GenerateKey (rand.Reader , 2048 )
37- if err != nil {
38- panic (err)
39- }
40-
41- // Create an HTTP request to sign
42- req , err := http.NewRequest (http.MethodPost , " https://example.com/api/data" , nil )
43- if err != nil {
44- panic (err)
45- }
46- req.Header .Set (" Content-Type" , " application/json" )
47- req.Header .Set (" Date" , " Tue, 20 Apr 2021 02:07:55 GMT" )
48-
49- // Create signature definition
50- def , err := input.NewDefinitionBuilder ().
51- Label (" my-signature" ).
52- Components (
53- component.Method (),
54- component.TargetURI (),
55- component.Authority (),
56- component.New (" content-type" ),
57- component.New (" date" ),
58- ).
59- KeyID (" my-key-id" ).
60- Algorithm (" rsa-pss-sha512" ).
61- Build ()
62- if err != nil {
63- panic (err)
64- }
65-
66- // Create input value containing the signature definition
67- inputValue := input.NewValueBuilder ().AddDefinition (def).MustBuild ()
68-
69- // Sign the request
70- ctx := component.WithRequestInfoFromHTTP (context.Background (), req)
71- err = htmsig.SignRequest (ctx, req.Header , inputValue, privateKey)
72- if err != nil {
73- panic (err)
74- }
75-
76- fmt.Printf (" Signed request has Signature-Input: %t \n " , req.Header .Get (" Signature-Input" ) != " " )
77- fmt.Printf (" Signed request has Signature: %t \n " , req.Header .Get (" Signature" ) != " " )
78- // Output:
79- // Signed request has Signature-Input: true
80- // Signed request has Signature: true
81- }
82-
83- // ExampleVerify demonstrates how to verify an HTTP request signature
84- func ExampleVerify () {
85- // Generate an RSA key pair for the example
86- privateKey , err := rsa.GenerateKey (rand.Reader , 2048 )
87- if err != nil {
88- panic (err)
89- }
90- publicKey := &privateKey.PublicKey
91-
92- // Create and sign a request first
93- req , err := http.NewRequest (" POST" , " https://example.com/api/data" , nil )
94- if err != nil {
95- panic (err)
96- }
97- req.Header .Set (" Content-Type" , " application/json" )
98- req.Header .Set (" Date" , " Tue, 20 Apr 2021 02:07:55 GMT" )
99-
100- def , err := input.NewDefinitionBuilder ().
101- Label (" my-signature" ).
102- Components (
103- component.Method (),
104- component.TargetURI (),
105- component.Authority (),
106- component.New (" content-type" ),
107- component.New (" date" ),
108- ).
109- KeyID (" my-key-id" ).
110- Algorithm (" rsa-pss-sha512" ).
111- Build ()
112- if err != nil {
113- panic (err)
114- }
115-
116- inputValue := input.NewValueBuilder ().AddDefinition (def).MustBuild ()
117- ctx := component.WithRequestInfoFromHTTP (context.Background (), req)
118- err = htmsig.SignRequest (ctx, req.Header , inputValue, privateKey)
119- if err != nil {
120- panic (err)
121- }
122-
123- // Create a key resolver that can resolve keys by ID
124- keyResolver := &exampleKeyResolver{
125- keys: map [string ]any{
126- " my-key-id" : publicKey,
127- },
128- }
129-
130- // Verify the request signature
131- ctx = component.WithRequestInfoFromHTTP (context.Background (), req)
132- err = htmsig.VerifyRequest (ctx, req.Header , keyResolver)
133- if err != nil {
134- fmt.Printf (" Verification failed: %v \n " , err)
135- return
136- }
17+ The easiest way to get started is using the ` http ` package for automatic signing and verification:
13718
138- fmt.Println (" Signature verification successful" )
139- // Output:
140- // Signature verification successful
141- }
142-
143- // exampleKeyResolver is a simple key resolver for the example
144- type exampleKeyResolver struct {
145- keys map [string ]any
146- }
147-
148- func (r *exampleKeyResolver ) ResolveKey (keyID string ) (any , error ) {
149- key , exists := r.keys [keyID]
150- if !exists {
151- return nil , fmt.Errorf (" key %q not found" , keyID)
152- }
153- return key, nil
154- }
155-
156- ```
157- source: [ example_test.go] ( https://github.com/lestrrat-go/htmsig/blob/main/example_test.go )
158- <!-- END INCLUDE -->
159-
160- ### HTTP Server with Signature Verification
161-
162- <!-- INCLUDE(http/http_example_test.go) -->
19+ <!-- INCLUDE(examples/client_server_example_test.go) -->
16320``` go
164- package http_test
21+ package htmsig_test
16522
16623import (
16724 " fmt"
16825 " net/http"
16926 " net/http/httptest"
27+ " strings"
17028 " time"
17129
17230 " github.com/lestrrat-go/htmsig/component"
17331 htmsighttp " github.com/lestrrat-go/htmsig/http"
17432)
17533
176- // ExampleWrap demonstrates how to create an HTTP handler with signature verification and response signing.
177- func ExampleWrap () {
178- // Use HMAC key for deterministic signatures
179- hmacKey := []byte (" test-hmac-secret-key" )
180-
181- // Create verifier that skips missing signatures for demo
182- verifier := htmsighttp.NewVerifier (
183- &htmsighttp.StaticKeyResolver {Key: hmacKey},
184- htmsighttp.WithSkipOnMissing (true ), // Allow unsigned requests for demo
185- )
186-
187- // Create the main application handler
188- appHandler := http.HandlerFunc (func (w http.ResponseWriter , r *http.Request ) {
189- w.Header ().Set (" Content-Type" , " application/json" )
190- w.Header ().Set (" Date" , " Mon, 01 Jan 2024 00:00:00 GMT" ) // Fixed date for testing
191- _, _ = fmt.Fprint (w, ` {"message": "Hello, signed world!"}` )
192- })
193-
194- // Create fixed clock for deterministic timestamps
195- fixedTime := time.Date (2024 , 1 , 1 , 0 , 0 , 0 , 0 , time.UTC )
196- clock := htmsighttp.FixedClock (fixedTime)
197-
198- // Create response signer with specific components
199- signer := htmsighttp.NewResponseSigner (hmacKey, " server-key-1" ,
200- htmsighttp.WithSignerComponents (
201- component.Status (),
202- component.New (" content-type" ),
203- component.New (" date" ),
204- ),
205- htmsighttp.WithSignerSignatureLabel (" server-key-1" ),
206- htmsighttp.WithClock (clock))
207-
208- // Wrap with both verification and signing
209- wrappedHandler := htmsighttp.Wrap (appHandler,
210- htmsighttp.WithVerifier (verifier),
211- htmsighttp.WithSigner (signer))
212-
213- // Test the wrapped handler
214- req := httptest.NewRequest (" GET" , " /api/data" , nil )
215- w := httptest.NewRecorder ()
216- wrappedHandler.ServeHTTP (w, req)
217-
218- fmt.Printf (" Status: %d \n " , w.Code )
219- fmt.Printf (" Content-Type: %s \n " , w.Header ().Get (" Content-Type" ))
220- fmt.Printf (" Signature: %s \n " , w.Header ().Get (" Signature" ))
221- fmt.Printf (" Signature-Input: %s \n " , w.Header ().Get (" Signature-Input" ))
222- fmt.Printf (" Body: %s \n " , w.Body .String ())
223-
224- // Output:
225- // Status: 200
226- // Content-Type: application/json
227- // Signature: server-key-1=:JohOBV+tyheV58LS0h5rT3TfHynbV6bncnEG0jP5vnE=:
228- // Signature-Input: server-key-1=("@status" "content-type" "date");created=1704067200;keyid="server-key-1"
229- // Body: {"message": "Hello, signed world!"}
230- }
231-
232- // ExampleNewClient demonstrates how to create an HTTP client that automatically signs requests
233- // and communicates with a server that both verifies incoming signatures and signs responses.
234- func ExampleNewClient () {
34+ // Example demonstrates a complete client/server interaction
35+ // with HTTP message signatures using the http package.
36+ func Example () {
23537 // Use HMAC key for deterministic signatures
23638 hmacKey := []byte (" shared-hmac-secret" )
23739
23840 // Create fixed clock for deterministic timestamps
23941 fixedTime := time.Date (2024 , 1 , 1 , 0 , 0 , 0 , 0 , time.UTC )
24042 clock := htmsighttp.FixedClock (fixedTime)
24143
242- // Create a response signer for outgoing responses
243- signer := htmsighttp.NewResponseSigner (hmacKey, " server-key" ,
44+ // Create response signer for outgoing responses
45+ responseSigner := htmsighttp.NewResponseSigner (hmacKey, " server-key" ,
24446 htmsighttp.WithSignerComponents (
24547 component.Status (),
24648 component.New (" content-type" ),
@@ -250,18 +52,18 @@ func ExampleNewClient() {
25052 // Create the application handler
25153 appHandler := http.HandlerFunc (func (w http.ResponseWriter , r *http.Request ) {
25254 w.Header ().Set (" Content-Type" , " application/json" )
253- _, _ = fmt.Fprint (w, ` {"status ": "success "}` )
55+ _, _ = fmt.Fprint (w, ` {"message ": "Hello, signed world! "}` )
25456 })
25557
256- // Wrap with just response signing for now (verification can be added separately)
58+ // Wrap handler with response signing
25759 wrappedHandler := htmsighttp.Wrap (appHandler,
258- htmsighttp.WithSigner (signer ))
60+ htmsighttp.WithSigner (responseSigner ))
25961
260- // Create test server with the wrapped handler
62+ // Create test server
26163 server := httptest.NewServer (wrappedHandler)
26264 defer server.Close ()
26365
264- // Create HTTP client with HMAC signing
66+ // Create HTTP client with automatic request signing
26567 client := htmsighttp.NewClient (
26668 hmacKey,
26769 " client-key" ,
@@ -273,42 +75,30 @@ func ExampleNewClient() {
27375 htmsighttp.WithClock (clock))
27476
27577 // Make a signed request to the server
276- resp , err := client.Get (server.URL + " /api/test" )
78+ resp , err := client.Post (server.URL +" /api/data" , " application/json" ,
79+ strings.NewReader (` {"data": "test"}` ))
27780 if err != nil {
27881 fmt.Printf (" Request failed: %v \n " , err)
27982 return
28083 }
28184 defer func () { _ = resp.Body .Close () }()
28285
283- // Only print if something went wrong
284- if resp.StatusCode != http.StatusOK {
285- fmt.Printf (" Unexpected status: %d \n " , resp.StatusCode )
286- return
287- }
288-
289- // Check the actual signature value
290- signature := resp.Header .Get (" Signature" )
291- expectedSignature := " sig=:yK6Vo4nyKwMLjsL9qPmXo87yb4FAuUT7ZFmo9duC8QY=:"
292- if signature != expectedSignature {
293- fmt.Printf (" Unexpected signature: %s \n " , signature)
294- return
295- }
296-
297- // Check the signature input value
298- signatureInput := resp.Header .Get (" Signature-Input" )
299- expectedSignatureInput := " sig=(\" @status\" \" content-type\" );created=1704067200;keyid=\" server-key\" "
300- if signatureInput != expectedSignatureInput {
301- fmt.Printf (" Unexpected signature input: %s \n " , signatureInput)
302- return
303- }
86+ fmt.Printf (" Response Status: %d \n " , resp.StatusCode )
87+ fmt.Printf (" Response has Signature: %t \n " , resp.Header .Get (" Signature" ) != " " )
88+ fmt.Printf (" Response has Signature-Input: %t \n " , resp.Header .Get (" Signature-Input" ) != " " )
30489
30590 // Output:
91+ // Response Status: 200
92+ // Response has Signature: true
93+ // Response has Signature-Input: true
30694}
30795
30896```
309- source: [ http/http_example_test .go] ( https://github.com/lestrrat-go/htmsig/blob/main/http/http_example_test .go )
97+ source: [ client_server_example_test .go] ( https://github.com/lestrrat-go/htmsig/blob/main/client_server_example_test .go )
31098<!-- END INCLUDE -->
31199
100+ For more detailed examples showing manual signing and verification using the core ` htmsig ` package, see [ manual_example_test.go] ( https://github.com/lestrrat-go/htmsig/blob/main/manual_example_test.go ) .
101+
312102## Supported Algorithms
313103
314104| Algorithm | RFC 9421 Name | Description |
0 commit comments