digest.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package digest
  2. import (
  3. "crypto/md5"
  4. "crypto/rand"
  5. "crypto/sha256"
  6. "crypto/sha512"
  7. "encoding/hex"
  8. "fmt"
  9. "hash"
  10. "io"
  11. "net/http"
  12. "strings"
  13. )
  14. // Prefix for digest authentication headers
  15. const Prefix = "Digest "
  16. // IsDigest returns true if the header value is a digest auth header
  17. func IsDigest(header string) bool {
  18. return strings.HasPrefix(header, Prefix)
  19. }
  20. // Options for creating a credentials
  21. type Options struct {
  22. Method string
  23. URI string
  24. GetBody func() (io.ReadCloser, error)
  25. Count int
  26. Username string
  27. Password string
  28. // The following are provided for advanced use cases where the client needs
  29. // to override the default digest calculation behavior. Most users should
  30. // leave these fields unset.
  31. A1 string
  32. Cnonce string
  33. }
  34. // CanDigest checks if the algorithm and qop are supported
  35. func CanDigest(c *Challenge) bool {
  36. switch strings.ToUpper(c.Algorithm) {
  37. case "", "MD5", "SHA-256", "SHA-512", "SHA-512-256":
  38. default:
  39. return false
  40. }
  41. return len(c.QOP) == 0 || c.SupportsQOP("auth") || c.SupportsQOP("auth-int")
  42. }
  43. // Digest creates credentials from a challenge and request options.
  44. // Note: if you want to re-use a challenge, you must increment the Count.
  45. func Digest(chal *Challenge, o Options) (*Credentials, error) {
  46. cred := &Credentials{
  47. Username: o.Username,
  48. URI: o.URI,
  49. Cnonce: o.Cnonce,
  50. Nc: o.Count,
  51. Realm: chal.Realm,
  52. Nonce: chal.Nonce,
  53. Algorithm: chal.Algorithm,
  54. Opaque: chal.Opaque,
  55. Userhash: chal.Userhash,
  56. }
  57. // we re-use the same hash.Hash
  58. var h hash.Hash
  59. switch strings.ToUpper(cred.Algorithm) {
  60. case "", "MD5":
  61. h = md5.New()
  62. case "SHA-256":
  63. h = sha256.New()
  64. case "SHA-512":
  65. h = sha512.New()
  66. case "SHA-512-256":
  67. h = sha512.New512_256()
  68. default:
  69. return nil, fmt.Errorf("digest: unsupported algorithm: %q", cred.Algorithm)
  70. }
  71. // hash the username if requested
  72. if cred.Userhash {
  73. cred.Username = hashf(h, "%s:%s", o.Username, cred.Realm)
  74. }
  75. // generate the a1 hash if one was not provided
  76. a1 := o.A1
  77. if a1 == "" {
  78. a1 = hashf(h, "%s:%s:%s", o.Username, cred.Realm, o.Password)
  79. }
  80. // generate the response
  81. switch {
  82. case len(chal.QOP) == 0:
  83. cred.Response = hashf(h, "%s:%s:%s",
  84. a1,
  85. cred.Nonce,
  86. hashf(h, "%s:%s", o.Method, o.URI), // A2
  87. )
  88. case chal.SupportsQOP("auth"):
  89. cred.QOP = "auth"
  90. if cred.Cnonce == "" {
  91. cred.Cnonce = cnonce()
  92. }
  93. if cred.Nc == 0 {
  94. cred.Nc = 1
  95. }
  96. cred.Response = hashf(h, "%s:%s:%08x:%s:%s:%s",
  97. a1,
  98. cred.Nonce,
  99. cred.Nc,
  100. cred.Cnonce,
  101. cred.QOP,
  102. hashf(h, "%s:%s", o.Method, o.URI), // A2
  103. )
  104. case chal.SupportsQOP("auth-int"):
  105. cred.QOP = "auth-int"
  106. if cred.Cnonce == "" {
  107. cred.Cnonce = cnonce()
  108. }
  109. if cred.Nc == 0 {
  110. cred.Nc = 1
  111. }
  112. hbody, err := hashbody(h, o.GetBody)
  113. if err != nil {
  114. return nil, fmt.Errorf("digest: failed to read body for auth-int: %w", err)
  115. }
  116. cred.Response = hashf(h, "%s:%s:%08x:%s:%s:%s",
  117. a1,
  118. cred.Nonce,
  119. cred.Nc,
  120. cred.Cnonce,
  121. cred.QOP,
  122. hashf(h, "%s:%s:%s", o.Method, o.URI, hbody), // A2
  123. )
  124. default:
  125. return nil, fmt.Errorf("digest: unsupported qop: %q", strings.Join(chal.QOP, ","))
  126. }
  127. return cred, nil
  128. }
  129. func hashf(h hash.Hash, format string, args ...interface{}) string {
  130. h.Reset()
  131. fmt.Fprintf(h, format, args...)
  132. return hex.EncodeToString(h.Sum(nil))
  133. }
  134. func hashbody(h hash.Hash, getbody func() (io.ReadCloser, error)) (string, error) {
  135. h.Reset()
  136. if getbody != nil {
  137. r, err := getbody()
  138. if err != nil {
  139. return "", err
  140. }
  141. defer r.Close()
  142. if r != http.NoBody {
  143. if _, err := io.Copy(h, r); err != nil {
  144. return "", err
  145. }
  146. }
  147. }
  148. return hex.EncodeToString(h.Sum(nil)), nil
  149. }
  150. func cnonce() string {
  151. b := make([]byte, 8)
  152. io.ReadFull(rand.Reader, b)
  153. return hex.EncodeToString(b)
  154. }