Giter Site home page Giter Site logo

digest's Introduction

HTTP Digest Access Authentication

go.dev reference

This package provides a http.RoundTripper implementation which re-uses digest challenges

package main

import (
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := &http.Client{
		Transport: &digest.Transport{
			Username: "foo",
			Password: "bar",
		},
	}
	res, err := client.Get("http://localhost:8080/some_outdated_service")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Using Cookies

If you're using an http.CookieJar the digest.Transport needs a reference to it.

package main

import (
	"net/http"
	"net/http/cookiejar"

	"github.com/icholy/digest"
)

func main() {
	jar, _ := cookiejar.New(nil)
	client := &http.Client{
		Transport: &digest.Transport{
			Jar:      jar,
			Username: "foo",
			Password: "bar",
		},
	}
	res, err := client.Get("http://localhost:8080/digest_with_cookies")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Custom Authenticate Header

package main

import (
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := &http.Client{
		Transport: &digest.Transport{
			Username: "foo",
			Password: "bar",
			FindChallenge: func(h http.Header) (*digest.Challenge, error) {
				value := h.Get("Custom-Authenticate-Header")
				if value == "" {
					return nil, digest.ErrNoChallenge
				}
				return digest.ParseChallenge(value)
			},
		},
	}
	res, err := client.Get("http://localhost:8080/non_compliant")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Override Digest Options

package main

import (
	"fmt"
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := &http.Client{
		Transport: &digest.Transport{
			Digest: func(req *http.Request, chal *digest.Challenge, opt digest.Options) (*digest.Credentials, error) {
				switch req.URL.Hostname() {
				case "badauth.org":
					opt.Username = "foo"
					opt.Password = "bar"
				case "poorsecurity.com":
					opt.Username = "zoo"
					opt.Password = "boo"
				default:
					return nil, fmt.Errorf("unsuported host: %q", req.URL)
				}
				return digest.Digest(chal, opt)
			},
		},
	}
	res, err := client.Get("http://poorsecurity.com/legacy.php")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Low Level API

func main() {
  // get the challenge from a 401 response
  header := res.Header.Get("WWW-Authenticate")
  chal, _ := digest.ParseChallenge(header)

  // use it to create credentials for the next request
  cred, _ := digest.Digest(chal, digest.Options{
    Username: "foo",
    Password: "bar",
    Method:   req.Method,
    URI:      req.URL.RequestURI(),
    GetBody:  req.GetBody,
    Count:    1,
  })
  req.Header.Set("Authorization", cred.String())

  // if you use the same challenge again, you must increment the Count
  cred2, _ := digest.Digest(chal, digest.Options{
    Username: "foo",
    Password: "bar",
    Method:   req2.Method,
    URI:      req2.URL.RequestURI(),
    GetBody:  req2.GetBody,
    Count:    2,
  })
  req2.Header.Set("Authorization", cred.String())
}

digest's People

Contributors

dsonck92 avatar efd6 avatar icholy avatar martende avatar sealbro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

digest's Issues

drain response body

The connection cannot be re-used if the body isn't drained before closing.

icholy/digest auth failed where curl --digest succeeds

As the title states, the icholy/digest auth fails where a curl --digest command succeeds.

The curl command with login removed:

curl -v -X PUT -H 'X-CSRF: x' -H "Accept: application/json" --data 'value=true' --digest 'http://***:***@192.168.0.100/restapi/relay/outlets/=0,1,2/state/'

Here is the Authorization header from curl's second, successful request with username and response removed:

Authorization: Digest username="*****", realm="DLI LPC92601002528", nonce="G/QIGjhJANuvwqcO", uri="/restapi/relay/outlets/=0,1,2/state/", cnonce="NjUxNzA5ZDQ0OGI1NDRlNzFmYTRiYTllNzJlMDdlYjM=", nc=00000001, qop=auth, response="************", opaque="MyhQh+WfWI37Ou00", algorithm="MD5"

Now with icholy/digest, using the README example to set username and password:

client := &http.Client{
		Transport: &digest.Transport{
			Username:      "*********",
			Password:      "**********",
		},
	}
	req, err := http.NewRequest(http.MethodPut,
		"http://192.168.0.100/restapi/relay/outlets/=0,1,2/state", 
                  strings.NewReader("value=true"))
	if err != nil { return }
	req.Header.Add("Accept", "application/json")
	req.Header["X-CSRF"] = []string{"x"}
	res, _ := client.Do(req)
	fmt.Println(res)

And here is the request from the digest.Transport's second request to the server with the digest username and response removed:

&{PUT http://192.168.0.100/restapi/relay/outlets/=0,1,2/state HTTP/1.1 1 1 map[Accept:[application/json] Authorization:[Digest username="**********", realm="DLI LPC92601002528", nonce="mn+ExiOvWOnZuwHN", uri="/restapi/relay/outlets/=0,1,2/state", response=***************", algorithm=MD5, cnonce="6d00de0791f29f8c509d218b83680979", opque="MOJIFcQn52wT0EYX", qop=auth, nc=00000001] X-CSRF:[x]] {0xc000282060} 0x123ef80 10 [] false 192.168.0.100 map[] map[] <nil> map[]   <nil> <nil> <nil> 0xc0000ba008}}

This request gives a 401 response with a new Www-Authenticate header.

To rule out the request body being the issue, the same curl above with garbage --data still authenticates with a 207 response.

Please let me know if there is any more information I can provide. I will update this as I explore the issue.

Edit: here is an example 401 response from the server via curl:

< HTTP/1.1 401 Unauthorized
< X-Content-Type-Options: nosniff
< Connection: close
< Vary: origin, accept, prefer
< Cache-Control: max-age=0, private, must-revalidate
< Content-Security-Policy: frame-ancestors 'self';default-src 'self';media-src 'none';img-src 'self' data:;object-src 'none'
< X-Frame-Options: sameorigin
< Accept-Ranges: dli-depth
< WWW-Authenticate: Digest algorithm="MD5", opaque="mNLjyiEsUhWpGkNB", qop="auth,auth-int", realm="DLI LPC92601002528", nonce="3U07AmOG2+OW9XOq"
< Content-Type: application/json

and the same via golang:

 &{401 Unauthorized 401 HTTP/1.1 1 1 map[Accept-Ranges:[dli-depth] Cache-Control:[max-age=0, private, must-revalidate] Content-Security-Policy:[frame-ancestors 'self';default-src 'self';media-src 'none';img-src 'self' data:;object-src 'none'] Content-Type:[application/json] Vary:[origin, accept, prefer] Www-Authenticate:[Digest algorithm="MD5", opaque="MOJIFcQn52wT0EYX", qop="auth,auth-int", realm="DLI LPC92601002528", nonce="mn+ExiOvWOnZuwHN"] X-Content-Type-Options:[nosniff] X-Frame-Options:[sameorigin]] 0xc000024080 -1 [] true false map[] 0xc0000f0100 <nil>}

Parameter 'response' position in Credentials.String() is important for Lilin cameras

Hi, Guys!

I have some problems with digest authorization on Lilin's cameras

try to explain:

  • 200 code from Postman or Python requests:

    • Digest username="user", realm="my camera", nonce="NzQ4OWJmNzI3MDEzZmY5ZDY1ZDA4ZDZiZWZhZGU0N2M6Mzc3Mjk5Nzc2NzgxNA==", uri="/snap1", algorithm="MD5", qop=auth, nc=00000001, cnonce="f35lPAce", response="49e0700bf5aed546860166453207b33e"
  • 401 code from icholy/digest

    • Digest username="user", realm="my camera", nonce="NzQ4OWJmNzI3MDEzZmY5ZDY1ZDA4ZDZiZWZhZGU0N2M6Mzc3Mjk5Nzc2NzgxNA==", uri="/snap1", response="49e0700bf5aed546860166453207b33e", cnonce="7e6e0b9f03113421", qop=auth, nc=00000001

It is OK if I make a PR where I change the order of the response in the formation of the string (c *Credentials) String() ?

Cookies discarded from challenge response

Thanks for your code, solved my proble.

But there is a problem,post the second url with Authorization header, it will be fail. because the server has given up the nonce paramter.

so I think post the second url with cookie header.

I modify the code:

  1. add cookiejar.go file
type HttpCookieJar struct {
	cookies []*http.Cookie
}

func (jar *HttpCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
	jar.cookies = cookies
}
func (jar *HttpCookieJar) Cookies(u *url.URL) []*http.Cookie {
	return jar.cookies
}
  1. modify transport.go file
func (t *Transport) clear(res *http.Response) error {
	host := res.Request.URL.Hostname()

	t.cacheMu.Lock()
	if t.cache == nil {
		t.cache = map[string]*cached{}
	}

	delete(t.cache, host)

	t.cacheMu.Unlock()

	return nil
}

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	// use the configured transport if there is one
	tr := t.Transport
	if tr == nil {
		tr = http.DefaultTransport
	}
	// don't modify the original request
	clone, err := cloner(req)
	if err != nil {
		return nil, err
	}
	// setup the first request
	first, err := clone()
	if err != nil {
		return nil, err
	}
	// try to authorize the request using a cached challenge
	if err := t.authorize(first); err != nil {
		return nil, err
	}
	// the first request will either succeed or return a 401
	res, err := tr.RoundTrip(first)
	if err != nil || res.StatusCode != http.StatusUnauthorized {
		return res, err
	}
	// close the first message body
	res.Body.Close()
	// save the challenge for future use
	if err := t.save(res); err != nil {
		return nil, err
	}
	// setup the second request
	second, err := clone()
	if err != nil {
		return nil, err
	}
	// authorise a second request based on the new challenge
	if err := t.authorize(second); err != nil {
		return nil, err
	}

	resp, error := tr.RoundTrip(second)

	t.clear(resp)

	return resp, error
}

3 Test demo

// Source code from https://github.com/icholy/digest

func TestHttpDigest(t *testing.T) {
	jar := new(HttpCookieJar)
	client := http.Client{
		Transport: &Transport{
			Username: "test",
			Password: "test",
		},
		Jar: jar,
	}

	res, _ := client.Get("http://localhost:8080/demo/user.action")
	println(res.Status)

	res, _ = client.Get("http://localhost:8080/demo/test.action")
	println(res.Status)
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.