You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

scram.go 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // Copyright (c) 2014 - Gustavo Niemeyer <gustavo@niemeyer.net>
  2. //
  3. // All rights reserved.
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are met:
  7. //
  8. // 1. Redistributions of source code must retain the above copyright notice, this
  9. // list of conditions and the following disclaimer.
  10. // 2. Redistributions in binary form must reproduce the above copyright notice,
  11. // this list of conditions and the following disclaimer in the documentation
  12. // and/or other materials provided with the distribution.
  13. //
  14. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  15. // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  16. // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  17. // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  18. // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  19. // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  20. // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  21. // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  23. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. // Package scram implements a SCRAM-{SHA-1,etc} client per RFC5802.
  25. //
  26. // http://tools.ietf.org/html/rfc5802
  27. //
  28. package scram
  29. import (
  30. "bytes"
  31. "crypto/hmac"
  32. "crypto/rand"
  33. "encoding/base64"
  34. "fmt"
  35. "hash"
  36. "strconv"
  37. "strings"
  38. )
  39. // Client implements a SCRAM-* client (SCRAM-SHA-1, SCRAM-SHA-256, etc).
  40. //
  41. // A Client may be used within a SASL conversation with logic resembling:
  42. //
  43. // var in []byte
  44. // var client = scram.NewClient(sha1.New, user, pass)
  45. // for client.Step(in) {
  46. // out := client.Out()
  47. // // send out to server
  48. // in := serverOut
  49. // }
  50. // if client.Err() != nil {
  51. // // auth failed
  52. // }
  53. //
  54. type Client struct {
  55. newHash func() hash.Hash
  56. user string
  57. pass string
  58. step int
  59. out bytes.Buffer
  60. err error
  61. clientNonce []byte
  62. serverNonce []byte
  63. saltedPass []byte
  64. authMsg bytes.Buffer
  65. }
  66. // NewClient returns a new SCRAM-* client with the provided hash algorithm.
  67. //
  68. // For SCRAM-SHA-256, for example, use:
  69. //
  70. // client := scram.NewClient(sha256.New, user, pass)
  71. //
  72. func NewClient(newHash func() hash.Hash, user, pass string) *Client {
  73. c := &Client{
  74. newHash: newHash,
  75. user: user,
  76. pass: pass,
  77. }
  78. c.out.Grow(256)
  79. c.authMsg.Grow(256)
  80. return c
  81. }
  82. // Out returns the data to be sent to the server in the current step.
  83. func (c *Client) Out() []byte {
  84. if c.out.Len() == 0 {
  85. return nil
  86. }
  87. return c.out.Bytes()
  88. }
  89. // Err returns the error that ocurred, or nil if there were no errors.
  90. func (c *Client) Err() error {
  91. return c.err
  92. }
  93. // SetNonce sets the client nonce to the provided value.
  94. // If not set, the nonce is generated automatically out of crypto/rand on the first step.
  95. func (c *Client) SetNonce(nonce []byte) {
  96. c.clientNonce = nonce
  97. }
  98. var escaper = strings.NewReplacer("=", "=3D", ",", "=2C")
  99. // Step processes the incoming data from the server and makes the
  100. // next round of data for the server available via Client.Out.
  101. // Step returns false if there are no errors and more data is
  102. // still expected.
  103. func (c *Client) Step(in []byte) bool {
  104. c.out.Reset()
  105. if c.step > 2 || c.err != nil {
  106. return false
  107. }
  108. c.step++
  109. switch c.step {
  110. case 1:
  111. c.err = c.step1(in)
  112. case 2:
  113. c.err = c.step2(in)
  114. case 3:
  115. c.err = c.step3(in)
  116. }
  117. return c.step > 2 || c.err != nil
  118. }
  119. func (c *Client) step1(in []byte) error {
  120. if len(c.clientNonce) == 0 {
  121. const nonceLen = 16
  122. buf := make([]byte, nonceLen+b64.EncodedLen(nonceLen))
  123. if _, err := rand.Read(buf[:nonceLen]); err != nil {
  124. return fmt.Errorf("cannot read random SCRAM-SHA-256 nonce from operating system: %v", err)
  125. }
  126. c.clientNonce = buf[nonceLen:]
  127. b64.Encode(c.clientNonce, buf[:nonceLen])
  128. }
  129. c.authMsg.WriteString("n=")
  130. escaper.WriteString(&c.authMsg, c.user)
  131. c.authMsg.WriteString(",r=")
  132. c.authMsg.Write(c.clientNonce)
  133. c.out.WriteString("n,,")
  134. c.out.Write(c.authMsg.Bytes())
  135. return nil
  136. }
  137. var b64 = base64.StdEncoding
  138. func (c *Client) step2(in []byte) error {
  139. c.authMsg.WriteByte(',')
  140. c.authMsg.Write(in)
  141. fields := bytes.Split(in, []byte(","))
  142. if len(fields) != 3 {
  143. return fmt.Errorf("expected 3 fields in first SCRAM-SHA-256 server message, got %d: %q", len(fields), in)
  144. }
  145. if !bytes.HasPrefix(fields[0], []byte("r=")) || len(fields[0]) < 2 {
  146. return fmt.Errorf("server sent an invalid SCRAM-SHA-256 nonce: %q", fields[0])
  147. }
  148. if !bytes.HasPrefix(fields[1], []byte("s=")) || len(fields[1]) < 6 {
  149. return fmt.Errorf("server sent an invalid SCRAM-SHA-256 salt: %q", fields[1])
  150. }
  151. if !bytes.HasPrefix(fields[2], []byte("i=")) || len(fields[2]) < 6 {
  152. return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
  153. }
  154. c.serverNonce = fields[0][2:]
  155. if !bytes.HasPrefix(c.serverNonce, c.clientNonce) {
  156. return fmt.Errorf("server SCRAM-SHA-256 nonce is not prefixed by client nonce: got %q, want %q+\"...\"", c.serverNonce, c.clientNonce)
  157. }
  158. salt := make([]byte, b64.DecodedLen(len(fields[1][2:])))
  159. n, err := b64.Decode(salt, fields[1][2:])
  160. if err != nil {
  161. return fmt.Errorf("cannot decode SCRAM-SHA-256 salt sent by server: %q", fields[1])
  162. }
  163. salt = salt[:n]
  164. iterCount, err := strconv.Atoi(string(fields[2][2:]))
  165. if err != nil {
  166. return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
  167. }
  168. c.saltPassword(salt, iterCount)
  169. c.authMsg.WriteString(",c=biws,r=")
  170. c.authMsg.Write(c.serverNonce)
  171. c.out.WriteString("c=biws,r=")
  172. c.out.Write(c.serverNonce)
  173. c.out.WriteString(",p=")
  174. c.out.Write(c.clientProof())
  175. return nil
  176. }
  177. func (c *Client) step3(in []byte) error {
  178. var isv, ise bool
  179. var fields = bytes.Split(in, []byte(","))
  180. if len(fields) == 1 {
  181. isv = bytes.HasPrefix(fields[0], []byte("v="))
  182. ise = bytes.HasPrefix(fields[0], []byte("e="))
  183. }
  184. if ise {
  185. return fmt.Errorf("SCRAM-SHA-256 authentication error: %s", fields[0][2:])
  186. } else if !isv {
  187. return fmt.Errorf("unsupported SCRAM-SHA-256 final message from server: %q", in)
  188. }
  189. if !bytes.Equal(c.serverSignature(), fields[0][2:]) {
  190. return fmt.Errorf("cannot authenticate SCRAM-SHA-256 server signature: %q", fields[0][2:])
  191. }
  192. return nil
  193. }
  194. func (c *Client) saltPassword(salt []byte, iterCount int) {
  195. mac := hmac.New(c.newHash, []byte(c.pass))
  196. mac.Write(salt)
  197. mac.Write([]byte{0, 0, 0, 1})
  198. ui := mac.Sum(nil)
  199. hi := make([]byte, len(ui))
  200. copy(hi, ui)
  201. for i := 1; i < iterCount; i++ {
  202. mac.Reset()
  203. mac.Write(ui)
  204. mac.Sum(ui[:0])
  205. for j, b := range ui {
  206. hi[j] ^= b
  207. }
  208. }
  209. c.saltedPass = hi
  210. }
  211. func (c *Client) clientProof() []byte {
  212. mac := hmac.New(c.newHash, c.saltedPass)
  213. mac.Write([]byte("Client Key"))
  214. clientKey := mac.Sum(nil)
  215. hash := c.newHash()
  216. hash.Write(clientKey)
  217. storedKey := hash.Sum(nil)
  218. mac = hmac.New(c.newHash, storedKey)
  219. mac.Write(c.authMsg.Bytes())
  220. clientProof := mac.Sum(nil)
  221. for i, b := range clientKey {
  222. clientProof[i] ^= b
  223. }
  224. clientProof64 := make([]byte, b64.EncodedLen(len(clientProof)))
  225. b64.Encode(clientProof64, clientProof)
  226. return clientProof64
  227. }
  228. func (c *Client) serverSignature() []byte {
  229. mac := hmac.New(c.newHash, c.saltedPass)
  230. mac.Write([]byte("Server Key"))
  231. serverKey := mac.Sum(nil)
  232. mac = hmac.New(c.newHash, serverKey)
  233. mac.Write(c.authMsg.Bytes())
  234. serverSignature := mac.Sum(nil)
  235. encoded := make([]byte, b64.EncodedLen(len(serverSignature)))
  236. b64.Encode(encoded, serverSignature)
  237. return encoded
  238. }