Skip to content

Commit 4ca1301

Browse files
committed
disallow upper characters (/A-F/) in hex-encoded portion
Signed-off-by: Akihiro Suda <[email protected]>
1 parent eaa6054 commit 4ca1301

File tree

3 files changed

+39
-17
lines changed

3 files changed

+39
-17
lines changed

algorithm.go

+29-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"fmt"
2020
"hash"
2121
"io"
22+
"regexp"
2223
)
2324

2425
// Algorithm identifies and implementation of a digester by an identifier.
@@ -28,9 +29,9 @@ type Algorithm string
2829

2930
// supported digest types
3031
const (
31-
SHA256 Algorithm = "sha256" // sha256 with hex encoding
32-
SHA384 Algorithm = "sha384" // sha384 with hex encoding
33-
SHA512 Algorithm = "sha512" // sha512 with hex encoding
32+
SHA256 Algorithm = "sha256" // sha256 with hex encoding (lower case only)
33+
SHA384 Algorithm = "sha384" // sha384 with hex encoding (lower case only)
34+
SHA512 Algorithm = "sha512" // sha512 with hex encoding (lower case only)
3435

3536
// Canonical is the primary digest algorithm used with the distribution
3637
// project. Other digests may be used but this one is the primary storage
@@ -50,6 +51,14 @@ var (
5051
SHA384: crypto.SHA384,
5152
SHA512: crypto.SHA512,
5253
}
54+
55+
// anchoredEncodedRegexps contains anchored regular expressions for hex-encoded digests.
56+
// Note that /A-F/ disallowed.
57+
anchoredEncodedRegexps = map[Algorithm]*regexp.Regexp{
58+
SHA256: regexp.MustCompile(`^[a-f0-9]{64}$`),
59+
SHA384: regexp.MustCompile(`^[a-f0-9]{96}$`),
60+
SHA512: regexp.MustCompile(`^[a-f0-9]{128}$`),
61+
}
5362
)
5463

5564
// Available returns true if the digest type is available for use. If this
@@ -164,3 +173,20 @@ func (a Algorithm) FromBytes(p []byte) Digest {
164173
func (a Algorithm) FromString(s string) Digest {
165174
return a.FromBytes([]byte(s))
166175
}
176+
177+
// Validate validates the encoded portion string
178+
func (a Algorithm) Validate(encoded string) error {
179+
r, ok := anchoredEncodedRegexps[a]
180+
if !ok {
181+
return ErrDigestUnsupported
182+
}
183+
// Digests much always be hex-encoded, ensuring that their hex portion will
184+
// always be size*2
185+
if a.Size()*2 != len(encoded) {
186+
return ErrDigestInvalidLength
187+
}
188+
if r.MatchString(encoded) {
189+
return nil
190+
}
191+
return ErrDigestInvalidFormat
192+
}

digest.go

+6-14
Original file line numberDiff line numberDiff line change
@@ -101,26 +101,18 @@ func FromString(s string) Digest {
101101
// error if not.
102102
func (d Digest) Validate() error {
103103
s := string(d)
104-
105104
i := strings.Index(s, ":")
106-
107-
// validate i then run through regexp
108-
if i < 0 || i+1 == len(s) || !DigestRegexpAnchored.MatchString(s) {
105+
if i <= 0 || i+1 == len(s) {
109106
return ErrDigestInvalidFormat
110107
}
111-
112-
algorithm := Algorithm(s[:i])
108+
algorithm, encoded := Algorithm(s[:i]), s[i+1:]
113109
if !algorithm.Available() {
110+
if !DigestRegexpAnchored.MatchString(s) {
111+
return ErrDigestInvalidFormat
112+
}
114113
return ErrDigestUnsupported
115114
}
116-
117-
// Digests much always be hex-encoded, ensuring that their hex portion will
118-
// always be size*2
119-
if algorithm.Size()*2 != len(s[i+1:]) {
120-
return ErrDigestInvalidLength
121-
}
122-
123-
return nil
115+
return algorithm.Validate(encoded)
124116
}
125117

126118
// Algorithm returns the algorithm portion of the digest. This will panic if

digest_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ func TestParseDigest(t *testing.T) {
9393
encoded: "LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564",
9494
err: ErrDigestUnsupported,
9595
},
96+
{
97+
input: "sha256:E58FCF7418D4390DEC8E8FB69D88C06EC07039D651FEDD3AA72AF9972E7D046B",
98+
err: ErrDigestInvalidFormat,
99+
},
96100
} {
97101
digest, err := Parse(testcase.input)
98102
if err != testcase.err {

0 commit comments

Comments
 (0)