tcpd-sso/legacy/ssha.go

147 lines
2.9 KiB
Go

package legacy
import (
"bytes"
"hash"
"crypto/sha1"
"crypto/sha512"
"encoding/base64"
"errors"
"fmt"
"strings"
)
// Credit: some of the SSHA code used from https://github.com/daknob/ssha/blob/master/ssha.go
// IsSSHA512 checks if a particular string is a SSHA hash or not
func IsSSHA512(hash string) bool {
/* Check if the string begins with {SSHA} */
if !strings.HasPrefix(hash, "{SSHA512}") {
return false
}
/* Check if the string has anything after {SSHA} and it has only one {SSHA} */
if len(strings.Split(hash, "{SSHA512}")) != 2 {
return false
}
/* Get the Base64-Encoded payload of the hash */
payload := strings.Split(hash, "{SSHA512}")[1]
/* Decode the payload */
decoded, err := base64.StdEncoding.DecodeString(payload)
if err != nil {
return false
}
/* Check the payload length */
if len(decoded) < 64 {
return false
}
return true
}
// IsSSHA checks if a particular string is a SSHA hash or not
func IsSSHA(hash string) bool {
/* Check if the string begins with {SSHA} */
if !strings.HasPrefix(hash, "{SSHA}") {
return false
}
/* Check if the string has anything after {SSHA} and it has only one {SSHA} */
if len(strings.Split(hash, "{SSHA}")) != 2 {
return false
}
/* Get the Base64-Encoded payload of the hash */
payload := strings.Split(hash, "{SSHA}")[1]
/* Decode the payload */
decoded, err := base64.StdEncoding.DecodeString(payload)
if err != nil {
return false
}
/* Check the payload length */
if len(decoded) < 21 {
return false
}
return true
}
type hashType uint8
const (
typeSSHA hashType = iota
typeSSHA512
typeInvalid
)
// GetHashParts returns the SSHA hash' Salt and SHA-1 hash
func GetHashParts(hash string) ([]byte, []byte, hashType, error) {
var sep string
var htype hashType
var hlen int
switch {
case IsSSHA(hash):
sep = "{SSHA}"
htype = typeSSHA
hlen = 20
case IsSSHA512(hash):
sep = "{SSHA512}"
htype = typeSSHA512
hlen = 64
default:
return nil, nil, typeInvalid, fmt.Errorf("hash is invalid")
}
decoded, err := base64.StdEncoding.DecodeString(strings.Split(hash, sep)[1])
if err != nil {
return nil, nil, typeInvalid, fmt.Errorf("failed to decode %s hash: %v", sep, err)
}
return decoded[0:hlen], decoded[hlen:], htype, nil
}
/*
VerifyHash accepts a SSHA string, as well as a plaintext password, and checks whether this
password is correct or not
*/
func VerifyHash(provided, password string) (bool, error) {
correct, salt, htype, err := GetHashParts(provided)
if err != nil {
return false, fmt.Errorf("failed to parse hash: %v", err)
}
if htype == typeInvalid {
return false, errors.New("failed to parse hash")
}
var phash hash.Hash
switch htype {
case typeSSHA:
phash = sha1.New()
phash.Write([]byte(password))
phash.Write(salt)
case typeSSHA512:
phash = sha512.New()
phash.Write([]byte(password))
phash.Write(salt)
default:
panic(htype)
}
if bytes.Equal(correct, phash.Sum(nil)) {
return true, nil
}
return false, nil
}