Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/pkg/gowebdav/digestAuth.go
1560 views
1
package gowebdav
2
3
import (
4
"crypto/md5"
5
"crypto/rand"
6
"encoding/hex"
7
"fmt"
8
"io"
9
"net/http"
10
"strings"
11
)
12
13
// DigestAuth structure holds our credentials
14
type DigestAuth struct {
15
user string
16
pw string
17
digestParts map[string]string
18
}
19
20
// Type identifies the DigestAuthenticator
21
func (d *DigestAuth) Type() string {
22
return "DigestAuth"
23
}
24
25
// User holds the DigestAuth username
26
func (d *DigestAuth) User() string {
27
return d.user
28
}
29
30
// Pass holds the DigestAuth password
31
func (d *DigestAuth) Pass() string {
32
return d.pw
33
}
34
35
// Authorize the current request
36
func (d *DigestAuth) Authorize(req *http.Request, method string, path string) {
37
d.digestParts["uri"] = path
38
d.digestParts["method"] = method
39
d.digestParts["username"] = d.user
40
d.digestParts["password"] = d.pw
41
req.Header.Set("Authorization", getDigestAuthorization(d.digestParts))
42
}
43
44
func digestParts(resp *http.Response) map[string]string {
45
result := map[string]string{}
46
if len(resp.Header["Www-Authenticate"]) > 0 {
47
wantedHeaders := []string{"nonce", "realm", "qop", "opaque", "algorithm", "entityBody"}
48
responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",")
49
for _, r := range responseHeaders {
50
for _, w := range wantedHeaders {
51
if strings.Contains(r, w) {
52
result[w] = strings.Trim(
53
strings.SplitN(r, `=`, 2)[1],
54
`"`,
55
)
56
}
57
}
58
}
59
}
60
return result
61
}
62
63
func getMD5(text string) string {
64
hasher := md5.New()
65
hasher.Write([]byte(text))
66
return hex.EncodeToString(hasher.Sum(nil))
67
}
68
69
func getCnonce() string {
70
b := make([]byte, 8)
71
io.ReadFull(rand.Reader, b)
72
return fmt.Sprintf("%x", b)[:16]
73
}
74
75
func getDigestAuthorization(digestParts map[string]string) string {
76
d := digestParts
77
// These are the correct ha1 and ha2 for qop=auth. We should probably check for other types of qop.
78
79
var (
80
ha1 string
81
ha2 string
82
nonceCount = 00000001
83
cnonce = getCnonce()
84
response string
85
)
86
87
// 'ha1' value depends on value of "algorithm" field
88
switch d["algorithm"] {
89
case "MD5", "":
90
ha1 = getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"])
91
case "MD5-sess":
92
ha1 = getMD5(
93
fmt.Sprintf("%s:%v:%s",
94
getMD5(d["username"]+":"+d["realm"]+":"+d["password"]),
95
nonceCount,
96
cnonce,
97
),
98
)
99
}
100
101
// 'ha2' value depends on value of "qop" field
102
switch d["qop"] {
103
case "auth", "":
104
ha2 = getMD5(d["method"] + ":" + d["uri"])
105
case "auth-int":
106
if d["entityBody"] != "" {
107
ha2 = getMD5(d["method"] + ":" + d["uri"] + ":" + getMD5(d["entityBody"]))
108
}
109
}
110
111
// 'response' value depends on value of "qop" field
112
switch d["qop"] {
113
case "":
114
response = getMD5(
115
fmt.Sprintf("%s:%s:%s",
116
ha1,
117
d["nonce"],
118
ha2,
119
),
120
)
121
case "auth", "auth-int":
122
response = getMD5(
123
fmt.Sprintf("%s:%s:%v:%s:%s:%s",
124
ha1,
125
d["nonce"],
126
nonceCount,
127
cnonce,
128
d["qop"],
129
ha2,
130
),
131
)
132
}
133
134
authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", nc=%v, cnonce="%s", response="%s"`,
135
d["username"], d["realm"], d["nonce"], d["uri"], nonceCount, cnonce, response)
136
137
if d["qop"] != "" {
138
authorization += fmt.Sprintf(`, qop=%s`, d["qop"])
139
}
140
141
if d["opaque"] != "" {
142
authorization += fmt.Sprintf(`, opaque="%s"`, d["opaque"])
143
}
144
145
return authorization
146
}
147
148