-
Notifications
You must be signed in to change notification settings - Fork 2
/
digest.go
144 lines (134 loc) · 3.85 KB
/
digest.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package httpsign
import (
"bytes"
"crypto/sha256"
"crypto/sha512"
"errors"
"fmt"
"github.com/dunglas/httpsfv"
"io"
)
// Constants define the hash algorithm to be used for the digest
const (
DigestSha256 = "sha-256"
DigestSha512 = "sha-512"
)
// GenerateContentDigestHeader generates a digest of the message body according to the given scheme(s)
// (currently supporting DigestSha256 and DigestSha512).
// Side effect: the message body is fully read, and replaced by a static buffer
// containing the body contents.
func GenerateContentDigestHeader(body *io.ReadCloser, schemes []string) (string, error) {
if len(schemes) == 0 {
return "", fmt.Errorf("received empty list of digest schemes")
}
err := validateSchemes(schemes)
if err != nil {
return "", err
}
buff, err := duplicateBody(body)
if err != nil {
return "", err
}
dict := httpsfv.NewDictionary()
for _, scheme := range schemes {
raw, err := rawDigest(buff.String(), scheme)
if err != nil { // When sending, must recognize all schemes
return "", err
}
i := httpsfv.NewItem(raw)
dict.Add(scheme, httpsfv.Member(i))
}
return httpsfv.Marshal(dict)
}
// Note side effect: the value of body is replaced
func duplicateBody(body *io.ReadCloser) (*bytes.Buffer, error) {
buff := &bytes.Buffer{}
if body != nil && *body != nil {
_, err := buff.ReadFrom(*body)
if err != nil {
return nil, err
}
_ = (*body).Close()
*body = io.NopCloser(bytes.NewReader(buff.Bytes()))
}
return buff, nil
}
var errUnknownDigestScheme = fmt.Errorf("unknown digest scheme")
func rawDigest(s string, scheme string) ([]byte, error) {
switch scheme {
case DigestSha256:
s := sha256.Sum256([]byte(s))
return s[:], nil
case DigestSha512:
s := sha512.Sum512([]byte(s))
return s[:], nil
default:
return nil, errUnknownDigestScheme
}
}
func validateSchemes(schemes []string) error {
valid := map[string]bool{DigestSha256: true, DigestSha512: true}
for _, s := range schemes {
if !valid[s] {
return fmt.Errorf("invalid scheme %s", s)
}
}
return nil
}
// ValidateContentDigestHeader validates that the Content-Digest header complies to policy: at least
// one of the "accepted" schemes is used, and all known schemes are associated with a correct
// digest of the message body. Schemes are constants defined in this file, e.g. DigestSha256.
// Note that "received" is a string array, typically retrieved through the
// "Values" method of the header. Returns nil if validation is successful.
func ValidateContentDigestHeader(received []string, body *io.ReadCloser, accepted []string) error {
if len(accepted) == 0 {
return fmt.Errorf("received an empty list of acceptable digest schemes")
}
err := validateSchemes(accepted)
if err != nil {
return err
}
receivedDict, err := httpsfv.UnmarshalDictionary(received)
if err != nil {
return fmt.Errorf("received Content-Digest header: %w", err)
}
buff, err := duplicateBody(body)
if err != nil {
return err
}
var ok bool
found:
for _, a := range accepted {
for _, r := range receivedDict.Names() {
if a == r {
ok = true
break found
}
}
}
if !ok {
return fmt.Errorf("no acceptable digest scheme found in Content-Digest header")
}
// But regardless of the list of accepted schemes, all included digest values (if recognized) must be correct
for _, scheme := range receivedDict.Names() {
raw, err := rawDigest(buff.String(), scheme)
if errors.Is(err, errUnknownDigestScheme) {
continue // unknown schemes are ignored
} else if err != nil {
return err
}
m, _ := receivedDict.Get(scheme)
i, ok := m.(httpsfv.Item)
if !ok {
return fmt.Errorf("received Content-Digest header is malformed")
}
b, ok := i.Value.([]byte)
if !ok {
return fmt.Errorf("non-byte string in received Content-Digest header")
}
if !bytes.Equal(raw, b) {
return fmt.Errorf("digest mismatch for scheme %s", scheme)
}
}
return nil
}