mirror of https://github.com/hashicorp/consul
266 lines
5.9 KiB
Go
266 lines
5.9 KiB
Go
|
/*
|
||
|
Copyright 2014 SAP SE
|
||
|
|
||
|
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
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package protocol
|
||
|
|
||
|
//Salted Challenge Response Authentication Mechanism (SCRAM)
|
||
|
|
||
|
import (
|
||
|
"crypto/hmac"
|
||
|
"crypto/rand"
|
||
|
"crypto/sha256"
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/SAP/go-hdb/internal/bufio"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
clientChallengeSize = 64
|
||
|
serverChallengeDataSize = 68
|
||
|
clientProofDataSize = 35
|
||
|
clientProofSize = 32
|
||
|
)
|
||
|
|
||
|
type scramsha256InitialRequest struct {
|
||
|
username []byte
|
||
|
clientChallenge []byte
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256InitialRequest) kind() partKind {
|
||
|
return pkAuthentication
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256InitialRequest) size() (int, error) {
|
||
|
return 2 + authFieldSize(r.username) + authFieldSize([]byte(mnSCRAMSHA256)) + authFieldSize(r.clientChallenge), nil
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256InitialRequest) numArg() int {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256InitialRequest) write(wr *bufio.Writer) error {
|
||
|
wr.WriteInt16(3)
|
||
|
writeAuthField(wr, r.username)
|
||
|
writeAuthField(wr, []byte(mnSCRAMSHA256))
|
||
|
writeAuthField(wr, r.clientChallenge)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type scramsha256InitialReply struct {
|
||
|
salt []byte
|
||
|
serverChallenge []byte
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256InitialReply) kind() partKind {
|
||
|
return pkAuthentication
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256InitialReply) setNumArg(int) {
|
||
|
//not needed
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256InitialReply) read(rd *bufio.Reader) error {
|
||
|
cnt := rd.ReadInt16()
|
||
|
if err := readMethodName(rd); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
size := rd.ReadB()
|
||
|
if size != serverChallengeDataSize {
|
||
|
return fmt.Errorf("invalid server challenge data size %d - %d expected", size, serverChallengeDataSize)
|
||
|
}
|
||
|
|
||
|
//server challenge data
|
||
|
|
||
|
cnt = rd.ReadInt16()
|
||
|
if cnt != 2 {
|
||
|
return fmt.Errorf("invalid server challenge data field count %d - %d expected", cnt, 2)
|
||
|
}
|
||
|
|
||
|
size = rd.ReadB()
|
||
|
if trace {
|
||
|
outLogger.Printf("salt size %d", size)
|
||
|
}
|
||
|
|
||
|
r.salt = make([]byte, size)
|
||
|
rd.ReadFull(r.salt)
|
||
|
if trace {
|
||
|
outLogger.Printf("salt %v", r.salt)
|
||
|
}
|
||
|
|
||
|
size = rd.ReadB()
|
||
|
r.serverChallenge = make([]byte, size)
|
||
|
rd.ReadFull(r.serverChallenge)
|
||
|
if trace {
|
||
|
outLogger.Printf("server challenge %v", r.serverChallenge)
|
||
|
}
|
||
|
|
||
|
return rd.GetError()
|
||
|
}
|
||
|
|
||
|
type scramsha256FinalRequest struct {
|
||
|
username []byte
|
||
|
clientProof []byte
|
||
|
}
|
||
|
|
||
|
func newScramsha256FinalRequest() *scramsha256FinalRequest {
|
||
|
return &scramsha256FinalRequest{}
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256FinalRequest) kind() partKind {
|
||
|
return pkAuthentication
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256FinalRequest) size() (int, error) {
|
||
|
return 2 + authFieldSize(r.username) + authFieldSize([]byte(mnSCRAMSHA256)) + authFieldSize(r.clientProof), nil
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256FinalRequest) numArg() int {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256FinalRequest) write(wr *bufio.Writer) error {
|
||
|
wr.WriteInt16(3)
|
||
|
writeAuthField(wr, r.username)
|
||
|
writeAuthField(wr, []byte(mnSCRAMSHA256))
|
||
|
writeAuthField(wr, r.clientProof)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type scramsha256FinalReply struct {
|
||
|
serverProof []byte
|
||
|
}
|
||
|
|
||
|
func newScramsha256FinalReply() *scramsha256FinalReply {
|
||
|
return &scramsha256FinalReply{}
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256FinalReply) kind() partKind {
|
||
|
return pkAuthentication
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256FinalReply) setNumArg(int) {
|
||
|
//not needed
|
||
|
}
|
||
|
|
||
|
func (r *scramsha256FinalReply) read(rd *bufio.Reader) error {
|
||
|
cnt := rd.ReadInt16()
|
||
|
if cnt != 2 {
|
||
|
return fmt.Errorf("invalid final reply field count %d - %d expected", cnt, 2)
|
||
|
}
|
||
|
if err := readMethodName(rd); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
//serverProof
|
||
|
size := rd.ReadB()
|
||
|
|
||
|
serverProof := make([]byte, size)
|
||
|
rd.ReadFull(serverProof)
|
||
|
|
||
|
return rd.GetError()
|
||
|
}
|
||
|
|
||
|
//helper
|
||
|
func authFieldSize(f []byte) int {
|
||
|
size := len(f)
|
||
|
if size >= 250 {
|
||
|
// - different indicators compared to db field handling
|
||
|
// - 1-5 bytes? but only 1 resp 3 bytes explained
|
||
|
panic("not implemented error")
|
||
|
}
|
||
|
return size + 1 //length indicator size := 1
|
||
|
}
|
||
|
|
||
|
func writeAuthField(wr *bufio.Writer, f []byte) {
|
||
|
size := len(f)
|
||
|
if size >= 250 {
|
||
|
// - different indicators compared to db field handling
|
||
|
// - 1-5 bytes? but only 1 resp 3 bytes explained
|
||
|
panic("not implemented error")
|
||
|
}
|
||
|
|
||
|
wr.WriteB(byte(size))
|
||
|
wr.Write(f)
|
||
|
}
|
||
|
|
||
|
func readMethodName(rd *bufio.Reader) error {
|
||
|
size := rd.ReadB()
|
||
|
methodName := make([]byte, size)
|
||
|
rd.ReadFull(methodName)
|
||
|
if string(methodName) != mnSCRAMSHA256 {
|
||
|
return fmt.Errorf("invalid authentication method %s - %s expected", methodName, mnSCRAMSHA256)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func clientChallenge() []byte {
|
||
|
r := make([]byte, clientChallengeSize)
|
||
|
if _, err := rand.Read(r); err != nil {
|
||
|
outLogger.Fatal("client challenge fatal error")
|
||
|
}
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
func clientProof(salt, serverChallenge, clientChallenge, password []byte) []byte {
|
||
|
|
||
|
clientProof := make([]byte, clientProofDataSize)
|
||
|
|
||
|
buf := make([]byte, 0, len(salt)+len(serverChallenge)+len(clientChallenge))
|
||
|
buf = append(buf, salt...)
|
||
|
buf = append(buf, serverChallenge...)
|
||
|
buf = append(buf, clientChallenge...)
|
||
|
|
||
|
key := _sha256(_hmac(password, salt))
|
||
|
sig := _hmac(_sha256(key), buf)
|
||
|
|
||
|
proof := xor(sig, key)
|
||
|
//actual implementation: only one salt value?
|
||
|
clientProof[0] = 0
|
||
|
clientProof[1] = 1
|
||
|
clientProof[2] = clientProofSize
|
||
|
copy(clientProof[3:], proof)
|
||
|
return clientProof
|
||
|
}
|
||
|
|
||
|
func _sha256(p []byte) []byte {
|
||
|
hash := sha256.New()
|
||
|
hash.Write(p)
|
||
|
s := hash.Sum(nil)
|
||
|
if trace {
|
||
|
outLogger.Printf("sha length %d value %v", len(s), s)
|
||
|
}
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func _hmac(key, p []byte) []byte {
|
||
|
hash := hmac.New(sha256.New, key)
|
||
|
hash.Write(p)
|
||
|
s := hash.Sum(nil)
|
||
|
if trace {
|
||
|
outLogger.Printf("hmac length %d value %v", len(s), s)
|
||
|
}
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func xor(sig, key []byte) []byte {
|
||
|
r := make([]byte, len(sig))
|
||
|
|
||
|
for i, v := range sig {
|
||
|
r[i] = v ^ key[i]
|
||
|
}
|
||
|
return r
|
||
|
}
|