parent
83d6e5e3f8
commit
3fb038c53a
@ -0,0 +1,264 @@ |
||||
// Copyright (c) 2014 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Pacakage scram implements a SCRAM-{SHA-1,etc} client per RFC5802.
|
||||
//
|
||||
// http://tools.ietf.org/html/rfc5802
|
||||
//
|
||||
package scram |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/hmac" |
||||
"crypto/rand" |
||||
"encoding/base64" |
||||
"fmt" |
||||
"hash" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
// Client implements a SCRAM-* client (SCRAM-SHA-1, SCRAM-SHA-256, etc).
|
||||
//
|
||||
// A Client may be used within a SASL conversation with logic resembling:
|
||||
//
|
||||
// var in []byte
|
||||
// var client = scram.NewClient(sha1.New, user, pass)
|
||||
// for client.Step(in) {
|
||||
// out := client.Out()
|
||||
// // send out to server
|
||||
// in := serverOut
|
||||
// }
|
||||
// if client.Err() != nil {
|
||||
// // auth failed
|
||||
// }
|
||||
//
|
||||
type Client struct { |
||||
newHash func() hash.Hash |
||||
|
||||
user string |
||||
pass string |
||||
step int |
||||
out bytes.Buffer |
||||
err error |
||||
|
||||
clientNonce []byte |
||||
serverNonce []byte |
||||
saltedPass []byte |
||||
authMsg bytes.Buffer |
||||
} |
||||
|
||||
// NewClient returns a new SCRAM-* client with the provided hash algorithm.
|
||||
//
|
||||
// For SCRAM-SHA-256, for example, use:
|
||||
//
|
||||
// client := scram.NewClient(sha256.New, user, pass)
|
||||
//
|
||||
func NewClient(newHash func() hash.Hash, user, pass string) *Client { |
||||
c := &Client{ |
||||
newHash: newHash, |
||||
user: user, |
||||
pass: pass, |
||||
} |
||||
c.out.Grow(256) |
||||
c.authMsg.Grow(256) |
||||
return c |
||||
} |
||||
|
||||
// Out returns the data to be sent to the server in the current step.
|
||||
func (c *Client) Out() []byte { |
||||
if c.out.Len() == 0 { |
||||
return nil |
||||
} |
||||
return c.out.Bytes() |
||||
} |
||||
|
||||
// Err returns the error that ocurred, or nil if there were no errors.
|
||||
func (c *Client) Err() error { |
||||
return c.err |
||||
} |
||||
|
||||
// SetNonce sets the client nonce to the provided value.
|
||||
// If not set, the nonce is generated automatically out of crypto/rand on the first step.
|
||||
func (c *Client) SetNonce(nonce []byte) { |
||||
c.clientNonce = nonce |
||||
} |
||||
|
||||
var escaper = strings.NewReplacer("=", "=3D", ",", "=2C") |
||||
|
||||
// Step processes the incoming data from the server and makes the
|
||||
// next round of data for the server available via Client.Out.
|
||||
// Step returns false if there are no errors and more data is
|
||||
// still expected.
|
||||
func (c *Client) Step(in []byte) bool { |
||||
c.out.Reset() |
||||
if c.step > 2 || c.err != nil { |
||||
return false |
||||
} |
||||
c.step++ |
||||
switch c.step { |
||||
case 1: |
||||
c.err = c.step1(in) |
||||
case 2: |
||||
c.err = c.step2(in) |
||||
case 3: |
||||
c.err = c.step3(in) |
||||
} |
||||
return c.step > 2 || c.err != nil |
||||
} |
||||
|
||||
func (c *Client) step1(in []byte) error { |
||||
if len(c.clientNonce) == 0 { |
||||
const nonceLen = 16 |
||||
buf := make([]byte, nonceLen+b64.EncodedLen(nonceLen)) |
||||
if _, err := rand.Read(buf[:nonceLen]); err != nil { |
||||
return fmt.Errorf("cannot read random SCRAM-SHA-256 nonce from operating system: %v", err) |
||||
} |
||||
c.clientNonce = buf[nonceLen:] |
||||
b64.Encode(c.clientNonce, buf[:nonceLen]) |
||||
} |
||||
c.authMsg.WriteString("n=") |
||||
escaper.WriteString(&c.authMsg, c.user) |
||||
c.authMsg.WriteString(",r=") |
||||
c.authMsg.Write(c.clientNonce) |
||||
|
||||
c.out.WriteString("n,,") |
||||
c.out.Write(c.authMsg.Bytes()) |
||||
return nil |
||||
} |
||||
|
||||
var b64 = base64.StdEncoding |
||||
|
||||
func (c *Client) step2(in []byte) error { |
||||
c.authMsg.WriteByte(',') |
||||
c.authMsg.Write(in) |
||||
|
||||
fields := bytes.Split(in, []byte(",")) |
||||
if len(fields) != 3 { |
||||
return fmt.Errorf("expected 3 fields in first SCRAM-SHA-256 server message, got %d: %q", len(fields), in) |
||||
} |
||||
if !bytes.HasPrefix(fields[0], []byte("r=")) || len(fields[0]) < 2 { |
||||
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 nonce: %q", fields[0]) |
||||
} |
||||
if !bytes.HasPrefix(fields[1], []byte("s=")) || len(fields[1]) < 6 { |
||||
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 salt: %q", fields[1]) |
||||
} |
||||
if !bytes.HasPrefix(fields[2], []byte("i=")) || len(fields[2]) < 6 { |
||||
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2]) |
||||
} |
||||
|
||||
c.serverNonce = fields[0][2:] |
||||
if !bytes.HasPrefix(c.serverNonce, c.clientNonce) { |
||||
return fmt.Errorf("server SCRAM-SHA-256 nonce is not prefixed by client nonce: got %q, want %q+\"...\"", c.serverNonce, c.clientNonce) |
||||
} |
||||
|
||||
salt := make([]byte, b64.DecodedLen(len(fields[1][2:]))) |
||||
n, err := b64.Decode(salt, fields[1][2:]) |
||||
if err != nil { |
||||
return fmt.Errorf("cannot decode SCRAM-SHA-256 salt sent by server: %q", fields[1]) |
||||
} |
||||
salt = salt[:n] |
||||
iterCount, err := strconv.Atoi(string(fields[2][2:])) |
||||
if err != nil { |
||||
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2]) |
||||
} |
||||
c.saltPassword(salt, iterCount) |
||||
|
||||
c.authMsg.WriteString(",c=biws,r=") |
||||
c.authMsg.Write(c.serverNonce) |
||||
|
||||
c.out.WriteString("c=biws,r=") |
||||
c.out.Write(c.serverNonce) |
||||
c.out.WriteString(",p=") |
||||
c.out.Write(c.clientProof()) |
||||
return nil |
||||
} |
||||
|
||||
func (c *Client) step3(in []byte) error { |
||||
var isv, ise bool |
||||
var fields = bytes.Split(in, []byte(",")) |
||||
if len(fields) == 1 { |
||||
isv = bytes.HasPrefix(fields[0], []byte("v=")) |
||||
ise = bytes.HasPrefix(fields[0], []byte("e=")) |
||||
} |
||||
if ise { |
||||
return fmt.Errorf("SCRAM-SHA-256 authentication error: %s", fields[0][2:]) |
||||
} else if !isv { |
||||
return fmt.Errorf("unsupported SCRAM-SHA-256 final message from server: %q", in) |
||||
} |
||||
if !bytes.Equal(c.serverSignature(), fields[0][2:]) { |
||||
return fmt.Errorf("cannot authenticate SCRAM-SHA-256 server signature: %q", fields[0][2:]) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (c *Client) saltPassword(salt []byte, iterCount int) { |
||||
mac := hmac.New(c.newHash, []byte(c.pass)) |
||||
mac.Write(salt) |
||||
mac.Write([]byte{0, 0, 0, 1}) |
||||
ui := mac.Sum(nil) |
||||
hi := make([]byte, len(ui)) |
||||
copy(hi, ui) |
||||
for i := 1; i < iterCount; i++ { |
||||
mac.Reset() |
||||
mac.Write(ui) |
||||
mac.Sum(ui[:0]) |
||||
for j, b := range ui { |
||||
hi[j] ^= b |
||||
} |
||||
} |
||||
c.saltedPass = hi |
||||
} |
||||
|
||||
func (c *Client) clientProof() []byte { |
||||
mac := hmac.New(c.newHash, c.saltedPass) |
||||
mac.Write([]byte("Client Key")) |
||||
clientKey := mac.Sum(nil) |
||||
hash := c.newHash() |
||||
hash.Write(clientKey) |
||||
storedKey := hash.Sum(nil) |
||||
mac = hmac.New(c.newHash, storedKey) |
||||
mac.Write(c.authMsg.Bytes()) |
||||
clientProof := mac.Sum(nil) |
||||
for i, b := range clientKey { |
||||
clientProof[i] ^= b |
||||
} |
||||
clientProof64 := make([]byte, b64.EncodedLen(len(clientProof))) |
||||
b64.Encode(clientProof64, clientProof) |
||||
return clientProof64 |
||||
} |
||||
|
||||
func (c *Client) serverSignature() []byte { |
||||
mac := hmac.New(c.newHash, c.saltedPass) |
||||
mac.Write([]byte("Server Key")) |
||||
serverKey := mac.Sum(nil) |
||||
|
||||
mac = hmac.New(c.newHash, serverKey) |
||||
mac.Write(c.authMsg.Bytes()) |
||||
serverSignature := mac.Sum(nil) |
||||
|
||||
encoded := make([]byte, b64.EncodedLen(len(serverSignature))) |
||||
b64.Encode(encoded, serverSignature) |
||||
return encoded |
||||
} |
@ -1,14 +0,0 @@ |
||||
// +build go1.7
|
||||
|
||||
package pq |
||||
|
||||
import "crypto/tls" |
||||
|
||||
// Accept renegotiation requests initiated by the backend.
|
||||
//
|
||||
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
|
||||
// the default configuration of older versions has it enabled. Redshift
|
||||
// also initiates renegotiations and cannot be reconfigured.
|
||||
func sslRenegotiation(conf *tls.Config) { |
||||
conf.Renegotiation = tls.RenegotiateFreelyAsClient |
||||
} |
@ -1,8 +0,0 @@ |
||||
// +build !go1.7
|
||||
|
||||
package pq |
||||
|
||||
import "crypto/tls" |
||||
|
||||
// Renegotiation is not supported by crypto/tls until Go 1.7.
|
||||
func sslRenegotiation(*tls.Config) {} |
Loading…
Reference in new issue