Skip to content

Commit 8975c8e

Browse files
Merge pull request #431 from cuixq:md5
PiperOrigin-RevId: 727625584
2 parents 1a8f2fa + 6253087 commit 8975c8e

File tree

2 files changed

+155
-156
lines changed

2 files changed

+155
-156
lines changed

clients/datasource/http_auth.go

+64-63
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
package datasource
1616

1717
import (
18+
"bytes"
1819
"context"
20+
"crypto/md5"
1921
"crypto/rand"
2022
"encoding/base64"
2123
"encoding/hex"
24+
"fmt"
2225
"net/http"
2326
"slices"
2427
"strings"
@@ -186,84 +189,82 @@ func (auth *HTTPAuthentication) addBearer(req *http.Request) bool {
186189
}
187190

188191
func (auth *HTTPAuthentication) addDigest(req *http.Request, challenge string) bool {
189-
// The original implementation of this function depends on crypto/md5, which
190-
// is not allowed internally so comment out the implementation for now.
191-
return false
192192
// Mostly following the algorithm as outlined in https://en.wikipedia.org/wiki/Digest_access_authentication
193193
// And also https://datatracker.ietf.org/doc/html/rfc2617
194-
/*
195-
if auth.Username == "" || auth.Password == "" {
196-
return false
197-
}
198-
params := auth.parseChallenge(challenge)
199-
realm, ok := params["realm"]
200-
if !ok {
201-
return false
202-
}
203-
204-
nonce, ok := params["nonce"]
205-
if !ok {
206-
return false
207-
}
208-
var cnonce string
194+
if auth.Username == "" || auth.Password == "" {
195+
return false
196+
}
197+
params := auth.parseChallenge(challenge)
198+
realm, ok := params["realm"]
199+
if !ok {
200+
return false
201+
}
209202

210-
ha1 := md5.Sum([]byte(auth.Username + ":" + realm + ":" + auth.Password)) //nolint:gosec
211-
switch params["algorithm"] {
212-
case "MD5-sess":
213-
cnonce = auth.cnonce()
214-
if cnonce == "" {
215-
return false
216-
}
217-
var b bytes.Buffer
218-
fmt.Fprintf(&b, "%x:%s:%s", ha1, nonce, cnonce)
219-
ha1 = md5.Sum(b.Bytes()) //nolint:gosec
220-
case "MD5":
221-
case "":
222-
default:
203+
nonce, ok := params["nonce"]
204+
if !ok {
205+
return false
206+
}
207+
var cnonce string
208+
//nolint:gosec
209+
ha1 := md5.Sum([]byte(auth.Username + ":" + realm + ":" + auth.Password))
210+
switch params["algorithm"] {
211+
case "MD5-sess":
212+
cnonce = auth.cnonce()
213+
if cnonce == "" {
223214
return false
224215
}
216+
var b bytes.Buffer
217+
fmt.Fprintf(&b, "%x:%s:%s", ha1, nonce, cnonce)
218+
//nolint:gosec
219+
ha1 = md5.Sum(b.Bytes())
220+
case "MD5":
221+
case "":
222+
default:
223+
return false
224+
}
225225

226-
// Only support "auth" qop
227-
if qop, ok := params["qop"]; ok && !slices.Contains(strings.Split(qop, ","), "auth") {
228-
return false
229-
}
226+
// Only support "auth" qop
227+
if qop, ok := params["qop"]; ok && !slices.Contains(strings.Split(qop, ","), "auth") {
228+
return false
229+
}
230230

231-
uri := req.URL.Path // is this sufficient?
231+
uri := req.URL.Path
232232

233-
ha2 := md5.Sum([]byte(req.Method + ":" + uri)) //nolint:gosec
233+
//nolint:gosec
234+
ha2 := md5.Sum([]byte(req.Method + ":" + uri))
234235

235-
// hard-coding nonceCount to 1 since we don't make a request more than once
236-
nonceCount := "00000001"
236+
// hard-coding nonceCount to 1 since we don't make a request more than once
237+
nonceCount := "00000001"
237238

238-
var b bytes.Buffer
239-
if _, ok := params["qop"]; ok {
239+
var b bytes.Buffer
240+
if _, ok := params["qop"]; ok {
241+
if cnonce == "" {
242+
cnonce = auth.cnonce()
240243
if cnonce == "" {
241-
cnonce = auth.cnonce()
242-
if cnonce == "" {
243-
return false
244-
}
244+
return false
245245
}
246-
fmt.Fprintf(&b, "%x:%s:%s:%s:%s:%x", ha1, nonce, nonceCount, cnonce, "auth", ha2)
247-
} else {
248-
fmt.Fprintf(&b, "%x:%s:%x", ha1, nonce, ha2)
249246
}
250-
response := md5.Sum(b.Bytes()) //nolint:gosec
251-
252-
var sb strings.Builder
253-
fmt.Fprintf(&sb, "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\"",
254-
auth.Username, realm, nonce, uri)
255-
if _, ok := params["qop"]; ok {
256-
fmt.Fprintf(&sb, ", qop=auth, nc=%s, cnonce=\"%s\"", nonceCount, cnonce)
257-
}
258-
if alg, ok := params["algorithm"]; ok {
259-
fmt.Fprintf(&sb, ", algorithm=%s", alg)
260-
}
261-
fmt.Fprintf(&sb, ", response=\"%x\", opaque=\"%s\"", response, params["opaque"])
247+
fmt.Fprintf(&b, "%x:%s:%s:%s:%s:%x", ha1, nonce, nonceCount, cnonce, "auth", ha2)
248+
} else {
249+
fmt.Fprintf(&b, "%x:%s:%x", ha1, nonce, ha2)
250+
}
251+
//nolint:gosec
252+
response := md5.Sum(b.Bytes())
253+
254+
var sb strings.Builder
255+
fmt.Fprintf(&sb, "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\"",
256+
auth.Username, realm, nonce, uri)
257+
if _, ok := params["qop"]; ok {
258+
fmt.Fprintf(&sb, ", qop=auth, nc=%s, cnonce=\"%s\"", nonceCount, cnonce)
259+
}
260+
if alg, ok := params["algorithm"]; ok {
261+
fmt.Fprintf(&sb, ", algorithm=%s", alg)
262+
}
263+
fmt.Fprintf(&sb, ", response=\"%x\", opaque=\"%s\"", response, params["opaque"])
262264

263-
req.Header.Add("Authorization", sb.String())
265+
req.Header.Add("Authorization", sb.String())
264266

265-
return true
266-
*/
267+
return true
267268
}
268269

269270
func (auth *HTTPAuthentication) parseChallenge(challenge string) map[string]string {

clients/datasource/http_auth_test.go

+91-93
Original file line numberDiff line numberDiff line change
@@ -179,102 +179,100 @@ func TestHTTPAuthentication(t *testing.T) {
179179
expectedAuths: []string{"", "Bearer PleaseUseThis"},
180180
expectedResponseCodes: []int{http.StatusOK},
181181
},
182-
// Digest authentication is not supported for now so temperatily comment out the tests.
183-
/*
184-
{
185-
name: "digest auth",
186-
// Example from https://en.wikipedia.org/wiki/Digest_access_authentication#Example_with_explanation
187-
httpAuth: &datasource.HTTPAuthentication{
188-
SupportedMethods: []datasource.HTTPAuthMethod{datasource.AuthDigest},
189-
AlwaysAuth: false,
190-
Username: "Mufasa",
191-
Password: "Circle Of Life",
192-
CnonceFunc: func() string { return "0a4f113b" },
193-
},
194-
requestURL: "https://127.0.0.1/dir/index.html",
195-
wwwAuth: []string{
196-
"Digest realm=\"[email protected]\", " +
197-
"qop=\"auth,auth-int\", " +
198-
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " +
199-
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"",
200-
},
201-
expectedAuths: []string{
202-
"",
203-
// The order of these fields shouldn't actually matter
204-
"Digest username=\"Mufasa\", " +
205-
"realm=\"[email protected]\", " +
206-
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " +
207-
"uri=\"/dir/index.html\", " +
208-
"qop=auth, " +
209-
"nc=00000001, " +
210-
"cnonce=\"0a4f113b\", " +
211-
"response=\"6629fae49393a05397450978507c4ef1\", " +
212-
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"",
213-
},
214-
expectedResponseCodes: []int{http.StatusOK},
182+
{
183+
name: "digest auth",
184+
// Example from https://en.wikipedia.org/wiki/Digest_access_authentication#Example_with_explanation
185+
httpAuth: &datasource.HTTPAuthentication{
186+
SupportedMethods: []datasource.HTTPAuthMethod{datasource.AuthDigest},
187+
AlwaysAuth: false,
188+
Username: "Mufasa",
189+
Password: "Circle Of Life",
190+
CnonceFunc: func() string { return "0a4f113b" },
191+
},
192+
requestURL: "https://127.0.0.1/dir/index.html",
193+
wwwAuth: []string{
194+
"Digest realm=\"[email protected]\", " +
195+
"qop=\"auth,auth-int\", " +
196+
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " +
197+
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"",
198+
},
199+
expectedAuths: []string{
200+
"",
201+
// The order of these fields shouldn't actually matter
202+
"Digest username=\"Mufasa\", " +
203+
"realm=\"[email protected]\", " +
204+
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " +
205+
"uri=\"/dir/index.html\", " +
206+
"qop=auth, " +
207+
"nc=00000001, " +
208+
"cnonce=\"0a4f113b\", " +
209+
"response=\"6629fae49393a05397450978507c4ef1\", " +
210+
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"",
211+
},
212+
expectedResponseCodes: []int{http.StatusOK},
213+
},
214+
{
215+
name: "digest auth rfc2069", // old spec, without qop header
216+
httpAuth: &datasource.HTTPAuthentication{
217+
SupportedMethods: []datasource.HTTPAuthMethod{datasource.AuthDigest},
218+
AlwaysAuth: false,
219+
Username: "Mufasa",
220+
Password: "Circle Of Life",
215221
},
216-
{
217-
name: "digest auth rfc2069", // old spec, without qop header
218-
httpAuth: &datasource.HTTPAuthentication{
219-
SupportedMethods: []datasource.HTTPAuthMethod{datasource.AuthDigest},
220-
AlwaysAuth: false,
221-
Username: "Mufasa",
222-
Password: "Circle Of Life",
223-
},
224-
requestURL: "https://127.0.0.1/dir/index.html",
225-
wwwAuth: []string{
226-
"Digest realm=\"[email protected]\", " +
227-
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " +
228-
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"",
229-
},
230-
expectedAuths: []string{
231-
"",
232-
// The order of these fields shouldn't actually matter
233-
"Digest username=\"Mufasa\", " +
234-
"realm=\"[email protected]\", " +
235-
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " +
236-
"uri=\"/dir/index.html\", " +
237-
"response=\"670fd8c2df070c60b045671b8b24ff02\", " +
238-
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"",
239-
},
240-
expectedResponseCodes: []int{http.StatusOK},
222+
requestURL: "https://127.0.0.1/dir/index.html",
223+
wwwAuth: []string{
224+
"Digest realm=\"[email protected]\", " +
225+
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " +
226+
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"",
241227
},
242-
{
243-
name: "digest auth mvn",
244-
// From what mvn sends.
245-
httpAuth: &datasource.HTTPAuthentication{
246-
SupportedMethods: []datasource.HTTPAuthMethod{datasource.AuthDigest},
247-
AlwaysAuth: false,
248-
Username: "my-username",
249-
Password: "cool-password",
250-
CnonceFunc: func() string { return "f7ef2d457dabcd54" },
251-
},
252-
requestURL: "https://127.0.0.1:41565/commons-io/commons-io/1.0/commons-io-1.0.pom",
253-
wwwAuth: []string{
254-
"Digest realm=\"[email protected]\"," +
255-
"qop=\"auth\"," +
256-
"nonce=\"deadbeef\"," +
257-
"opaque=\"aaaa\"," +
258-
"algorithm=\"MD5-sess\"," +
259-
"domain=\"/test\"",
260-
},
261-
expectedAuths: []string{
262-
"",
263-
// The order of these fields shouldn't actually matter
264-
"Digest username=\"my-username\", " +
265-
"realm=\"[email protected]\", " +
266-
"nonce=\"deadbeef\", " +
267-
"uri=\"/commons-io/commons-io/1.0/commons-io-1.0.pom\", " +
268-
"qop=auth, " +
269-
"nc=00000001, " +
270-
"cnonce=\"f7ef2d457dabcd54\", " +
271-
"algorithm=MD5-sess, " +
272-
"response=\"15a35e7018a0fc7db05d31185e0d2c9e\", " +
273-
"opaque=\"aaaa\"",
274-
},
275-
expectedResponseCodes: []int{http.StatusOK},
228+
expectedAuths: []string{
229+
"",
230+
// The order of these fields shouldn't actually matter
231+
"Digest username=\"Mufasa\", " +
232+
"realm=\"[email protected]\", " +
233+
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " +
234+
"uri=\"/dir/index.html\", " +
235+
"response=\"670fd8c2df070c60b045671b8b24ff02\", " +
236+
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"",
276237
},
277-
*/
238+
expectedResponseCodes: []int{http.StatusOK},
239+
},
240+
{
241+
name: "digest auth mvn",
242+
// From what mvn sends.
243+
httpAuth: &datasource.HTTPAuthentication{
244+
SupportedMethods: []datasource.HTTPAuthMethod{datasource.AuthDigest},
245+
AlwaysAuth: false,
246+
Username: "my-username",
247+
Password: "cool-password",
248+
CnonceFunc: func() string { return "f7ef2d457dabcd54" },
249+
},
250+
requestURL: "https://127.0.0.1:41565/commons-io/commons-io/1.0/commons-io-1.0.pom",
251+
wwwAuth: []string{
252+
"Digest realm=\"[email protected]\"," +
253+
"qop=\"auth\"," +
254+
"nonce=\"deadbeef\"," +
255+
"opaque=\"aaaa\"," +
256+
"algorithm=\"MD5-sess\"," +
257+
"domain=\"/test\"",
258+
},
259+
expectedAuths: []string{
260+
"",
261+
// The order of these fields shouldn't actually matter
262+
"Digest username=\"my-username\", " +
263+
"realm=\"[email protected]\", " +
264+
"nonce=\"deadbeef\", " +
265+
"uri=\"/commons-io/commons-io/1.0/commons-io-1.0.pom\", " +
266+
"qop=auth, " +
267+
"nc=00000001, " +
268+
"cnonce=\"f7ef2d457dabcd54\", " +
269+
"algorithm=MD5-sess, " +
270+
"response=\"15a35e7018a0fc7db05d31185e0d2c9e\", " +
271+
"opaque=\"aaaa\"",
272+
},
273+
expectedResponseCodes: []int{http.StatusOK},
274+
},
275+
278276
{
279277
name: "basic auth reuse on subsequent",
280278
httpAuth: &datasource.HTTPAuthentication{

0 commit comments

Comments
 (0)