open-consul/vendor/github.com/SAP/go-hdb/internal/protocol/scramsha256.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
}