148 lines
5.1 KiB
Go
148 lines
5.1 KiB
Go
// Copyright 2024 Dolthub, Inc.
|
|
//
|
|
// 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 rfc5802
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
|
|
"github.com/xdg-go/stringprep"
|
|
"golang.org/x/crypto/pbkdf2"
|
|
)
|
|
|
|
var (
|
|
clientKeyConstant = OctetString("Client Key")
|
|
serverKeyConstant = OctetString("Server Key")
|
|
)
|
|
|
|
// OctetString is equivalent to a byte slice. An octet, as defined in the RFC, is an 8-bit byte. Go only supports 8-bit
|
|
// bytes. Additionally, an octet string is defined as a sequence of octets, which we can represent as a slice.
|
|
type OctetString []byte
|
|
|
|
// Base64ToOctetString returns the original octet string from its base64 encoded form.
|
|
func Base64ToOctetString(base64String string) OctetString {
|
|
decoded, err := base64.StdEncoding.DecodeString(base64String)
|
|
if err != nil {
|
|
// If we've encountered an error, then we'll return the equivalent of an empty hash. This will fail in a later step.
|
|
return make(OctetString, 32)
|
|
}
|
|
return decoded
|
|
}
|
|
|
|
// ClientKey returns the client key created using the salted password and a specific constant.
|
|
func ClientKey(saltedPassword OctetString) OctetString {
|
|
return HMAC(saltedPassword, clientKeyConstant)
|
|
}
|
|
|
|
// ClientProof returns the client proof by xor'ing the client key and client signature.
|
|
func ClientProof(clientKey OctetString, clientSignature OctetString) OctetString {
|
|
if len(clientKey) != len(clientSignature) {
|
|
return make(OctetString, 32)
|
|
}
|
|
return clientKey.Xor(clientSignature)
|
|
}
|
|
|
|
// ClientSignature returns the client signature using the given stored key and auth message.
|
|
func ClientSignature(storedKey OctetString, authMessage string) OctetString {
|
|
return HMAC(storedKey, OctetString(authMessage))
|
|
}
|
|
|
|
// H performs the SHA256 hash function, which is the hash function used by Postgres. The returned OctetString will
|
|
// always have a length of 32.
|
|
func H(str OctetString) OctetString {
|
|
ret := sha256.Sum256(str)
|
|
return ret[:]
|
|
}
|
|
|
|
// Hi is, essentially, PBKDF2 with HMAC as the pseudorandom function.
|
|
func Hi(str OctetString, salt OctetString, i uint32) OctetString {
|
|
return pbkdf2.Key(str, salt, int(i), 32, sha256.New)
|
|
}
|
|
|
|
// HMAC applies the HMAC keyed hash algorithm on the given octet strings.
|
|
func HMAC(key OctetString, str OctetString) OctetString {
|
|
mac := hmac.New(sha256.New, key)
|
|
mac.Write(str)
|
|
return mac.Sum(nil)
|
|
}
|
|
|
|
// Normalize runs the SASLprep profile (https://datatracker.ietf.org/doc/html/rfc4013) of the stringprep algorithm
|
|
// (https://datatracker.ietf.org/doc/html/rfc3454). This accepts a standard UTF8 encoded string, unlike other functions
|
|
// which may take an OctetString.
|
|
func Normalize(str string) (string, error) {
|
|
return stringprep.SASLprep.Prepare(str)
|
|
}
|
|
|
|
// SaltedPassword returns the salted password. The password should not have been normalized, as it is normalized within
|
|
// the function.
|
|
func SaltedPassword(password string, salt OctetString, i uint32) (OctetString, error) {
|
|
normalizedPassword, err := Normalize(password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Hi(OctetString(normalizedPassword), salt, i), nil
|
|
}
|
|
|
|
// ServerKey returns the server key created using the salted password and a specific constant.
|
|
func ServerKey(saltedPassword OctetString) OctetString {
|
|
return HMAC(saltedPassword, serverKeyConstant)
|
|
}
|
|
|
|
// ServerSignature returns the server signature using the given server key and auth message.
|
|
func ServerSignature(serverKey OctetString, authMessage string) OctetString {
|
|
return HMAC(serverKey, OctetString(authMessage))
|
|
}
|
|
|
|
// StoredKey returns the stored key created using the client key.
|
|
func StoredKey(clientKey OctetString) OctetString {
|
|
return H(clientKey)
|
|
}
|
|
|
|
// AppendInteger appends the given integer.
|
|
func (os OctetString) AppendInteger(val uint32) OctetString {
|
|
result := make(OctetString, len(os)+4)
|
|
binary.BigEndian.PutUint32(result[len(os):], val)
|
|
return result
|
|
}
|
|
|
|
// Equals returns whether the calling octet string is equal to the given octet string.
|
|
func (os OctetString) Equals(other OctetString) bool {
|
|
return bytes.Equal(os, other)
|
|
}
|
|
|
|
// ToBase64 returns the OctetString as a base64 encoded UTF8 string.
|
|
func (os OctetString) ToBase64() string {
|
|
return base64.StdEncoding.EncodeToString(os)
|
|
}
|
|
|
|
// ToHex returns the OctetString as a hex encoded UTF8 string (lowercase).
|
|
func (os OctetString) ToHex() string {
|
|
return hex.EncodeToString(os)
|
|
}
|
|
|
|
// Xor applies "exclusive or" for every octet between both strings. Assumes that both strings have the same length, so
|
|
// perform any checks before calling this function.
|
|
func (os OctetString) Xor(other OctetString) OctetString {
|
|
result := make(OctetString, len(os))
|
|
for i := range os {
|
|
result[i] = os[i] ^ other[i]
|
|
}
|
|
return result
|
|
}
|