Skip to content

Commit 01a6d3e

Browse files
committed
Dictionary headers
1 parent 22a7956 commit 01a6d3e

File tree

7 files changed

+146
-87
lines changed

7 files changed

+146
-87
lines changed

fields.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ type field struct {
1616
}
1717

1818
func (f *field) String() string {
19+
if f.flagName == "" {
20+
return f.name
21+
}
1922
return fmt.Sprintf("%s;%s=\"%s\"", f.name, f.flagName, f.flagValue)
2023
}
2124

@@ -74,7 +77,13 @@ func (fs *Fields) AddQueryParam(qp string) *Fields {
7477
return fs
7578
}
7679

77-
// AddDictHeader indicates that a specific instance of a header is to be signed (TODO: unimplemented)
80+
func fromDictHeader(hdr, key string) *field {
81+
h := strings.ToLower(hdr)
82+
f := field{h, "key", key}
83+
return &f
84+
}
85+
86+
// AddDictHeader indicates that out of a header structured as a dictionary, a specific key value is signed/verified
7887
func (fs *Fields) AddDictHeader(hdr, key string) *Fields {
7988
k := strings.ToLower(key)
8089
return fs.addHeaderAndFlag(hdr, "key", k)

handler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
// WrapHandler wraps a server's HTTP request handler so that the incoming request is verified
1111
// and the response is signed. Both operations are optional. If config is nil, the default
1212
// configuration is applied: requests are verified and responses are signed.
13+
// Note: unlike the standard net.http behavior, if you want the "Content-Type" header to be signed,
14+
// you should specify it explicitly.
1315
func WrapHandler(h http.Handler, config *HandlerConfig) http.Handler {
1416
if config == nil {
1517
config = NewHandlerConfig()

handler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func Test_WrapHandler(t *testing.T) {
2727

2828
simpleHandler := func(w http.ResponseWriter, r *http.Request) {
2929
w.WriteHeader(200)
30-
w.Header().Set("bar", "baz")
30+
w.Header().Set("bar", "baz and baz again")
3131
fmt.Fprintln(w, "Hello, client")
3232
fmt.Fprintln(w, "Hello again")
3333
}

handlerex_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,14 @@ func ExampleWrapHandler_serverSigns() {
5757
fetchSigner := func(res http.Response, r *http.Request) (string, *httpsign.Signer) {
5858
sigName := "sig1"
5959
signer, _ := httpsign.NewHMACSHA256Signer("key", bytes.Repeat([]byte{0}, 64), nil,
60-
httpsign.HeaderList([]string{"@status", "bar", "date"}))
60+
httpsign.HeaderList([]string{"@status", "bar", "date", "content-type"}))
6161
return sigName, signer
6262
}
6363

6464
simpleHandler := func(w http.ResponseWriter, r *http.Request) { // this handler gets wrapped
6565
w.WriteHeader(200)
66-
w.Header().Set("bar", "baz")
66+
w.Header().Set("bar", "some text here") // note: a single word in the header value would be interpreted is a trivial dictionary!
67+
w.Header().Set("Content-Type", "text/plain")
6768
fmt.Fprintln(w, "Hello, client")
6869
}
6970

httpparse.go

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package httpsign
22

33
import (
44
"fmt"
5+
"github.com/dunglas/httpsfv"
56
"net/http"
67
"net/url"
78
"strconv"
@@ -40,7 +41,10 @@ func parseRequest(req *http.Request) (*parsedMessage, error) {
4041
}
4142
components := components{}
4243
generateReqSpecialtyComponents(req, components)
43-
generateHeaderComponents(req.Header, components)
44+
err = generateHeaderComponents(req.Header, components)
45+
if err != nil {
46+
return nil, err
47+
}
4448
values, err := url.ParseQuery(req.URL.RawQuery)
4549
if err != nil {
4650
return nil, fmt.Errorf("cannot parse query: %s", req.URL.RawQuery)
@@ -63,7 +67,10 @@ func parseResponse(res *http.Response) (*parsedMessage, error) {
6367
}
6468
components := components{}
6569
generateResSpecialtyComponents(res, components)
66-
generateHeaderComponents(res.Header, components)
70+
err = generateHeaderComponents(res.Header, components)
71+
if err != nil {
72+
return nil, err
73+
}
6774

6875
return &parsedMessage{components}, nil
6976
}
@@ -78,12 +85,35 @@ func validateMessageHeaders(header http.Header) error {
7885
return nil
7986
}
8087

81-
// TODO: dictionary headers
82-
func generateHeaderComponents(headers http.Header, components components) {
83-
for key, val := range headers {
84-
k := strings.ToLower(key)
85-
components[*fromHeaderName(k)] = []string{foldFields(val)}
88+
func generateHeaderComponents(headers http.Header, components components) error {
89+
for hdrName, val := range headers {
90+
lower := strings.ToLower(hdrName)
91+
dict, err := httpsfv.UnmarshalDictionary(val)
92+
if err == nil { // dictionary
93+
for _, name := range dict.Names() {
94+
v, _ := dict.Get(name)
95+
switch v.(type) {
96+
case httpsfv.Item:
97+
vv, err := httpsfv.Marshal(v.(httpsfv.Item))
98+
if err != nil {
99+
return fmt.Errorf("malformed dictionry member %s: %v", name, err)
100+
}
101+
components[*fromDictHeader(lower, name)] = []string{vv}
102+
case httpsfv.InnerList:
103+
vv, err := httpsfv.Marshal(v.(httpsfv.InnerList))
104+
if err != nil {
105+
return fmt.Errorf("malformed dictionry member %s: %v", name, err)
106+
}
107+
components[*fromDictHeader(lower, name)] = []string{vv}
108+
default:
109+
return fmt.Errorf("unexpected dictionary value")
110+
}
111+
}
112+
} else {
113+
components[*fromHeaderName(lower)] = []string{foldFields(val)}
114+
}
86115
}
116+
return nil
87117
}
88118

89119
func foldFields(fields []string) string {

signatures.go

Lines changed: 64 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -219,22 +219,28 @@ func GetRequestSignature(req *http.Request, signatureName string) (string, error
219219
if err != nil {
220220
return "", err
221221
}
222-
ws, found := parsedMessage.components[*fromHeaderName("signature")]
222+
ws, found := parsedMessage.components[*fromDictHeader("signature", signatureName)]
223223
if !found {
224-
return "", fmt.Errorf("missing \"signature\" header")
224+
return "", fmt.Errorf("missing \"signature\" header for \"%s\"", signatureName)
225+
}
226+
if len(ws) > 1 {
227+
return "", fmt.Errorf("more than one \"signature\" value for \"%s\"", signatureName)
225228
}
226229
sigHeader := ws[0]
227-
sigRaw, err := parseWantSignature(sigHeader, signatureName)
230+
sigRaw, err := parseWantSignature(sigHeader)
228231
if err != nil {
229232
return "", err
230233
}
231234
return encodeBytes(sigRaw), nil
232235
}
233236

234237
func messageKeyID(signatureName string, parsedMessage parsedMessage) (keyID, alg string, err error) {
235-
si, found := parsedMessage.components[*fromHeaderName("signature-input")]
238+
si, found := parsedMessage.components[*fromDictHeader("signature-input", signatureName)]
236239
if !found {
237-
return "", "", fmt.Errorf("missing \"signature-input\" header")
240+
return "", "", fmt.Errorf("missing \"signature-input\" header, or cannot find \"%s\"", signatureName)
241+
}
242+
if len(si) > 1 {
243+
return "", "", fmt.Errorf("more than one \"signature-input\" for %s", signatureName)
238244
}
239245
signatureInput := si[0]
240246
psi, err := parseSignatureInput(signatureInput, signatureName)
@@ -278,23 +284,29 @@ func VerifyResponse(signatureName string, verifier Verifier, res *http.Response)
278284
}
279285

280286
func verifyMessage(config VerifyConfig, name string, verifier Verifier, message parsedMessage, fields Fields) error {
281-
wsi, found := message.components[*fromHeaderName("signature-input")]
287+
wsi, found := message.components[*fromDictHeader("signature-input", name)]
282288
if !found {
283-
return fmt.Errorf("missing \"signature-input\" header")
289+
return fmt.Errorf("missing \"signature-input\" header, or cannot find signature \"%s\"", name)
290+
}
291+
if len(wsi) > 1 {
292+
return fmt.Errorf("multiple \"signature-header\" values for %s", name)
284293
}
285294
wantSignatureInput := wsi[0]
286-
ws, found := message.components[*fromHeaderName("signature")]
295+
ws, found := message.components[*fromDictHeader("signature", name)]
287296
if !found {
288297
return fmt.Errorf("missing \"signature\" header")
289298
}
299+
if len(ws) > 1 {
300+
return fmt.Errorf("multiple \"signature\" values for %s", name)
301+
}
290302
wantSignature := ws[0]
291-
delete(message.components, *fromHeaderName("signature-input"))
292-
delete(message.components, *fromHeaderName("signature"))
303+
delete(message.components, *fromDictHeader("signature-input", name))
304+
delete(message.components, *fromDictHeader("signature", name))
293305
err := validateFields(fields)
294306
if err != nil {
295307
return err
296308
}
297-
wantSigRaw, err := parseWantSignature(wantSignature, name)
309+
wantSigRaw, err := parseWantSignature(wantSignature)
298310
if err != nil {
299311
return err
300312
}
@@ -386,83 +398,59 @@ type psiSignature struct {
386398
params map[string]interface{}
387399
}
388400

389-
type parsedSignatureInput struct {
390-
signatures []psiSignature
391-
}
392-
393-
func parseSignatureInput(input string, name string) (*psiSignature, error) {
394-
psi := parsedSignatureInput{}
395-
sigs, err := httpsfv.UnmarshalDictionary([]string{input})
401+
func parseSignatureInput(input string, sigName string) (*psiSignature, error) {
402+
sigs, err := httpsfv.UnmarshalDictionary([]string{sigName + "=" + input}) // yes this is a hack, there is no UnmarshalInnerList
396403
if err != nil {
397-
return nil, fmt.Errorf("could not parse Signature-Input as list: %w", err)
404+
return nil, fmt.Errorf("could not parse Signature-Input as dictionary: %w", err)
398405
}
399-
for _, name := range sigs.Names() {
400-
memberForName, ok := sigs.Get(name)
401-
if !ok {
402-
return nil, fmt.Errorf("could not parse Signature-Input for signature %s", name)
403-
}
404-
fieldsList, ok := memberForName.(httpsfv.InnerList)
405-
osp, err := httpsfv.Marshal(fieldsList) // undocumented functionality
406-
if err != nil {
407-
return nil, fmt.Errorf("could not marshal inner list: %w", err)
408-
}
406+
memberForName, _ := sigs.Get(sigName)
407+
fieldsList, ok := memberForName.(httpsfv.InnerList)
408+
osp, err := httpsfv.Marshal(fieldsList) // undocumented functionality
409+
if err != nil {
410+
return nil, fmt.Errorf("could not marshal inner list: %w", err)
411+
}
412+
if !ok {
413+
return nil, fmt.Errorf("Signature-Input: signature %s does not have an inner list", sigName)
414+
}
415+
var f Fields
416+
for _, ff := range fieldsList.Items {
417+
fname, ok := ff.Value.(string)
409418
if !ok {
410-
return nil, fmt.Errorf("Signature-Input: signature %s does not have an inner list", name)
411-
}
412-
var f Fields
413-
for _, ff := range fieldsList.Items {
414-
fname, ok := ff.Value.(string)
415-
if !ok {
416-
return nil, fmt.Errorf("Signature-Input: value is not a string")
417-
}
418-
if ff.Params == nil || len(ff.Params.Names()) == 0 {
419-
f = append(f, *fromHeaderName(fname))
420-
} else {
421-
if len(ff.Params.Names()) > 1 {
422-
return nil, fmt.Errorf("more than one param for \"%s\"", fname)
423-
}
424-
flagNames := ff.Params.Names()
425-
flagName := flagNames[0]
426-
flagValue, _ := ff.Params.Get(flagName)
427-
fv := flagValue.(string)
428-
f = append(f, field{
429-
name: fname,
430-
flagName: flagName,
431-
flagValue: fv,
432-
})
433-
}
419+
return nil, fmt.Errorf("Signature-Input: value is not a string")
434420
}
435-
params := map[string]interface{}{}
436-
ps := fieldsList.Params
437-
for _, p := range (*ps).Names() {
438-
pp, ok := ps.Get(p)
439-
if !ok {
440-
return nil, fmt.Errorf("could not read param \"%s\"", p)
421+
if ff.Params == nil || len(ff.Params.Names()) == 0 {
422+
f = append(f, *fromHeaderName(fname))
423+
} else {
424+
if len(ff.Params.Names()) > 1 {
425+
return nil, fmt.Errorf("more than one param for \"%s\"", fname)
441426
}
442-
params[p] = pp
427+
flagNames := ff.Params.Names()
428+
flagName := flagNames[0]
429+
flagValue, _ := ff.Params.Get(flagName)
430+
fv := flagValue.(string)
431+
f = append(f, field{
432+
name: fname,
433+
flagName: flagName,
434+
flagValue: fv,
435+
})
443436
}
444-
psi.signatures = append(psi.signatures, psiSignature{name, osp, f, params})
445437
}
446-
for _, s := range psi.signatures {
447-
if s.signatureName == name {
448-
return &s, nil
438+
params := map[string]interface{}{}
439+
ps := fieldsList.Params
440+
for _, p := range (*ps).Names() {
441+
pp, ok := ps.Get(p)
442+
if !ok {
443+
return nil, fmt.Errorf("could not read param \"%s\"", p)
449444
}
445+
params[p] = pp
450446
}
451-
return nil, fmt.Errorf("couldn't find signature input for \"%s\"", name)
447+
return &psiSignature{sigName, osp, f, params}, nil
452448
}
453449

454-
func parseWantSignature(wantSignature string, name string) ([]byte, error) {
455-
parsedSignature, err := httpsfv.UnmarshalDictionary([]string{wantSignature})
450+
func parseWantSignature(wantSignature string) ([]byte, error) {
451+
wantSigItem, err := httpsfv.UnmarshalItem([]string{wantSignature})
456452
if err != nil {
457-
return nil, fmt.Errorf("could not parse signature field: %w", err)
458-
}
459-
wantSigValue, found := parsedSignature.Get(name)
460-
if !found {
461-
return nil, fmt.Errorf("could not find signature \"%s\"", name)
462-
}
463-
wantSigItem, ok := wantSigValue.(httpsfv.Item)
464-
if !ok {
465-
return nil, fmt.Errorf("unexpected value in signature field")
453+
return nil, fmt.Errorf("unexpected value in signature field: %s", err)
466454
}
467455
wantSigRaw, ok := wantSigItem.Value.([]byte)
468456
if !ok {

signatures_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,3 +998,32 @@ func TestRequestResponse(t *testing.T) {
998998
t.Errorf("Could not verify response: %v", err)
999999
}
10001000
}
1001+
1002+
func TestDictionary(t *testing.T) {
1003+
priv, pub, err := genP256KeyPair()
1004+
res := readResponse(httpres2)
1005+
res.Header.Set("X-Dictionary", "a=1, b=2;x=1;y=2, c=(a b c)")
1006+
signer2, err := NewP256Signer("key10", priv, NewSignConfig(),
1007+
*NewFields().AddHeader("@status").AddDictHeader("x-dictionary", "a"))
1008+
if err != nil {
1009+
t.Errorf("Could not create signer")
1010+
}
1011+
sigInput2, sig2, err := SignResponse("sig2", *signer2, res)
1012+
if err != nil {
1013+
t.Errorf("Could not sign response: %v", err)
1014+
}
1015+
res.Header.Add("Signature-Input", sigInput2)
1016+
res.Header.Add("Signature", sig2)
1017+
1018+
// Client verifies response
1019+
verifier2, err := NewP256Verifier("key10", pub, NewVerifyConfig().SetVerifyCreated(false),
1020+
*NewFields().AddHeader("@status").AddDictHeader("x-dictionary", "a"))
1021+
if err != nil {
1022+
t.Errorf("Could not create verifier: %v", err)
1023+
}
1024+
err = VerifyResponse("sig2", *verifier2, res)
1025+
if err != nil {
1026+
t.Errorf("Could not verify response: %v", err)
1027+
}
1028+
1029+
}

0 commit comments

Comments
 (0)