Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
Go Httpbin
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Jira
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Container registry
Monitor
Service Desk
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
OSS
Nix
Go Httpbin
Commits
3878d15a
Commit
3878d15a
authored
7 years ago
by
Will McCutchen
Browse files
Options
Downloads
Patches
Plain Diff
Add digest package
parent
d45bfe51
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
httpbin/digest/digest.go
+193
-0
193 additions, 0 deletions
httpbin/digest/digest.go
with
193 additions
and
0 deletions
httpbin/digest/digest.go
0 → 100644
+
193
−
0
View file @
3878d15a
// Package digest provides a limited implementation of HTTP Digest
// Authentication, as defined in RFC 2617.
//
// Only the "auth" QOP directive is handled at this time, and while support for
// the SHA-256 algorithm is implemented here it does not actually work in
// either Chrome or Firefox.
//
// For more info, see:
// https://tools.ietf.org/html/rfc2617
// https://en.wikipedia.org/wiki/Digest_access_authentication
package
digest
import
(
"crypto"
"crypto/md5"
"crypto/sha256"
"crypto/subtle"
"fmt"
"math/rand"
"net/http"
"strings"
"time"
)
// Check returns a bool indicating whether the request is correctly
// authenticated for the given username and password.
//
// TODO: use constant-time equality comparison.
func
Check
(
req
*
http
.
Request
,
username
,
password
string
)
bool
{
auth
:=
parseAuthorizationHeader
(
req
.
Header
.
Get
(
"Authorization"
))
if
auth
==
nil
{
return
false
}
expectedResponse
:=
response
(
auth
,
password
,
req
.
Method
,
req
.
RequestURI
)
return
compare
(
auth
.
response
,
expectedResponse
)
}
// Challenge returns a WWW-Authenticate header value for the given realm and
// algorithm.
func
Challenge
(
realm
,
algorithm
string
)
string
{
entropy
:=
make
([]
byte
,
32
)
rand
.
Read
(
entropy
)
opaqueVal
:=
entropy
[
:
16
]
nonceVal
:=
fmt
.
Sprintf
(
"%s:%x"
,
time
.
Now
(),
entropy
[
16
:
31
])
opaque
:=
hash
(
opaqueVal
,
crypto
.
MD5
)
nonce
:=
hash
([]
byte
(
nonceVal
),
crypto
.
MD5
)
return
fmt
.
Sprintf
(
"Digest qop=auth, realm=%s, algorithm=%s, nonce=%s, opaque=%s"
,
realm
,
algorithm
,
nonce
,
opaque
)
}
// authorization is the result of parsing an Authorization header
type
authorization
struct
{
algorithm
crypto
.
Hash
cnonce
string
nc
string
nonce
string
opaque
string
qop
string
realm
string
response
string
uri
string
username
string
}
// parseAuthorizationHeader parses an Authorization header into an
// Authorization struct, given a an authorization header like:
//
// Authorization: Digest username="Mufasa",
// realm="testrealm@host.com",
// nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
// uri="/dir/index.html",
// qop=auth,
// nc=00000001,
// cnonce="0a4f113b",
// response="6629fae49393a05397450978507c4ef1",
// opaque="5ccc069c403ebaf9f0171e9517f40e41"
//
// If the given value does not contain a Digest authorization header, or is in
// some other way malformed, nil is returned.
//
// Example from Wikipedia: https://en.wikipedia.org/wiki/Digest_access_authentication#Example_with_explanation
func
parseAuthorizationHeader
(
value
string
)
*
authorization
{
if
value
==
""
{
return
nil
}
parts
:=
strings
.
SplitN
(
value
,
" "
,
2
)
if
parts
[
0
]
!=
"Digest"
||
len
(
parts
)
!=
2
{
return
nil
}
authInfo
:=
parts
[
1
]
auth
:=
parseDictHeader
(
authInfo
)
algo
:=
crypto
.
MD5
if
strings
.
ToLower
(
auth
[
"algorithm"
])
==
"sha-256"
{
algo
=
crypto
.
SHA256
}
return
&
authorization
{
algorithm
:
algo
,
cnonce
:
auth
[
"cnonce"
],
nc
:
auth
[
"nc"
],
nonce
:
auth
[
"nonce"
],
opaque
:
auth
[
"opaque"
],
qop
:
auth
[
"qop"
],
realm
:
auth
[
"realm"
],
response
:
auth
[
"response"
],
uri
:
auth
[
"uri"
],
username
:
auth
[
"username"
],
}
}
// parseDictHeader is a simplistic, buggy, and incomplete implementation of
// parsing key-value pairs from a header value into a map.
func
parseDictHeader
(
value
string
)
map
[
string
]
string
{
res
:=
make
(
map
[
string
]
string
)
for
_
,
pair
:=
range
strings
.
Split
(
value
,
","
)
{
parts
:=
strings
.
SplitN
(
pair
,
"="
,
2
)
if
len
(
parts
)
==
1
{
res
[
parts
[
0
]]
=
""
continue
}
key
:=
parts
[
0
]
val
:=
parts
[
1
]
if
strings
.
HasPrefix
(
val
,
`"`
)
&&
strings
.
HasSuffix
(
val
,
`"`
)
{
val
=
val
[
1
:
len
(
val
)
-
1
]
}
res
[
strings
.
TrimSpace
(
key
)]
=
strings
.
TrimSpace
(
val
)
}
return
res
}
// hash generates the hex digest of the given data using the given hashing
// algorithm, which must be one of MD5 or SHA256.
func
hash
(
data
[]
byte
,
algorithm
crypto
.
Hash
)
string
{
switch
algorithm
{
case
crypto
.
SHA256
:
return
fmt
.
Sprintf
(
"%x"
,
sha256
.
Sum256
(
data
))
default
:
return
fmt
.
Sprintf
(
"%x"
,
md5
.
Sum
(
data
))
}
}
// makeHA1 returns the HA1 hash, where
//
// HA1 = H(A1) = H(username:realm:password)
//
// and H is one of MD5 or SHA256.
func
makeHA1
(
realm
,
username
,
password
string
,
algorithm
crypto
.
Hash
)
string
{
A1
:=
fmt
.
Sprintf
(
"%s:%s:%s"
,
username
,
realm
,
password
)
return
hash
([]
byte
(
A1
),
algorithm
)
}
// makeHA2 returns the HA2 hash, where
//
// HA2 = H(A2) = H(method:digestURI)
//
// and H is one of MD5 or SHA256.
func
makeHA2
(
auth
*
authorization
,
method
,
uri
string
)
string
{
A2
:=
fmt
.
Sprintf
(
"%s:%s"
,
method
,
uri
)
return
hash
([]
byte
(
A2
),
auth
.
algorithm
)
}
// Response calculates the correct digest auth response. If the qop directive's
// value is "auth" or "auth-int" , then compute the response as
//
// RESPONSE = H(HA1:nonce:nonceCount:clientNonce:qop:HA2)
//
// and if the qop directive is unspecified, then compute the response as
//
// RESPONSE = H(HA1:nonce:HA2)
//
// where H is one of MD5 or SHA256.
func
response
(
auth
*
authorization
,
password
,
method
,
uri
string
)
string
{
ha1
:=
makeHA1
(
auth
.
realm
,
auth
.
username
,
password
,
auth
.
algorithm
)
ha2
:=
makeHA2
(
auth
,
method
,
uri
)
var
r
string
if
auth
.
qop
==
"auth"
||
auth
.
qop
==
"auth-int"
{
r
=
fmt
.
Sprintf
(
"%s:%s:%s:%s:%s:%s"
,
ha1
,
auth
.
nonce
,
auth
.
nc
,
auth
.
cnonce
,
auth
.
qop
,
ha2
)
}
else
{
r
=
fmt
.
Sprintf
(
"%s:%s:%s"
,
ha1
,
auth
.
nonce
,
ha2
)
}
return
hash
([]
byte
(
r
),
auth
.
algorithm
)
}
// compare is a constant-time string comparison
func
compare
(
x
,
y
string
)
bool
{
return
subtle
.
ConstantTimeCompare
([]
byte
(
x
),
[]
byte
(
y
))
==
1
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment