Repository navigation
Expand file tree
/
Copy pathreader.go
More file actions
166 lines (149 loc) · 4.59 KB
/
Copy pathreader.go
File metadata and controls
166 lines (149 loc) · 4.59 KB
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package integrity
import (
"crypto/md5" //nolint:gosec // verification-only compatibility checksum
"crypto/sha1" //nolint:gosec // verification-only compatibility checksum
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"io"
)
type hashState struct {
algorithm Algorithm
hash hash.Hash
}
// Reader calculates requested digests as bytes pass through it. Bytes remain
// unverified until a completed Result passes Verify.
type Reader struct {
source io.Reader
hashes []hashState
bytes int64
complete bool
}
// Result is a snapshot of a Reader's calculated digests and progress.
type Result struct {
Digests []Digest
Bytes int64
Complete bool
}
// ErrIncomplete reports that verification was requested before the reader
// observed EOF.
type ErrIncomplete struct{}
// Error implements error.
func (*ErrIncomplete) Error() string {
return "integrity verification requires a complete stream"
}
// NewReader returns a reader that calculates each requested algorithm once.
func NewReader(source io.Reader, algorithms ...Algorithm) (*Reader, error) {
if source == nil {
return nil, fmt.Errorf("new integrity reader: nil source")
}
reader := &Reader{source: source}
seen := make(map[Algorithm]bool, len(algorithms))
for _, algorithm := range algorithms {
if seen[algorithm] {
continue
}
seen[algorithm] = true
var calculator hash.Hash
switch algorithm {
case SHA256:
calculator = sha256.New()
case SHA384:
calculator = sha512.New384()
case SHA512:
calculator = sha512.New()
case MD5:
calculator = md5.New() //nolint:gosec // verification-only compatibility checksum
case SHA1:
calculator = sha1.New() //nolint:gosec // verification-only compatibility checksum
default:
return nil, fmt.Errorf("new integrity reader: unsupported algorithm %q", algorithm)
}
reader.hashes = append(reader.hashes, hashState{algorithm: algorithm, hash: calculator})
}
return reader, nil
}
// Read passes bytes from the source through each requested digest calculator.
func (r *Reader) Read(p []byte) (int, error) {
if r.complete {
return 0, io.EOF
}
n, err := r.source.Read(p)
if n > 0 {
r.bytes += int64(n)
for i := range r.hashes {
_, _ = r.hashes[i].hash.Write(p[:n])
}
}
if err == io.EOF {
r.complete = true
}
return n, err
}
// Result returns a snapshot. Calling Result does not finish or reset the
// reader.
func (r *Reader) Result() Result {
result := Result{
Digests: make([]Digest, 0, len(r.hashes)),
Bytes: r.bytes,
Complete: r.complete,
}
for _, state := range r.hashes {
result.Digests = append(result.Digests, Digest{algorithm: state.algorithm, bytes: state.hash.Sum(nil)})
}
return result
}
// Verify applies the W3C SRI matching rule to a completed result. Only the
// strongest algorithm in expected is considered, and any digest using that
// algorithm may match. expected must hold only SRI algorithms; use
// VerifyDigest for an MD5 or SHA-1 checksum.
func (r Result) Verify(expected SRI) error {
if !r.Complete {
return &ErrIncomplete{}
}
algorithm, err := strongestAlgorithm(expected)
if err != nil {
return err
}
expectedStrongest := make(SRI, 0, len(expected))
for _, digest := range expected {
if digest.Algorithm() == algorithm {
expectedStrongest = append(expectedStrongest, digest)
}
}
for _, calculated := range r.Digests {
if calculated.Algorithm() != algorithm {
continue
}
for _, digest := range expectedStrongest {
if calculated.Equal(digest) {
return nil
}
}
return fmt.Errorf("integrity mismatch: expected %s, calculated %s", FormatSRI(expectedStrongest), calculated.SRI())
}
return fmt.Errorf("integrity mismatch: expected %s, calculated no %s digest", FormatSRI(expectedStrongest), algorithm)
}
// VerifyDigest checks a completed result against one exact digest, such as a
// registry checksum from ParseChecksum or ParseHex. It accepts every
// supported algorithm, MD5 and SHA-1 included, and requires that the reader
// calculated that algorithm.
func (r Result) VerifyDigest(expected Digest) error {
if !r.Complete {
return &ErrIncomplete{}
}
if _, err := digestSize(expected.Algorithm()); err != nil || len(expected.bytes) == 0 {
return fmt.Errorf("verify digest: no expected digest")
}
for _, calculated := range r.Digests {
if calculated.Algorithm() != expected.Algorithm() {
continue
}
if calculated.Equal(expected) {
return nil
}
return fmt.Errorf("integrity mismatch: expected %s, calculated %s", expected.Checksum(), calculated.Checksum())
}
return fmt.Errorf("integrity mismatch: expected %s, calculated no %s digest", expected.Checksum(), expected.Algorithm())
}