Add functions for handling Digest headers.

These are not yet integrated into the rest of the library.
このコミットが含まれているのは:
Cory Slep 2019-09-04 23:35:07 +02:00
コミット 1307faeb98
2個のファイルの変更245行の追加0行の削除

88
digest.go ノーマルファイル
ファイルの表示

@ -0,0 +1,88 @@
package httpsig
import (
"bytes"
"crypto"
"encoding/base64"
"fmt"
"hash"
"net/http"
"strings"
)
type DigestAlgorithm string
const (
DigestSha256 DigestAlgorithm = "SHA-256"
DigestSha512 = "SHA-512"
)
var digestToDef = map[DigestAlgorithm]crypto.Hash{
DigestSha256: crypto.SHA256,
DigestSha512: crypto.SHA512,
}
func getHash(alg DigestAlgorithm) (h hash.Hash, toUse DigestAlgorithm, err error) {
upper := DigestAlgorithm(strings.ToUpper(string(alg)))
c, ok := digestToDef[upper]
if !ok {
err = fmt.Errorf("unknown or unsupported Digest algorithm: %s", alg)
} else if !c.Available() {
err = fmt.Errorf("unavailable Digest algorithm: %s", alg)
} else {
h = c.New()
toUse = upper
}
return
}
const (
digestHeader = "Digest"
digestDelim = "="
)
func addDigest(r *http.Request, algo DigestAlgorithm, b []byte) (err error) {
_, ok := r.Header[digestHeader]
if ok {
err = fmt.Errorf("cannot add Digest: Digest is already set")
return
}
var h hash.Hash
var a DigestAlgorithm
h, a, err = getHash(algo)
if err != nil {
return
}
sum := h.Sum(b)
r.Header.Add(digestHeader,
fmt.Sprintf("%s%s%s",
a,
digestDelim,
base64.StdEncoding.EncodeToString(sum[:])))
return
}
func verifyDigest(r *http.Request, body *bytes.Buffer) (err error) {
d := r.Header.Get(digestHeader)
if len(d) == 0 {
err = fmt.Errorf("cannot verify Digest: request has no Digest header")
return
}
elem := strings.SplitN(d, digestDelim, 2)
if len(elem) != 2 {
err = fmt.Errorf("cannot verify Digest: malformed Digest: %s", d)
return
}
var h hash.Hash
h, _, err = getHash(DigestAlgorithm(elem[0]))
if err != nil {
return
}
sum := h.Sum(body.Bytes())
encSum := base64.StdEncoding.EncodeToString(sum[:])
if encSum != elem[1] {
err = fmt.Errorf("cannot verify Digest: header Digest does not match the digest of the request body")
return
}
return
}

157
digest_test.go ノーマルファイル
ファイルの表示

@ -0,0 +1,157 @@
package httpsig
import (
"bytes"
"net/http"
"testing"
)
func TestAddDigest(t *testing.T) {
tests := []struct {
name string
r func() *http.Request
algo DigestAlgorithm
body []byte
expectedDigest string
expectError bool
}{
{
name: "adds sha256 digest",
r: func() *http.Request {
r, _ :=http.NewRequest("POST", "example.com", nil)
return r
},
algo: "SHA-256",
body: []byte("johnny grab your gun"),
expectedDigest: "SHA-256=am9obm55IGdyYWIgeW91ciBndW7jsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ==",
},
{
name: "adds sha512 digest",
r: func() *http.Request {
r, _ := http.NewRequest("POST", "example.com", nil)
return r
},
algo: "SHA-512",
body: []byte("yours is the drill that will pierce the heavens"),
expectedDigest: "SHA-512=eW91cnMgaXMgdGhlIGRyaWxsIHRoYXQgd2lsbCBwaWVyY2UgdGhlIGhlYXZlbnPPg+E1fu+4vfFUKFDWbYAH1iDkBQtXFdyD9Kkh02zpzkfQ0TxdhfKw/4MY0od+7C9juTG9R0F6gaU4Mnr5J9o+",
},
{
name: "digest already set",
r: func() *http.Request {
r, _ := http.NewRequest("POST", "example.com", nil)
r.Header.Set("Digest", "oops")
return r
},
algo: "SHA-512",
body: []byte("did bob ewell fall on his knife"),
expectError: true,
},
{
name: "unknown/unsupported digest algorithm",
r: func() *http.Request {
r, _ := http.NewRequest("POST", "example.com", nil)
return r
},
algo: "MD5",
body: []byte("two times Cuchulainn almost drowned"),
expectError: true,
},
}
for _, test := range tests {
req := test.r()
err := addDigest(req, test.algo, test.body)
gotErr := err != nil
if gotErr != test.expectError {
if test.expectError {
t.Fatalf("%q: expected error, got: %s", test.name, err)
} else {
t.Fatalf("%q: expected no error, got: %s", test.name, err)
}
} else if !gotErr {
d := req.Header.Get("Digest")
if d != test.expectedDigest {
t.Fatalf("%q: unexpected digest: want %s, got %s", test.name, test.expectedDigest, d)
}
}
}
}
func TestVerifyDigest(t *testing.T) {
tests := []struct{
name string
r func() *http.Request
body []byte
expectError bool
}{
{
name: "verify sha256",
r: func() *http.Request {
r, _ := http.NewRequest("POST", "example.com", nil)
r.Header.Set("Digest", "SHA-256=am9obm55IGdyYWIgeW91ciBndW7jsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ==")
return r
},
body: []byte("johnny grab your gun"),
},
{
name: "verify sha512",
r: func() *http.Request {
r, _ := http.NewRequest("POST", "example.com", nil)
r.Header.Set("Digest", "SHA-512=eW91cnMgaXMgdGhlIGRyaWxsIHRoYXQgd2lsbCBwaWVyY2UgdGhlIGhlYXZlbnPPg+E1fu+4vfFUKFDWbYAH1iDkBQtXFdyD9Kkh02zpzkfQ0TxdhfKw/4MY0od+7C9juTG9R0F6gaU4Mnr5J9o+")
return r
},
body: []byte("yours is the drill that will pierce the heavens"),
},
{
name: "no digest header",
r: func() *http.Request {
r, _ := http.NewRequest("POST", "example.com", nil)
return r
},
body: []byte("Yuji's gender is blue"),
expectError: true,
},
{
name: "malformed digest",
r: func() *http.Request {
r, _ := http.NewRequest("POST", "example.com", nil)
r.Header.Set("Digest", "SHA-256am9obm55IGdyYWIgeW91ciBndW7jsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ==")
return r
},
body: []byte("Tochee and Ozzie BFFs forever"),
expectError: true,
},
{
name: "unsupported/unknown algo",
r: func() *http.Request {
r, _ := http.NewRequest("POST", "example.com", nil)
r.Header.Set("Digest", "MD5=poo")
return r
},
body: []byte("what is a man? a miserable pile of secrets"),
expectError: true,
},
{
name: "bad digest",
r: func() *http.Request {
r, _ := http.NewRequest("POST", "example.com", nil)
r.Header.Set("Digest", "SHA-256=bm9obm55IGdyYWIgeW91ciBndW7jsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ==")
return r
},
body: []byte("johnny grab your gun"),
expectError: true,
},
}
for _, test := range tests {
req := test.r()
buf := bytes.NewBuffer(test.body)
err := verifyDigest(req, buf)
gotErr := err != nil
if gotErr != test.expectError {
if test.expectError {
t.Fatalf("%q: expected error, got: %s", test.name, err)
} else {
t.Fatalf("%q: expected no error, got: %s", test.name, err)
}
}
}
}