Skip to content

Commit

Permalink
Merge pull request #4 from MihaiBojin/master
Browse files Browse the repository at this point in the history
  • Loading branch information
Mihai Bojin authored Jul 16, 2020
2 parents 5cfb3b7 + b75b068 commit aa4346a
Show file tree
Hide file tree
Showing 11 changed files with 859 additions and 47 deletions.
1 change: 1 addition & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
make all
88 changes: 88 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
linters-settings:
goconst:
min-len: 2
min-occurrences: 2
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
disabled-checks:
- octalLiteral
gocyclo:
min-complexity: 15
golint:
min-confidence: 0
govet:
check-shadowing: true
maligned:
suggest-new: true
misspell:
locale: US
linters:
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- errcheck
- exportloopref
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- golint
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- misspell
- nakedret
- nolintlint
- rowserrcheck
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace

# don't enable:
# - gochecknoinits
# - funlen
# - lll
# - gomnd
# - asciicheck
# - gochecknoglobals
# - gocognit
# - godot
# - godox
# - goerr113
# - maligned
# - nestif
# - prealloc
# - testpackage
# - wsl

run:
tests: true
build-tags:
- e2e
skip-dirs:
- internal/mocks

issues:
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
- linters:
- gosec
text: "G501:"
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
golang 1.14
3 changes: 3 additions & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@
Bipasa Chattopadhyay <[email protected]>
Eric Gavaletz <[email protected]>
Seon-Wook Park <[email protected]>
Eric Daniels <[email protected]>
Gustavo Bazan <[email protected]>
Mihai Bojin <[email protected]>
52 changes: 52 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.PHONY: fmt
fmt: ## Format the code
@echo "==>"
@echo "==> Formatting the code..."
@gofmt -w -s .
@goimports -w .

@echo "==>"
@echo "==> Running go mod tidy..."
@go mod tidy


.PHONY: lint
lint: ## Lint the code
@echo "==>"
@echo "==> Linting all packages..."
@go run github.com/golangci/golangci-lint/cmd/golangci-lint run

.PHONY: fix-lint
fix-lint: ## Fix linting errors
@echo "==>"
@echo "==> Fixing lint errors"
@go run github.com/golangci/golangci-lint/cmd/golangci-lint --fix

.PHONY: build
build: ## Build the library
@echo "==>"
@echo "==> Building the code"
@go build ./...

.PHONY: test
test: ## Run the tests
@echo "==>"
@echo "==> Running tests"
@go test ./...

.PHONY: all
all: fmt lint build test ## Run all targets

.PHONY: link-git-hooks
link-git-hooks: ## Install git hooks
@echo "==>"
@echo "==> Installing all git hooks..."
@find .git/hooks -type l -exec rm {} \;
@find .githooks -type f -exec ln -sf ../../{} .git/hooks/ \;

.PHONY: help
.DEFAULT_GOAL := help
help:
@echo
@echo "Makefile targets:"
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![GoDoc](https://godoc.org/github.com/bobziuchkovski/digest?status.svg)](https://godoc.org/github.com/bobziuchkovski/digest)
[![GoDoc](https://godoc.org/github.com/mongodb-forks/digest?status.svg)](https://godoc.org/github.com/mongodb-forks/digest)

# Golang HTTP Digest Authentication

Expand All @@ -8,6 +8,12 @@ This is a fork of the (unmaintained) code.google.com/p/mlab-ns2/gae/ns/digest pa
There's a descriptor leak in the original package, so this fork was created to patch
the leak.

### Update 2020

This is a fork of the now unmaintained fork of [digest](https://github.com/bobziuchkovski/digest).
This implementation now supports the SHA-256 algorithm which was added as part of [rfc 7616](https://tools.ietf.org/html/rfc7616).


## Usage

See the [godocs](https://godoc.org/github.com/bobziuchkovski/digest) for details.
Expand Down
116 changes: 84 additions & 32 deletions digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
// limitations under the License.

// The digest package provides an implementation of http.RoundTripper that takes
// care of HTTP Digest Authentication (http://www.ietf.org/rfc/rfc2617.txt).
// This only implements the MD5 and "auth" portions of the RFC, but that covers
// the majority of avalible server side implementations including apache web
// server.
// care of HTTP Digest Authentication (https://tools.ietf.org/html/rfc7616).

// This only implements the MD5, SHA-256 and "auth" portions of the RFC, which
// is enough to authenticate the client to the majority of available
// server side implementations using Digest Access Authentication (e.g.,
// the Apache Web Server).
//
//
// Example usage:
//
Expand All @@ -41,6 +44,31 @@
// return err
// }
//
// OR if you want fine-grained control over timeouts
//
// t := &digest.Transport{Username: "myUserName", Password: "myP@55w0rd"}
// t.Transport = &http.Transport{
// DialContext: (&net.Dialer{
// Timeout: 30 * time.Second,
// KeepAlive: 10 * time.Second,
// }).DialContext,
// ExpectContinueTimeout: 10 * time.Second,
// IdleConnTimeout: 60 * time.Second,
// MaxIdleConns: 100,
// MaxIdleConnsPerHost: 4,
// Proxy: http.ProxyFromEnvironment,
// ResponseHeaderTimeout: 30 * time.Second,
// TLSHandshakeTimeout: 10 * time.Second,
// }
// c, err := t.Client()
// if err != nil {
// return nil, err
// }
// resp, err := c.Get("http://notreal.com/path?arg=1")
// if err != nil {
// return nil, err
// }

package digest

import (
Expand All @@ -57,10 +85,16 @@ import (
"strings"
)

const (
MsgAuth string = "auth"
AlgMD5 string = "MD5"
AlgSha256 string = "SHA-256"
)

var (
ErrNilTransport = errors.New("Transport is nil")
ErrBadChallenge = errors.New("Challenge is bad")
ErrAlgNotImplemented = errors.New("Alg not implemented")
ErrNilTransport = errors.New("transport is nil")
ErrBadChallenge = errors.New("challenge is bad")
ErrAlgNotImplemented = errors.New("alg not implemented")
)

// Transport is an implementation of http.RoundTripper that takes care of http
Expand Down Expand Up @@ -101,7 +135,7 @@ func parseChallenge(input string) (*challenge, error) {
s = strings.Trim(s[7:], ws)
sl := strings.Split(s, ", ")
c := &challenge{
Algorithm: "MD5",
Algorithm: AlgMD5,
}
var r []string
for i := range sl {
Expand All @@ -120,7 +154,7 @@ func parseChallenge(input string) (*challenge, error) {
case "algorithm":
c.Algorithm = strings.Trim(r[1], qs)
case "qop":
//TODO(gavaletz) should be an array of strings?
// TODO(gavaletz) should be an array of strings?
c.Qop = strings.Trim(r[1], qs)
default:
return nil, ErrBadChallenge
Expand All @@ -146,72 +180,90 @@ type credentials struct {

type hashingFunc func() hash.Hash

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

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

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

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

func (c *credentials) resp(cnonce string) (string, error) {
func (c *credentials) resp(cnonce string) (resp string, err error) {
var ha1 string
var ha2 string
c.NonceCount++
if c.MessageQop == "auth" {
if c.MessageQop == MsgAuth {
if cnonce != "" {
c.Cnonce = cnonce
} else {
b := make([]byte, 8)
io.ReadFull(rand.Reader, b)
_, err = io.ReadFull(rand.Reader, b)
if err != nil {
return "", err
}
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()), c.impl), nil
if ha1, err = c.ha1(); err != nil {
return "", err
}
if ha2, err = c.ha2(); err != nil {
return "", err
}
return kd(ha1, fmt.Sprintf("%s:%08x:%s:%s:%s", c.Nonce, c.NonceCount, c.Cnonce, c.MessageQop, ha2), c.impl)
} else if c.MessageQop == "" {
return kd(c.ha1(), fmt.Sprintf("%s:%s", c.Nonce, c.ha2()), c.impl), nil
if ha1, err = c.ha1(); err != nil {
return "", err
}
if ha2, err = c.ha2(); err != nil {
return "", err
}
return kd(ha1, fmt.Sprintf("%s:%s", c.Nonce, ha2), c.impl)
}
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" && c.Algorithm != "SHA-256" {
if c.Algorithm != AlgMD5 && c.Algorithm != AlgSha256 {
return "", ErrAlgNotImplemented
}
// Note that this is NOT implemented for "qop=auth-int". Similarly the
// auth-int server side implementations that do exist are a mess.
if c.MessageQop != "auth" && c.MessageQop != "" {
if c.MessageQop != MsgAuth && c.MessageQop != "" {
return "", ErrAlgNotImplemented
}
resp, err := c.resp("")
if err != nil {
return "", ErrAlgNotImplemented
}
sl := []string{fmt.Sprintf(`username="%s"`, c.Username)}
sl = append(sl, fmt.Sprintf(`realm="%s"`, c.Realm))
sl = append(sl, fmt.Sprintf(`nonce="%s"`, c.Nonce))
sl = append(sl, fmt.Sprintf(`uri="%s"`, c.DigestURI))
sl = append(sl, fmt.Sprintf(`response="%s"`, resp))
sl = append(sl, fmt.Sprintf(`realm="%s"`, c.Realm),
fmt.Sprintf(`nonce="%s"`, c.Nonce),
fmt.Sprintf(`uri="%s"`, c.DigestURI),
fmt.Sprintf(`response="%s"`, resp))
if c.Algorithm != "" {
sl = append(sl, fmt.Sprintf(`algorithm="%s"`, c.Algorithm))
}
if c.Opaque != "" {
sl = append(sl, fmt.Sprintf(`opaque="%s"`, c.Opaque))
}
if c.MessageQop != "" {
sl = append(sl, fmt.Sprintf("qop=%s", c.MessageQop))
sl = append(sl, fmt.Sprintf("nc=%08x", c.NonceCount))
sl = append(sl, fmt.Sprintf(`cnonce="%s"`, c.Cnonce))
sl = append(sl, fmt.Sprintf("qop=%s", c.MessageQop),
fmt.Sprintf("nc=%08x", c.NonceCount),
fmt.Sprintf(`cnonce="%s"`, c.Cnonce))
}
return fmt.Sprintf("Digest %s", strings.Join(sl, ", ")), nil
}
Expand All @@ -230,9 +282,9 @@ func (t *Transport) newCredentials(req *http.Request, c *challenge) (*credential
password: t.Password,
}
switch c.Algorithm {
case "MD5":
case AlgMD5:
cred.impl = md5.New
case "SHA-256":
case AlgSha256:
cred.impl = sha256.New
default:
return nil, ErrAlgNotImplemented
Expand Down
Loading

0 comments on commit aa4346a

Please sign in to comment.