Skip to content

Commit 390fbee

Browse files
committed
show body when send http digest f
1 parent 9480c6c commit 390fbee

File tree

2 files changed

+284
-1
lines changed

2 files changed

+284
-1
lines changed

digest/digest.go

+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
// Copyright 2013 M-Lab
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// The digest package provides an implementation of http.RoundTripper that takes
16+
// care of HTTP Digest Authentication (http://www.ietf.org/rfc/rfc2617.txt).
17+
// This only implements the MD5 and "auth" portions of the RFC, but that covers
18+
// the majority of avalible server side implementations including apache web
19+
// server.
20+
//
21+
// Example usage:
22+
//
23+
// t := NewTransport("myUserName", "myP@55w0rd")
24+
// req, err := http.NewRequest("GET", "http://notreal.com/path?arg=1", nil)
25+
// if err != nil {
26+
// return err
27+
// }
28+
// resp, err := t.RoundTrip(req)
29+
// if err != nil {
30+
// return err
31+
// }
32+
//
33+
// OR it can be used as a client:
34+
//
35+
// c, err := t.Client()
36+
// if err != nil {
37+
// return err
38+
// }
39+
// resp, err := c.Get("http://notreal.com/path?arg=1")
40+
// if err != nil {
41+
// return err
42+
// }
43+
//
44+
package digest
45+
46+
import (
47+
"crypto/md5"
48+
"crypto/rand"
49+
"errors"
50+
"fmt"
51+
"github.com/golang/glog"
52+
"io"
53+
"io/ioutil"
54+
"net/http"
55+
"strings"
56+
)
57+
58+
var (
59+
ErrNilTransport = errors.New("Transport is nil")
60+
ErrBadChallenge = errors.New("Challenge is bad")
61+
ErrAlgNotImplemented = errors.New("Alg not implemented")
62+
)
63+
64+
// Transport is an implementation of http.RoundTripper that takes care of http
65+
// digest authentication.
66+
type Transport struct {
67+
Username string
68+
Password string
69+
Transport http.RoundTripper
70+
}
71+
72+
// NewTransport creates a new digest transport using the http.DefaultTransport.
73+
func NewTransport(username, password string) *Transport {
74+
t := &Transport{
75+
Username: username,
76+
Password: password,
77+
}
78+
t.Transport = http.DefaultTransport
79+
return t
80+
}
81+
82+
type challenge struct {
83+
Realm string
84+
Domain string
85+
Nonce string
86+
Opaque string
87+
Stale string
88+
Algorithm string
89+
Qop string
90+
}
91+
92+
func parseChallenge(input string) (*challenge, error) {
93+
const ws = " \n\r\t"
94+
const qs = `"`
95+
s := strings.Trim(input, ws)
96+
if !strings.HasPrefix(s, "Digest ") {
97+
return nil, ErrBadChallenge
98+
}
99+
s = strings.Trim(s[7:], ws)
100+
sl := strings.Split(s, ", ")
101+
c := &challenge{
102+
Algorithm: "MD5",
103+
}
104+
var r []string
105+
for i := range sl {
106+
r = strings.SplitN(sl[i], "=", 2)
107+
switch r[0] {
108+
case "realm":
109+
c.Realm = strings.Trim(r[1], qs)
110+
case "domain":
111+
c.Domain = strings.Trim(r[1], qs)
112+
case "nonce":
113+
c.Nonce = strings.Trim(r[1], qs)
114+
case "opaque":
115+
c.Opaque = strings.Trim(r[1], qs)
116+
case "stale":
117+
c.Stale = strings.Trim(r[1], qs)
118+
case "algorithm":
119+
c.Algorithm = strings.Trim(r[1], qs)
120+
case "qop":
121+
//TODO(gavaletz) should be an array of strings?
122+
c.Qop = strings.Trim(r[1], qs)
123+
default:
124+
return nil, ErrBadChallenge
125+
}
126+
}
127+
return c, nil
128+
}
129+
130+
type credentials struct {
131+
Username string
132+
Realm string
133+
Nonce string
134+
DigestURI string
135+
Algorithm string
136+
Cnonce string
137+
Opaque string
138+
MessageQop string
139+
NonceCount int
140+
method string
141+
password string
142+
}
143+
144+
func h(data string) string {
145+
hf := md5.New()
146+
io.WriteString(hf, data)
147+
return fmt.Sprintf("%x", hf.Sum(nil))
148+
}
149+
150+
func kd(secret, data string) string {
151+
return h(fmt.Sprintf("%s:%s", secret, data))
152+
}
153+
154+
func (c *credentials) ha1() string {
155+
return h(fmt.Sprintf("%s:%s:%s", c.Username, c.Realm, c.password))
156+
}
157+
158+
func (c *credentials) ha2() string {
159+
return h(fmt.Sprintf("%s:%s", c.method, c.DigestURI))
160+
}
161+
162+
func (c *credentials) resp(cnonce string) (string, error) {
163+
c.NonceCount++
164+
if c.MessageQop == "auth" {
165+
if cnonce != "" {
166+
c.Cnonce = cnonce
167+
} else {
168+
b := make([]byte, 8)
169+
io.ReadFull(rand.Reader, b)
170+
c.Cnonce = fmt.Sprintf("%x", b)[:16]
171+
}
172+
return kd(c.ha1(), fmt.Sprintf("%s:%08x:%s:%s:%s",
173+
c.Nonce, c.NonceCount, c.Cnonce, c.MessageQop, c.ha2())), nil
174+
} else if c.MessageQop == "" {
175+
return kd(c.ha1(), fmt.Sprintf("%s:%s", c.Nonce, c.ha2())), nil
176+
}
177+
return "", ErrAlgNotImplemented
178+
}
179+
180+
func (c *credentials) authorize() (string, error) {
181+
// Note that this is only implemented for MD5 and NOT MD5-sess.
182+
// MD5-sess is rarely supported and those that do are a big mess.
183+
if c.Algorithm != "MD5" {
184+
return "", ErrAlgNotImplemented
185+
}
186+
// Note that this is NOT implemented for "qop=auth-int". Similarly the
187+
// auth-int server side implementations that do exist are a mess.
188+
if c.MessageQop != "auth" && c.MessageQop != "" {
189+
return "", ErrAlgNotImplemented
190+
}
191+
resp, err := c.resp("")
192+
if err != nil {
193+
return "", ErrAlgNotImplemented
194+
}
195+
sl := []string{fmt.Sprintf(`username="%s"`, c.Username)}
196+
sl = append(sl, fmt.Sprintf(`realm="%s"`, c.Realm))
197+
sl = append(sl, fmt.Sprintf(`nonce="%s"`, c.Nonce))
198+
sl = append(sl, fmt.Sprintf(`uri="%s"`, c.DigestURI))
199+
sl = append(sl, fmt.Sprintf(`response="%s"`, resp))
200+
if c.Algorithm != "" {
201+
sl = append(sl, fmt.Sprintf(`algorithm="%s"`, c.Algorithm))
202+
}
203+
if c.Opaque != "" {
204+
sl = append(sl, fmt.Sprintf(`opaque="%s"`, c.Opaque))
205+
}
206+
if c.MessageQop != "" {
207+
sl = append(sl, fmt.Sprintf("qop=%s", c.MessageQop))
208+
sl = append(sl, fmt.Sprintf("nc=%08x", c.NonceCount))
209+
sl = append(sl, fmt.Sprintf(`cnonce="%s"`, c.Cnonce))
210+
}
211+
return fmt.Sprintf("Digest %s", strings.Join(sl, ", ")), nil
212+
}
213+
214+
func (t *Transport) newCredentials(req *http.Request, c *challenge) *credentials {
215+
return &credentials{
216+
Username: t.Username,
217+
Realm: c.Realm,
218+
Nonce: c.Nonce,
219+
DigestURI: req.URL.RequestURI(),
220+
Algorithm: c.Algorithm,
221+
Opaque: c.Opaque,
222+
MessageQop: c.Qop, // "auth" must be a single value
223+
NonceCount: 0,
224+
method: req.Method,
225+
password: t.Password,
226+
}
227+
}
228+
229+
// RoundTrip makes a request expecting a 401 response that will require digest
230+
// authentication. It creates the credentials it needs and makes a follow-up
231+
// request.
232+
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
233+
if t.Transport == nil {
234+
return nil, ErrNilTransport
235+
}
236+
237+
// Copy the request so we don't modify the input.
238+
req2 := new(http.Request)
239+
*req2 = *req
240+
req2.Header = make(http.Header)
241+
for k, s := range req.Header {
242+
req2.Header[k] = s
243+
}
244+
245+
// Make a request to get the 401 that contains the challenge.
246+
resp, err := t.Transport.RoundTrip(req)
247+
if err != nil || resp.StatusCode != 401 {
248+
return resp, err
249+
}
250+
chal := resp.Header.Get("WWW-Authenticate")
251+
c, err := parseChallenge(chal)
252+
if err != nil {
253+
// Read response body
254+
responseBody, errR := ioutil.ReadAll(resp.Body)
255+
if errR == nil {
256+
glog.Infof("Onvif response: %s", string(responseBody))
257+
return resp, err
258+
}
259+
return resp, errR
260+
}
261+
262+
// Form credentials based on the challenge.
263+
cr := t.newCredentials(req2, c)
264+
auth, err := cr.authorize()
265+
if err != nil {
266+
return resp, err
267+
}
268+
269+
// We'll no longer use the initial response, so close it
270+
resp.Body.Close()
271+
272+
// Make authenticated request.
273+
req2.Header.Set("Authorization", auth)
274+
return t.Transport.RoundTrip(req2)
275+
}
276+
277+
// Client returns an HTTP client that uses the digest transport.
278+
func (t *Transport) Client() (*http.Client, error) {
279+
if t.Transport == nil {
280+
return nil, ErrNilTransport
281+
}
282+
return &http.Client{Transport: t}, nil
283+
}

soap.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"crypto/sha1"
66
"encoding/base64"
77
"errors"
8-
"github.com/bobziuchkovski/digest"
8+
"github.com/quocson95/go-onvif/digest"
99
"io/ioutil"
1010
"net/http"
1111
"net/url"

0 commit comments

Comments
 (0)