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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. // Copyright 2018 by David A. Golden. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  6. package scram
  7. import (
  8. "sync"
  9. "github.com/xdg-go/pbkdf2"
  10. )
  11. // Client implements the client side of SCRAM authentication. It holds
  12. // configuration values needed to initialize new client-side conversations for
  13. // a specific username, password and authorization ID tuple. Client caches
  14. // the computationally-expensive parts of a SCRAM conversation as described in
  15. // RFC-5802. If repeated authentication conversations may be required for a
  16. // user (e.g. disconnect/reconnect), the user's Client should be preserved.
  17. //
  18. // For security reasons, Clients have a default minimum PBKDF2 iteration count
  19. // of 4096. If a server requests a smaller iteration count, an authentication
  20. // conversation will error.
  21. //
  22. // A Client can also be used by a server application to construct the hashed
  23. // authentication values to be stored for a new user. See StoredCredentials()
  24. // for more.
  25. type Client struct {
  26. sync.RWMutex
  27. username string
  28. password string
  29. authzID string
  30. minIters int
  31. nonceGen NonceGeneratorFcn
  32. hashGen HashGeneratorFcn
  33. cache map[KeyFactors]derivedKeys
  34. }
  35. func newClient(username, password, authzID string, fcn HashGeneratorFcn) *Client {
  36. return &Client{
  37. username: username,
  38. password: password,
  39. authzID: authzID,
  40. minIters: 4096,
  41. nonceGen: defaultNonceGenerator,
  42. hashGen: fcn,
  43. cache: make(map[KeyFactors]derivedKeys),
  44. }
  45. }
  46. // WithMinIterations changes minimum required PBKDF2 iteration count.
  47. func (c *Client) WithMinIterations(n int) *Client {
  48. c.Lock()
  49. defer c.Unlock()
  50. c.minIters = n
  51. return c
  52. }
  53. // WithNonceGenerator replaces the default nonce generator (base64 encoding of
  54. // 24 bytes from crypto/rand) with a custom generator. This is provided for
  55. // testing or for users with custom nonce requirements.
  56. func (c *Client) WithNonceGenerator(ng NonceGeneratorFcn) *Client {
  57. c.Lock()
  58. defer c.Unlock()
  59. c.nonceGen = ng
  60. return c
  61. }
  62. // NewConversation constructs a client-side authentication conversation.
  63. // Conversations cannot be reused, so this must be called for each new
  64. // authentication attempt.
  65. func (c *Client) NewConversation() *ClientConversation {
  66. c.RLock()
  67. defer c.RUnlock()
  68. return &ClientConversation{
  69. client: c,
  70. nonceGen: c.nonceGen,
  71. hashGen: c.hashGen,
  72. minIters: c.minIters,
  73. }
  74. }
  75. func (c *Client) getDerivedKeys(kf KeyFactors) derivedKeys {
  76. dk, ok := c.getCache(kf)
  77. if !ok {
  78. dk = c.computeKeys(kf)
  79. c.setCache(kf, dk)
  80. }
  81. return dk
  82. }
  83. // GetStoredCredentials takes a salt and iteration count structure and
  84. // provides the values that must be stored by a server to authentication a
  85. // user. These values are what the Server credential lookup function must
  86. // return for a given username.
  87. func (c *Client) GetStoredCredentials(kf KeyFactors) StoredCredentials {
  88. dk := c.getDerivedKeys(kf)
  89. return StoredCredentials{
  90. KeyFactors: kf,
  91. StoredKey: dk.StoredKey,
  92. ServerKey: dk.ServerKey,
  93. }
  94. }
  95. func (c *Client) computeKeys(kf KeyFactors) derivedKeys {
  96. h := c.hashGen()
  97. saltedPassword := pbkdf2.Key([]byte(c.password), []byte(kf.Salt), kf.Iters, h.Size(), c.hashGen)
  98. clientKey := computeHMAC(c.hashGen, saltedPassword, []byte("Client Key"))
  99. return derivedKeys{
  100. ClientKey: clientKey,
  101. StoredKey: computeHash(c.hashGen, clientKey),
  102. ServerKey: computeHMAC(c.hashGen, saltedPassword, []byte("Server Key")),
  103. }
  104. }
  105. func (c *Client) getCache(kf KeyFactors) (derivedKeys, bool) {
  106. c.RLock()
  107. defer c.RUnlock()
  108. dk, ok := c.cache[kf]
  109. return dk, ok
  110. }
  111. func (c *Client) setCache(kf KeyFactors, dk derivedKeys) {
  112. c.Lock()
  113. defer c.Unlock()
  114. c.cache[kf] = dk
  115. return
  116. }