79 lines
2.4 KiB
Go
79 lines
2.4 KiB
Go
// Package credential generates and verifies DWH installation credentials.
|
|
package credential
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"crypto/subtle"
|
|
"encoding/base64"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/aritmolab/thothii/tools/dwh-auth/internal/record"
|
|
)
|
|
|
|
const Prefix = "thtdwh_v1"
|
|
const KeyIDEncodedLength = 16
|
|
const SecretEncodedLength = 43
|
|
const MaxHeaderBytes = 128
|
|
|
|
type Material struct {
|
|
Value []byte
|
|
KeyID string
|
|
Digest record.Digest
|
|
}
|
|
|
|
// Generate creates one canonical version 1 credential from random.
|
|
func Generate(random io.Reader) (Material, error) {
|
|
keyIDBytes := make([]byte, 12)
|
|
secretBytes := make([]byte, 32)
|
|
if _, err := io.ReadFull(random, keyIDBytes); err != nil {
|
|
return Material{}, err
|
|
}
|
|
if _, err := io.ReadFull(random, secretBytes); err != nil {
|
|
return Material{}, err
|
|
}
|
|
keyID := base64.RawURLEncoding.EncodeToString(keyIDBytes)
|
|
secret := base64.RawURLEncoding.EncodeToString(secretBytes)
|
|
value := []byte(Prefix + "." + keyID + "." + secret)
|
|
sum := sha256.Sum256(value)
|
|
return Material{Value: value, KeyID: keyID, Digest: record.Digest(sum)}, nil
|
|
}
|
|
|
|
// VerifyV1 authenticates one canonical version 1 credential and returns its key ID.
|
|
func VerifyV1(value []byte, expected record.Digest) (string, bool) {
|
|
if len(value) > MaxHeaderBytes || len(value) != len(Prefix)+1+KeyIDEncodedLength+1+SecretEncodedLength {
|
|
return "", false
|
|
}
|
|
parts := strings.Split(string(value), ".")
|
|
if len(parts) != 3 || parts[0] != Prefix || !canonicalBase64(parts[1], 12, KeyIDEncodedLength) || !canonicalBase64(parts[2], 32, SecretEncodedLength) {
|
|
return "", false
|
|
}
|
|
sum := sha256.Sum256(value)
|
|
if subtle.ConstantTimeCompare(sum[:], expected[:]) != 1 {
|
|
return "", false
|
|
}
|
|
return parts[1], true
|
|
}
|
|
|
|
// VerifyLegacy authenticates an opaque legacy credential without interpreting its syntax.
|
|
func VerifyLegacy(value []byte, expected record.Digest) bool {
|
|
if len(value) < 1 || len(value) > MaxHeaderBytes {
|
|
return false
|
|
}
|
|
for _, b := range value {
|
|
if b <= 0x1f || b == 0x7f {
|
|
return false
|
|
}
|
|
}
|
|
sum := sha256.Sum256(value)
|
|
return subtle.ConstantTimeCompare(sum[:], expected[:]) == 1
|
|
}
|
|
|
|
func canonicalBase64(value string, decodedLength, encodedLength int) bool {
|
|
if len(value) != encodedLength {
|
|
return false
|
|
}
|
|
decoded, err := base64.RawURLEncoding.DecodeString(value)
|
|
return err == nil && len(decoded) == decodedLength && base64.RawURLEncoding.EncodeToString(decoded) == value
|
|
}
|