Skip to content

Commit 22a7956

Browse files
committed
Request-response feature
1 parent ebaebfc commit 22a7956

File tree

5 files changed

+196
-44
lines changed

5 files changed

+196
-44
lines changed

config.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import (
77
"time"
88
)
99

10-
// SignConfig contains additional configuration for the Signer.
10+
type requestResponse struct{ name, signature string }
11+
12+
// SignConfig contains additional configuration for the signer.
1113
type SignConfig struct {
1214
signAlg bool
1315
signCreated bool
1416
fakeCreated int64
1517
expires int64
1618
nonce string
17-
requestResponse struct{ name, signature string }
19+
requestResponse *requestResponse
1820
}
1921

2022
// NewSignConfig generates a default configuration.
@@ -64,18 +66,18 @@ func (c *SignConfig) SetNonce(nonce string) *SignConfig {
6466
// SetRequestResponse allows the server to indicate signature name and signature that
6567
// it had received from a client and include it in the signature input.
6668
func (c *SignConfig) SetRequestResponse(name, signature string) *SignConfig {
67-
// TODO RequestResponse
68-
c.requestResponse = struct{ name, signature string }{name, signature}
69+
c.requestResponse = &requestResponse{name, signature}
6970
return c
7071
}
7172

72-
// VerifyConfig contains additional configuration for the Verifier.
73+
// VerifyConfig contains additional configuration for the verifier.
7374
type VerifyConfig struct {
74-
verifyCreated bool
75-
notNewerThan time.Duration
76-
notOlderThan time.Duration
77-
allowedAlgs []string
78-
rejectExpired bool
75+
verifyCreated bool
76+
notNewerThan time.Duration
77+
notOlderThan time.Duration
78+
allowedAlgs []string
79+
rejectExpired bool
80+
requestResponse *requestResponse
7981
}
8082

8183
// SetNotNewerThan sets the window for messages that appear to be newer than the current time,
@@ -114,6 +116,17 @@ func (v *VerifyConfig) SetAllowedAlgs(allowedAlgs []string) *VerifyConfig {
114116
return v
115117
}
116118

119+
// SetRequestResponse allows the server to indicate signature name and signature that
120+
// it had received from a client and include it in the signature input. Here this is configured
121+
// on the client side when verifying the response.
122+
func (v *VerifyConfig) SetRequestResponse(name, signature string) *VerifyConfig {
123+
v.requestResponse = &requestResponse{
124+
name: name,
125+
signature: signature,
126+
}
127+
return v
128+
}
129+
117130
// NewVerifyConfig generates a default configuration.
118131
func NewVerifyConfig() *VerifyConfig {
119132
return &VerifyConfig{

crypto.go

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ func NewHMACSHA256Signer(keyID string, key []byte, config *SignConfig, fields Fi
3333
if config == nil {
3434
config = NewSignConfig()
3535
}
36+
if fields == nil {
37+
return nil, fmt.Errorf("fields must not be nil")
38+
}
3639
return &Signer{
3740
keyID: keyID,
3841
key: key,
@@ -54,6 +57,9 @@ func NewRSASigner(keyID string, key *rsa.PrivateKey, config *SignConfig, fields
5457
if config == nil {
5558
config = NewSignConfig()
5659
}
60+
if fields == nil {
61+
return nil, fmt.Errorf("fields must not be nil")
62+
}
5763
return &Signer{
5864
keyID: keyID,
5965
key: key,
@@ -75,6 +81,9 @@ func NewRSAPSSSigner(keyID string, key *rsa.PrivateKey, config *SignConfig, fiel
7581
if config == nil {
7682
config = NewSignConfig()
7783
}
84+
if fields == nil {
85+
return nil, fmt.Errorf("fields must not be nil")
86+
}
7887
return &Signer{
7988
keyID: keyID,
8089
key: key,
@@ -96,6 +105,9 @@ func NewP256Signer(keyID string, key *ecdsa.PrivateKey, config *SignConfig, fiel
96105
if config == nil {
97106
config = NewSignConfig()
98107
}
108+
if fields == nil {
109+
return nil, fmt.Errorf("fields must not be nil")
110+
}
99111
return &Signer{
100112
keyID: keyID,
101113
key: key,
@@ -135,11 +147,11 @@ func (s Signer) sign(buff []byte) ([]byte, error) {
135147

136148
// Verifier includes a cryptographic key (typically a public key) and configuration of what needs to be verified.
137149
type Verifier struct {
138-
keyID string
139-
key interface{}
140-
alg string
141-
c *VerifyConfig
142-
f Fields
150+
keyID string
151+
key interface{}
152+
alg string
153+
config *VerifyConfig
154+
fields Fields
143155
}
144156

145157
// NewHMACSHA256Verifier generates a new Verifier for HMAC-SHA256 signatures. Set config to nil for a default configuration.
@@ -155,11 +167,11 @@ func NewHMACSHA256Verifier(keyID string, key []byte, config *VerifyConfig, field
155167
config = NewVerifyConfig()
156168
}
157169
return &Verifier{
158-
keyID: keyID,
159-
key: key,
160-
alg: "hmac-sha256",
161-
c: config,
162-
f: fields,
170+
keyID: keyID,
171+
key: key,
172+
alg: "hmac-sha256",
173+
config: config,
174+
fields: fields,
163175
}, nil
164176
}
165177

@@ -172,12 +184,15 @@ func NewRSAVerifier(keyID string, key *rsa.PublicKey, config *VerifyConfig, fiel
172184
if config == nil {
173185
config = NewVerifyConfig()
174186
}
187+
if fields == nil {
188+
return nil, fmt.Errorf("fields must not be nil")
189+
}
175190
return &Verifier{
176-
keyID: keyID,
177-
key: key,
178-
alg: "rsa-v1_5-sha256",
179-
c: config,
180-
f: fields,
191+
keyID: keyID,
192+
key: key,
193+
alg: "rsa-v1_5-sha256",
194+
config: config,
195+
fields: fields,
181196
}, nil
182197
}
183198

@@ -190,12 +205,15 @@ func NewRSAPSSVerifier(keyID string, key *rsa.PublicKey, config *VerifyConfig, f
190205
if config == nil {
191206
config = NewVerifyConfig()
192207
}
208+
if fields == nil {
209+
return nil, fmt.Errorf("fields must not be nil")
210+
}
193211
return &Verifier{
194-
keyID: keyID,
195-
key: key,
196-
alg: "rsa-pss-sha512",
197-
c: config,
198-
f: fields,
212+
keyID: keyID,
213+
key: key,
214+
alg: "rsa-pss-sha512",
215+
config: config,
216+
fields: fields,
199217
}, nil
200218
}
201219

@@ -208,12 +226,15 @@ func NewP256Verifier(keyID string, key *ecdsa.PublicKey, config *VerifyConfig, f
208226
if config == nil {
209227
config = NewVerifyConfig()
210228
}
229+
if fields == nil {
230+
return nil, fmt.Errorf("fields must not be nil")
231+
}
211232
return &Verifier{
212-
keyID: keyID,
213-
key: key,
214-
alg: "ecdsa-p256-sha256",
215-
c: config,
216-
f: fields,
233+
keyID: keyID,
234+
key: key,
235+
alg: "ecdsa-p256-sha256",
236+
config: config,
237+
fields: fields,
217238
}, nil
218239
}
219240

fields.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package httpsign
22

33
import (
4+
"fmt"
45
"github.com/dunglas/httpsfv"
56
"strings"
67
)
@@ -14,6 +15,10 @@ type field struct {
1415
flagName, flagValue string
1516
}
1617

18+
func (f *field) String() string {
19+
return fmt.Sprintf("%s;%s=\"%s\"", f.name, f.flagName, f.flagValue)
20+
}
21+
1722
// HeaderList is a simple way to generate a Fields list, where only simple header names and specialty headers
1823
// are needed.
1924
func HeaderList(hs []string) Fields {

signatures.go

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ func generateSignature(name string, signer Signer, input string) (string, error)
5353
if err != nil {
5454
return "", err
5555
}
56-
return fmt.Sprintf("%s=:%s:", name, base64.StdEncoding.EncodeToString(raw)), nil
56+
return fmt.Sprintf("%s=%s", name, encodeBytes(raw)), nil
57+
}
58+
59+
func encodeBytes(raw []byte) string {
60+
return ":" + base64.StdEncoding.EncodeToString(raw) + ":"
5761
}
5862

5963
func generateSignatureInput(message parsedMessage, fields Fields, params string) (string, error) {
@@ -110,6 +114,9 @@ func SignRequest(signatureName string, signer Signer, req *http.Request) (signat
110114
if signatureName == "" {
111115
return "", "", fmt.Errorf("empty signature name")
112116
}
117+
if signer.config.requestResponse != nil {
118+
return "", "", fmt.Errorf("use request-response only to sign responses")
119+
}
113120
parsedMessage, err := parseRequest(req)
114121
if err != nil {
115122
return "", "", err
@@ -131,15 +138,22 @@ func SignResponse(signatureName string, signer Signer, res *http.Response) (sign
131138
if err != nil {
132139
return "", "", err
133140
}
134-
addPseudoHeaders(parsedMessage, *signer.config)
135-
return signMessage(*signer.config, signatureName, signer, *parsedMessage, signer.fields)
141+
extendedFields := addPseudoHeaders(parsedMessage, signer.config.requestResponse, signer.fields)
142+
return signMessage(*signer.config, signatureName, signer, *parsedMessage, extendedFields)
136143
}
137144

138-
func addPseudoHeaders(message *parsedMessage, config SignConfig) {
139-
if config.requestResponse.name != "" {
140-
message.components[*fromHeaderName("@request-response")] = []string{config.requestResponse.signature}
141-
// TODO and what about the name? (request-response)
145+
// Handle the special header-like @request-response
146+
func addPseudoHeaders(message *parsedMessage, rr *requestResponse, fields Fields) Fields {
147+
if rr != nil {
148+
rrfield := field{
149+
name: "@request-response",
150+
flagName: "key",
151+
flagValue: rr.name,
152+
}
153+
message.components[rrfield] = []string{rr.signature}
154+
return append(fields, rrfield)
142155
}
156+
return fields
143157
}
144158

145159
//
@@ -152,11 +166,14 @@ func VerifyRequest(signatureName string, verifier Verifier, req *http.Request) (
152166
if signatureName == "" {
153167
return fmt.Errorf("empty signature name")
154168
}
169+
if verifier.config.requestResponse != nil {
170+
return fmt.Errorf("use request-response only to verify responses")
171+
}
155172
parsedMessage, err := parseRequest(req)
156173
if err != nil {
157174
return err
158175
}
159-
return verifyMessage(*verifier.c, signatureName, verifier, *parsedMessage, verifier.f)
176+
return verifyMessage(*verifier.config, signatureName, verifier, *parsedMessage, verifier.fields)
160177
}
161178

162179
// RequestDetails parses a signed request and returns the key ID and optionally the algorithm used in the given signature.
@@ -189,6 +206,31 @@ func ResponseDetails(signatureName string, res *http.Response) (keyID, alg strin
189206
return messageKeyID(signatureName, *parsedMessage)
190207
}
191208

209+
// GetRequestSignature returns the base64-encoded signature, parsed from a signed request.
210+
// This is useful for the request-response feature.
211+
func GetRequestSignature(req *http.Request, signatureName string) (string, error) {
212+
if req == nil {
213+
return "", fmt.Errorf("nil request")
214+
}
215+
if signatureName == "" {
216+
return "", fmt.Errorf("empty signature name")
217+
}
218+
parsedMessage, err := parseRequest(req)
219+
if err != nil {
220+
return "", err
221+
}
222+
ws, found := parsedMessage.components[*fromHeaderName("signature")]
223+
if !found {
224+
return "", fmt.Errorf("missing \"signature\" header")
225+
}
226+
sigHeader := ws[0]
227+
sigRaw, err := parseWantSignature(sigHeader, signatureName)
228+
if err != nil {
229+
return "", err
230+
}
231+
return encodeBytes(sigRaw), nil
232+
}
233+
192234
func messageKeyID(signatureName string, parsedMessage parsedMessage) (keyID, alg string, err error) {
193235
si, found := parsedMessage.components[*fromHeaderName("signature-input")]
194236
if !found {
@@ -231,7 +273,8 @@ func VerifyResponse(signatureName string, verifier Verifier, res *http.Response)
231273
if err != nil {
232274
return err
233275
}
234-
return verifyMessage(*verifier.c, signatureName, verifier, *parsedMessage, verifier.f)
276+
extendedFields := addPseudoHeaders(parsedMessage, verifier.config.requestResponse, verifier.fields)
277+
return verifyMessage(*verifier.config, signatureName, verifier, *parsedMessage, extendedFields)
235278
}
236279

237280
func verifyMessage(config VerifyConfig, name string, verifier Verifier, message parsedMessage, fields Fields) error {

0 commit comments

Comments
 (0)