github.com/lianghucheng/zrddz@v0.0.0-20200923083010-c71f680932e2/src/gopkg.in/mgo.v2/internal/scram/scram.go (about)

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