curl --digest not working
See original GitHub issueAre you requesting support for a new curl flag? If so, what is the flag and the equivalent Go code?
curl --user "{PUBLIC-KEY}:{PRIVATE-KEY}" --digest \
--header "Accept: application/json" \
--include \
--request GET "https://{OPSMANAGER-HOST}:{PORT}/api/public/v1.0/groups/{PROJECT-ID}/hosts?pretty=true"
import (
"bytes"
"crypto/md5"
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
"hash"
"io"
"io/ioutil"
"net/http"
"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")
)
func DoRequest() (err error) {
body := bytes.NewBufferString(data)
req, err := http.NewRequest("GET","https://{OPSMANAGER-HOST}:{PORT}/api/public/v1.0/groups/{PROJECT-ID}/hosts?pretty=true",nil)
if err != nil {
return err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
client, err := NewTransport("{PUBLIC-KEY}","{PRIVATE-KEY}").Client()
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
bytesResp, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Println(string(bytesResp))
return
}
// Transport is an implementation of http.RoundTripper that takes care of http
// digest authentication.
type Transport struct {
Username string
Password string
Transport http.RoundTripper
}
// NewTransport creates a new digest transport using the http.DefaultTransport.
func NewTransport(username, password string) *Transport {
t := &Transport{
Username: username,
Password: password,
}
t.Transport = http.DefaultTransport
return t
}
type challenge struct {
Realm string
Domain string
Nonce string
Opaque string
Stale string
Algorithm string
Qop string
}
func parseChallenge(input string) (*challenge, error) {
const ws = " \n\r\t"
const qs = `"`
s := strings.Trim(input, ws)
if !strings.HasPrefix(s, "Digest ") {
return nil, ErrBadChallenge
}
s = strings.Trim(s[7:], ws)
sl := strings.Split(s, ", ")
c := &challenge{
Algorithm: AlgMD5,
}
var r []string
for i := range sl {
r = strings.SplitN(sl[i], "=", 2)
switch r[0] {
case "realm":
c.Realm = strings.Trim(r[1], qs)
case "domain":
c.Domain = strings.Trim(r[1], qs)
case "nonce":
c.Nonce = strings.Trim(r[1], qs)
case "opaque":
c.Opaque = strings.Trim(r[1], qs)
case "stale":
c.Stale = strings.Trim(r[1], qs)
case "algorithm":
c.Algorithm = strings.Trim(r[1], qs)
case "qop":
c.Qop = strings.Trim(r[1], qs)
default:
return nil, ErrBadChallenge
}
}
return c, nil
}
type credentials struct {
Username string
Realm string
Nonce string
DigestURI string
Algorithm string
Cnonce string
Opaque string
MessageQop string
NonceCount int
method string
password string
impl hashingFunc
}
type hashingFunc func() hash.Hash
func h(data string, f hashingFunc) (string, error) {
hf := f()
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, error) {
return h(fmt.Sprintf("%s:%s", secret, data), f)
}
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, error) {
return h(fmt.Sprintf("%s:%s", c.method, c.DigestURI), c.impl)
}
func (c *credentials) resp(cnonce string) (resp string, err error) {
var ha1 string
var ha2 string
c.NonceCount++
if c.MessageQop == MsgAuth {
if cnonce != "" {
c.Cnonce = cnonce
} else {
b := make([]byte, 8)
_, err = io.ReadFull(rand.Reader, b)
if err != nil {
return "", err
}
c.Cnonce = fmt.Sprintf("%x", b)[:16]
}
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 == "" {
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 != 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 != 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),
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),
fmt.Sprintf("nc=%08x", c.NonceCount),
fmt.Sprintf(`cnonce="%s"`, c.Cnonce))
}
return fmt.Sprintf("Digest %s", strings.Join(sl, ", ")), nil
}
func (t *Transport) newCredentials(req *http.Request, c *challenge) (*credentials, error) {
cred := &credentials{
Username: t.Username,
Realm: c.Realm,
Nonce: c.Nonce,
DigestURI: req.URL.RequestURI(),
Algorithm: c.Algorithm,
Opaque: c.Opaque,
MessageQop: c.Qop, // "auth" must be a single value
NonceCount: 0,
method: req.Method,
password: t.Password,
}
switch c.Algorithm {
case AlgMD5:
cred.impl = md5.New
case AlgSha256:
cred.impl = sha256.New
default:
return nil, ErrAlgNotImplemented
}
return cred, nil
}
// RoundTrip makes a request expecting a 401 response that will require digest
// authentication. It creates the credentials it needs and makes a follow-up
// request.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.Transport == nil {
return nil, ErrNilTransport
}
// Copy the request so we don't modify the input.
origReq := new(http.Request)
*origReq = *req
origReq.Header = make(http.Header, len(req.Header))
for k, s := range req.Header {
origReq.Header[k] = s
}
// We'll need the request body twice. In some cases we can use GetBody
// to obtain a fresh reader for the second request, which we do right
// before the RoundTrip(origReq) call. If GetBody is unavailable, read
// the body into a memory buffer and use it for both requests.
if req.Body != nil && req.GetBody == nil {
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
origReq.Body = ioutil.NopCloser(bytes.NewBuffer(body))
}
// Make a request to get the 401 that contains the challenge.
challenge, resp, err := t.fetchChallenge(req)
if challenge == "" || err != nil {
return resp, err
}
c, err := parseChallenge(challenge)
if err != nil {
return nil, err
}
// Form credentials based on the challenge.
cr, err := t.newCredentials(origReq, c)
if err != nil {
return nil, err
}
auth, err := cr.authorize()
if err != nil {
return nil, err
}
// Obtain a fresh body.
if req.Body != nil && req.GetBody != nil {
origReq.Body, err = req.GetBody()
if err != nil {
return nil, err
}
}
// Make authenticated request.
origReq.Header.Set("Authorization", auth)
return t.Transport.RoundTrip(origReq)
}
func (t *Transport) fetchChallenge(req *http.Request) (string, *http.Response, error) {
resp, err := t.Transport.RoundTrip(req)
if err != nil {
return "", resp, err
}
if resp.StatusCode != http.StatusUnauthorized {
return "", resp, nil
}
// We'll no longer use the initial response, so close it
defer func() {
// Ensure the response body is fully read and closed
// before we reconnect, so that we reuse the same TCP connection.
// Close the previous response's body. But read at least some of
// the body so if it's small the underlying TCP connection will be
// re-used. No need to check for errors: if it fails, the Transport
// won't reuse it anyway.
const maxBodySlurpSize = 2 << 10
if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize {
_, _ = io.CopyN(ioutil.Discard, resp.Body, maxBodySlurpSize)
}
resp.Body.Close()
}()
return resp.Header.Get("WWW-Authenticate"), resp, nil
}
// Client returns an HTTP client that uses the digest transport.
func (t *Transport) Client() (*http.Client, error) {
if t.Transport == nil {
return nil, ErrNilTransport
}
return &http.Client{Transport: t}, nil
}
Issue Analytics
- State:
- Created 2 years ago
- Comments:7 (4 by maintainers)
Top Results From Across the Web
DIGEST authentication not working with curl #397 - GitHub
I'm not able to get a successful response with the following command: curl -D - --digest -u "user:passwd" ...
Read more >Curl not working with Digest Authentication - OpenWrt Forum
I'm using curl on a OpenWrt device to fetch images from a network camera. The problem is that the camera supports only Digest...
Read more >How do I send a digest auth request using curl? - Super User
CURL takes care of computing the client response for you. This is exactly what "supporting of digest authentication" means for any client.
Read more >How do I send a digest auth request using curl? - Stack Overflow
The only thing you do have to is username/password pair. CURL takes care of computing the client response for you. This is exactly...
Read more >Digest authentification in Geoserver not working
I am trying to use Digest authentification in Geoserver. But when I send a Curl request I get the 401 error.
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
You are right, I am very supportive of originality, we will write one when we have time
Ok, thanks for the link. If any of the code was borrowed, we need to conform to the license restrictions. Maybe it’s a little tricky since we’re doing code generation (see the whole GitHub Copilot fiasco, though technically that’s very different, in practice it’s a very similar task)… it’s weird because the code we generate we don’t really care about license or have one, it’s basically public domain. So it’ll be difficult to use unoriginal code that has a license attached to that part of it. And part of conforming to the restrictions of the license is crediting the authors and putting in the copyright. But only that part of the code would be “copyrighted,” I guess.
See what I mean? Is there any way we can do this without licensing someone else’s code?