123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- // Copyright 2018 by David A. Golden. All rights reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License"); you may
- // not use this file except in compliance with the License. You may obtain
- // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
-
- package scram
-
- import (
- "sync"
-
- "github.com/xdg-go/pbkdf2"
- )
-
- // Client implements the client side of SCRAM authentication. It holds
- // configuration values needed to initialize new client-side conversations for
- // a specific username, password and authorization ID tuple. Client caches
- // the computationally-expensive parts of a SCRAM conversation as described in
- // RFC-5802. If repeated authentication conversations may be required for a
- // user (e.g. disconnect/reconnect), the user's Client should be preserved.
- //
- // For security reasons, Clients have a default minimum PBKDF2 iteration count
- // of 4096. If a server requests a smaller iteration count, an authentication
- // conversation will error.
- //
- // A Client can also be used by a server application to construct the hashed
- // authentication values to be stored for a new user. See StoredCredentials()
- // for more.
- type Client struct {
- sync.RWMutex
- username string
- password string
- authzID string
- minIters int
- nonceGen NonceGeneratorFcn
- hashGen HashGeneratorFcn
- cache map[KeyFactors]derivedKeys
- }
-
- func newClient(username, password, authzID string, fcn HashGeneratorFcn) *Client {
- return &Client{
- username: username,
- password: password,
- authzID: authzID,
- minIters: 4096,
- nonceGen: defaultNonceGenerator,
- hashGen: fcn,
- cache: make(map[KeyFactors]derivedKeys),
- }
- }
-
- // WithMinIterations changes minimum required PBKDF2 iteration count.
- func (c *Client) WithMinIterations(n int) *Client {
- c.Lock()
- defer c.Unlock()
- c.minIters = n
- return c
- }
-
- // WithNonceGenerator replaces the default nonce generator (base64 encoding of
- // 24 bytes from crypto/rand) with a custom generator. This is provided for
- // testing or for users with custom nonce requirements.
- func (c *Client) WithNonceGenerator(ng NonceGeneratorFcn) *Client {
- c.Lock()
- defer c.Unlock()
- c.nonceGen = ng
- return c
- }
-
- // NewConversation constructs a client-side authentication conversation.
- // Conversations cannot be reused, so this must be called for each new
- // authentication attempt.
- func (c *Client) NewConversation() *ClientConversation {
- c.RLock()
- defer c.RUnlock()
- return &ClientConversation{
- client: c,
- nonceGen: c.nonceGen,
- hashGen: c.hashGen,
- minIters: c.minIters,
- }
- }
-
- func (c *Client) getDerivedKeys(kf KeyFactors) derivedKeys {
- dk, ok := c.getCache(kf)
- if !ok {
- dk = c.computeKeys(kf)
- c.setCache(kf, dk)
- }
- return dk
- }
-
- // GetStoredCredentials takes a salt and iteration count structure and
- // provides the values that must be stored by a server to authentication a
- // user. These values are what the Server credential lookup function must
- // return for a given username.
- func (c *Client) GetStoredCredentials(kf KeyFactors) StoredCredentials {
- dk := c.getDerivedKeys(kf)
- return StoredCredentials{
- KeyFactors: kf,
- StoredKey: dk.StoredKey,
- ServerKey: dk.ServerKey,
- }
- }
-
- func (c *Client) computeKeys(kf KeyFactors) derivedKeys {
- h := c.hashGen()
- saltedPassword := pbkdf2.Key([]byte(c.password), []byte(kf.Salt), kf.Iters, h.Size(), c.hashGen)
- clientKey := computeHMAC(c.hashGen, saltedPassword, []byte("Client Key"))
-
- return derivedKeys{
- ClientKey: clientKey,
- StoredKey: computeHash(c.hashGen, clientKey),
- ServerKey: computeHMAC(c.hashGen, saltedPassword, []byte("Server Key")),
- }
- }
-
- func (c *Client) getCache(kf KeyFactors) (derivedKeys, bool) {
- c.RLock()
- defer c.RUnlock()
- dk, ok := c.cache[kf]
- return dk, ok
- }
-
- func (c *Client) setCache(kf KeyFactors, dk derivedKeys) {
- c.Lock()
- defer c.Unlock()
- c.cache[kf] = dk
- return
- }
|