Skip to content

Commit

Permalink
Merge pull request #3 from gssbzn/add-sha-256
Browse files Browse the repository at this point in the history
feat: add sha-256 support
  • Loading branch information
gssbzn authored Jul 16, 2020
2 parents 9e11ceb + 67f5433 commit 5cfb3b7
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 51 deletions.
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin/
dist/
# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# We don't want to commit the 3rd party notices
third_party_notices/
42 changes: 30 additions & 12 deletions digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ import (
"bytes"
"crypto/md5"
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
"hash"
"io"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -139,24 +141,27 @@ type credentials struct {
NonceCount int
method string
password string
impl hashingFunc
}

func h(data string) string {
hf := md5.New()
type hashingFunc func() hash.Hash

func h(data string, f hashingFunc) string {
hf := f()
io.WriteString(hf, data)
return fmt.Sprintf("%x", hf.Sum(nil))
}

func kd(secret, data string) string {
return h(fmt.Sprintf("%s:%s", secret, data))
func kd(secret, data string, f hashingFunc) string {
return h(fmt.Sprintf("%s:%s", secret, data), f)
}

func (c *credentials) ha1() string {
return h(fmt.Sprintf("%s:%s:%s", c.Username, c.Realm, c.password))
return h(fmt.Sprintf("%s:%s:%s", c.Username, c.Realm, c.password), c.impl)
}

func (c *credentials) ha2() string {
return h(fmt.Sprintf("%s:%s", c.method, c.DigestURI))
return h(fmt.Sprintf("%s:%s", c.method, c.DigestURI), c.impl)
}

func (c *credentials) resp(cnonce string) (string, error) {
Expand All @@ -170,17 +175,17 @@ func (c *credentials) resp(cnonce string) (string, error) {
c.Cnonce = fmt.Sprintf("%x", b)[:16]
}
return kd(c.ha1(), fmt.Sprintf("%s:%08x:%s:%s:%s",
c.Nonce, c.NonceCount, c.Cnonce, c.MessageQop, c.ha2())), nil
c.Nonce, c.NonceCount, c.Cnonce, c.MessageQop, c.ha2()), c.impl), nil
} else if c.MessageQop == "" {
return kd(c.ha1(), fmt.Sprintf("%s:%s", c.Nonce, c.ha2())), nil
return kd(c.ha1(), fmt.Sprintf("%s:%s", c.Nonce, c.ha2()), c.impl), nil
}
return "", ErrAlgNotImplemented
}

func (c *credentials) authorize() (string, error) {
// Note that this is only implemented for MD5 and NOT MD5-sess.
// MD5-sess is rarely supported and those that do are a big mess.
if c.Algorithm != "MD5" {
if c.Algorithm != "MD5" && c.Algorithm != "SHA-256" {
return "", ErrAlgNotImplemented
}
// Note that this is NOT implemented for "qop=auth-int". Similarly the
Expand Down Expand Up @@ -211,8 +216,8 @@ func (c *credentials) authorize() (string, error) {
return fmt.Sprintf("Digest %s", strings.Join(sl, ", ")), nil
}

func (t *Transport) newCredentials(req *http.Request, c *challenge) *credentials {
return &credentials{
func (t *Transport) newCredentials(req *http.Request, c *challenge) (*credentials, error) {
cred := &credentials{
Username: t.Username,
Realm: c.Realm,
Nonce: c.Nonce,
Expand All @@ -224,6 +229,16 @@ func (t *Transport) newCredentials(req *http.Request, c *challenge) *credentials
method: req.Method,
password: t.Password,
}
switch c.Algorithm {
case "MD5":
cred.impl = md5.New
case "SHA-256":
cred.impl = sha256.New
default:
return nil, ErrAlgNotImplemented
}

return cred, nil
}

// RoundTrip makes a request expecting a 401 response that will require digest
Expand Down Expand Up @@ -272,7 +287,10 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
}

// Form credentials based on the challenge.
cr := t.newCredentials(req2, c)
cr, err := t.newCredentials(req2, c)
if err != nil {
return nil, err
}
auth, err := cr.authorize()
if err != nil {
return nil, err
Expand Down
180 changes: 141 additions & 39 deletions digest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,69 +22,171 @@
package digest

import (
"crypto/md5"
"crypto/sha256"
"fmt"
"testing"
)

var cred = &credentials{
Username: "Mufasa",
Realm: "[email protected]",
Nonce: "dcd98b7102dd2f0e8b11d0f600bfb0c093",
DigestURI: "/dir/index.html",
Algorithm: "MD5",
Opaque: "5ccc069c403ebaf9f0171e9517f40e41",
MessageQop: "auth",
method: "GET",
password: "Circle Of Life",
}

var cnonce = "0a4f113b"

func TestH(t *testing.T) {
r1 := h("Mufasa:[email protected]:Circle Of Life")
if r1 != "939e7578ed9e3c518a452acee763bce9" {
t.Fail()
}

r2 := h("GET:/dir/index.html")
if r2 != "39aff3a2bab6126f332b942af96d3366" {
t.Fail()
testCases := map[string]map[string]string{
"MD5": {
"r1": "Mufasa:[email protected]:Circle Of Life",
"r2": "GET:/dir/index.html",
"expected_r1": "939e7578ed9e3c518a452acee763bce9",
"expected_r2": "39aff3a2bab6126f332b942af96d3366",
"expected_r3": "6629fae49393a05397450978507c4ef1",
},
"SHA-256": {
"r1": "Mufasa:[email protected]:Circle Of Life",
"r2": "GET:/dir/index.html",
"expected_r1": "3ba6cd94661c5ef34598040c868f13b8775df29109986be50ad35ae537dd3aa4",
"expected_r2": "9a3fdae9a622fe8de177c24fa9c070f2b181ec85e15dcbdc32e10c82ad450b04",
"expected_r3": "5abdd07184ba512a22c53f41470e5eea7dcaa3a93a59b630c13dfe0a5dc6e38b",
},
}

r3 := h(fmt.Sprintf("%s:dcd98b7102dd2f0e8b11d0f600bfb0c093:00000001:0a4f113b:auth:%s", r1, r2))
if r3 != "6629fae49393a05397450978507c4ef1" {
t.Fail()
for testType, values := range testCases {
t.Run(testType, func(t *testing.T) {
hashingFunc := testHashingFunc(testType)
r1 := h(values["r1"], hashingFunc)
if r1 != values["expected_r1"] {
t.Errorf("expected=%s, but got=%s\n", values["expected_r1"], r1)
}
r2 := h(values["r2"], hashingFunc)
if r2 != values["expected_r2"] {
t.Errorf("expected=%s, but got=%s\n", values["expected_r2"], r2)
}
r3 := h(fmt.Sprintf("%s:dcd98b7102dd2f0e8b11d0f600bfb0c093:00000001:0a4f113b:auth:%s", r1, r2), hashingFunc)
if r3 != values["expected_r3"] {
t.Errorf("expected=%s, but got=%s\n", values["expected_r3"], r3)
}
})
}
}

func TestKd(t *testing.T) {
r1 := kd("939e7578ed9e3c518a452acee763bce9",
"dcd98b7102dd2f0e8b11d0f600bfb0c093:00000001:0a4f113b:auth:39aff3a2bab6126f332b942af96d3366")
if r1 != "6629fae49393a05397450978507c4ef1" {
t.Fail()
testCases := map[string]map[string]string{
"MD5": {
"secret": "939e7578ed9e3c518a452acee763bce9",
"data": "dcd98b7102dd2f0e8b11d0f600bfb0c093:00000001:0a4f113b:auth:39aff3a2bab6126f332b942af96d3366",
"expected": "6629fae49393a05397450978507c4ef1",
},
"SHA-256": {
"secret": "939e7578ed9e3c518a452acee763bce9",
"data": "dcd98b7102dd2f0e8b11d0f600bfb0c093:00000001:0a4f113b:auth:39aff3a2bab6126f332b942af96d3366",
"expected": "ca165e8478c14bd2a5c64cc86ffe17c277ee2cff3e98c330ee5565e8e206ca3e",
},
}
for testType, values := range testCases {
t.Run(testType, func(t *testing.T) {
hashingFunc := testHashingFunc(testType)
if r1 := kd(values["secret"], values["data"], hashingFunc); r1 != values["expected"] {
t.Errorf("expected=%s, but got=%s\n", values["expected"], r1)
}
})
}
}

func TestHa1(t *testing.T) {
r1 := cred.ha1()
if r1 != "939e7578ed9e3c518a452acee763bce9" {
t.Fail()
testCases := map[string]map[string]string{
"MD5": {
"expected": "939e7578ed9e3c518a452acee763bce9",
},
"SHA-256": {
"expected": "3ba6cd94661c5ef34598040c868f13b8775df29109986be50ad35ae537dd3aa4",
},
}
for testType, values := range testCases {
t.Run(testType, func(t *testing.T) {
hashingFunc := testHashingFunc(testType)
cred := &credentials{
Username: "Mufasa",
Realm: "[email protected]",
Nonce: "dcd98b7102dd2f0e8b11d0f600bfb0c093",
DigestURI: "/dir/index.html",
Algorithm: testType,
Opaque: "5ccc069c403ebaf9f0171e9517f40e41",
MessageQop: "auth",
method: "GET",
password: "Circle Of Life",
impl: hashingFunc,
}
if r1 := cred.ha1(); r1 != values["expected"] {
t.Errorf("expected=%s, but got=%s\n", values["expected"], r1)
}
})
}
}

func TestHa2(t *testing.T) {
r1 := cred.ha2()
if r1 != "39aff3a2bab6126f332b942af96d3366" {
t.Fail()
testCases := map[string]map[string]string{
"MD5": {
"expected": "39aff3a2bab6126f332b942af96d3366",
},
"SHA-256": {
"expected": "9a3fdae9a622fe8de177c24fa9c070f2b181ec85e15dcbdc32e10c82ad450b04",
},
}
for testType, values := range testCases {
t.Run(testType, func(t *testing.T) {
hashingFunc := testHashingFunc(testType)
cred := &credentials{
Username: "Mufasa",
Realm: "[email protected]",
Nonce: "dcd98b7102dd2f0e8b11d0f600bfb0c093",
DigestURI: "/dir/index.html",
Algorithm: testType,
Opaque: "5ccc069c403ebaf9f0171e9517f40e41",
MessageQop: "auth",
method: "GET",
password: "Circle Of Life",
impl: hashingFunc,
}
if r1 := cred.ha2(); r1 != values["expected"] {
t.Errorf("expected=%s, but got=%s\n", values["expected"], r1)
}
})
}
}

func TestResp(t *testing.T) {
r1, err := cred.resp(cnonce)
if err != nil {
t.Fail()
testCases := map[string]map[string]string{
"MD5": {
"expected": "6629fae49393a05397450978507c4ef1",
},
"SHA-256": {
"expected": "5abdd07184ba512a22c53f41470e5eea7dcaa3a93a59b630c13dfe0a5dc6e38b",
},
}
for testType, values := range testCases {
t.Run(testType, func(t *testing.T) {
hashingFunc := testHashingFunc(testType)
cred := &credentials{
Username: "Mufasa",
Realm: "[email protected]",
Nonce: "dcd98b7102dd2f0e8b11d0f600bfb0c093",
DigestURI: "/dir/index.html",
Algorithm: testType,
Opaque: "5ccc069c403ebaf9f0171e9517f40e41",
MessageQop: "auth",
method: "GET",
password: "Circle Of Life",
impl: hashingFunc,
}
if r1, err := cred.resp(cnonce); err != nil || r1 != values["expected"] {
t.Errorf("expected=%s, but got=%s\n", values["expected"], r1)
}
})
}
if r1 != "6629fae49393a05397450978507c4ef1" {
t.Fail()
}

func testHashingFunc(testType string) hashingFunc {
if testType == "MD5" {
return md5.New
} else if testType == "SHA-256" {
return sha256.New
}
return nil
}

0 comments on commit 5cfb3b7

Please sign in to comment.