mirror of
https://github.com/ergochat/ergo.git
synced 2025-12-23 11:32:02 -08:00
implement SCRAM-SHA-256
This commit is contained in:
parent
3264687803
commit
e1401934df
27 changed files with 1545 additions and 10 deletions
149
vendor/github.com/xdg-go/scram/client_conv.go
generated
vendored
Normal file
149
vendor/github.com/xdg-go/scram/client_conv.go
generated
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// 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 (
|
||||
"crypto/hmac"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type clientState int
|
||||
|
||||
const (
|
||||
clientStarting clientState = iota
|
||||
clientFirst
|
||||
clientFinal
|
||||
clientDone
|
||||
)
|
||||
|
||||
// ClientConversation implements the client-side of an authentication
|
||||
// conversation with a server. A new conversation must be created for
|
||||
// each authentication attempt.
|
||||
type ClientConversation struct {
|
||||
client *Client
|
||||
nonceGen NonceGeneratorFcn
|
||||
hashGen HashGeneratorFcn
|
||||
minIters int
|
||||
state clientState
|
||||
valid bool
|
||||
gs2 string
|
||||
nonce string
|
||||
c1b string
|
||||
serveSig []byte
|
||||
}
|
||||
|
||||
// Step takes a string provided from a server (or just an empty string for the
|
||||
// very first conversation step) and attempts to move the authentication
|
||||
// conversation forward. It returns a string to be sent to the server or an
|
||||
// error if the server message is invalid. Calling Step after a conversation
|
||||
// completes is also an error.
|
||||
func (cc *ClientConversation) Step(challenge string) (response string, err error) {
|
||||
switch cc.state {
|
||||
case clientStarting:
|
||||
cc.state = clientFirst
|
||||
response, err = cc.firstMsg()
|
||||
case clientFirst:
|
||||
cc.state = clientFinal
|
||||
response, err = cc.finalMsg(challenge)
|
||||
case clientFinal:
|
||||
cc.state = clientDone
|
||||
response, err = cc.validateServer(challenge)
|
||||
default:
|
||||
response, err = "", errors.New("Conversation already completed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Done returns true if the conversation is completed or has errored.
|
||||
func (cc *ClientConversation) Done() bool {
|
||||
return cc.state == clientDone
|
||||
}
|
||||
|
||||
// Valid returns true if the conversation successfully authenticated with the
|
||||
// server, including counter-validation that the server actually has the
|
||||
// user's stored credentials.
|
||||
func (cc *ClientConversation) Valid() bool {
|
||||
return cc.valid
|
||||
}
|
||||
|
||||
func (cc *ClientConversation) firstMsg() (string, error) {
|
||||
// Values are cached for use in final message parameters
|
||||
cc.gs2 = cc.gs2Header()
|
||||
cc.nonce = cc.client.nonceGen()
|
||||
cc.c1b = fmt.Sprintf("n=%s,r=%s", encodeName(cc.client.username), cc.nonce)
|
||||
|
||||
return cc.gs2 + cc.c1b, nil
|
||||
}
|
||||
|
||||
func (cc *ClientConversation) finalMsg(s1 string) (string, error) {
|
||||
msg, err := parseServerFirst(s1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Check nonce prefix and update
|
||||
if !strings.HasPrefix(msg.nonce, cc.nonce) {
|
||||
return "", errors.New("server nonce did not extend client nonce")
|
||||
}
|
||||
cc.nonce = msg.nonce
|
||||
|
||||
// Check iteration count vs minimum
|
||||
if msg.iters < cc.minIters {
|
||||
return "", fmt.Errorf("server requested too few iterations (%d)", msg.iters)
|
||||
}
|
||||
|
||||
// Create client-final-message-without-proof
|
||||
c2wop := fmt.Sprintf(
|
||||
"c=%s,r=%s",
|
||||
base64.StdEncoding.EncodeToString([]byte(cc.gs2)),
|
||||
cc.nonce,
|
||||
)
|
||||
|
||||
// Create auth message
|
||||
authMsg := cc.c1b + "," + s1 + "," + c2wop
|
||||
|
||||
// Get derived keys from client cache
|
||||
dk := cc.client.getDerivedKeys(KeyFactors{Salt: string(msg.salt), Iters: msg.iters})
|
||||
|
||||
// Create proof as clientkey XOR clientsignature
|
||||
clientSignature := computeHMAC(cc.hashGen, dk.StoredKey, []byte(authMsg))
|
||||
clientProof := xorBytes(dk.ClientKey, clientSignature)
|
||||
proof := base64.StdEncoding.EncodeToString(clientProof)
|
||||
|
||||
// Cache ServerSignature for later validation
|
||||
cc.serveSig = computeHMAC(cc.hashGen, dk.ServerKey, []byte(authMsg))
|
||||
|
||||
return fmt.Sprintf("%s,p=%s", c2wop, proof), nil
|
||||
}
|
||||
|
||||
func (cc *ClientConversation) validateServer(s2 string) (string, error) {
|
||||
msg, err := parseServerFinal(s2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(msg.err) > 0 {
|
||||
return "", fmt.Errorf("server error: %s", msg.err)
|
||||
}
|
||||
|
||||
if !hmac.Equal(msg.verifier, cc.serveSig) {
|
||||
return "", errors.New("server validation failed")
|
||||
}
|
||||
|
||||
cc.valid = true
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (cc *ClientConversation) gs2Header() string {
|
||||
if cc.client.authzID == "" {
|
||||
return "n,,"
|
||||
}
|
||||
return fmt.Sprintf("n,%s,", encodeName(cc.client.authzID))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue