You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
91 lines
2.5 KiB
91 lines
2.5 KiB
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/hmac"
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
type TokenGenerator struct {
|
|
AuthenticationKey []byte
|
|
Algorithm crypto.Hash
|
|
}
|
|
|
|
type TokenData struct {
|
|
Timestamp time.Time
|
|
Username string
|
|
Entitlement string
|
|
}
|
|
|
|
func NewTokenGenerator(authenticationKey []byte, algorithm crypto.Hash) (*TokenGenerator, error) {
|
|
if !algorithm.Available() {
|
|
return nil, fmt.Errorf("Hash algorithm %d is not available", algorithm)
|
|
}
|
|
|
|
return &TokenGenerator{AuthenticationKey: authenticationKey, Algorithm: algorithm}, nil
|
|
}
|
|
|
|
func (g *TokenGenerator) NewToken(data TokenData) string {
|
|
// Fill a buffer with the token data
|
|
buffer := getBufferFor(data)
|
|
|
|
// Pass the token data to the hash function
|
|
hasher := hmac.New(g.Algorithm.New, g.AuthenticationKey)
|
|
hasher.Write(buffer)
|
|
hash := hasher.Sum(nil)
|
|
|
|
//fmt.Println(hex.EncodeToString(hash))
|
|
|
|
return base64.RawURLEncoding.EncodeToString(hash)
|
|
}
|
|
|
|
func getBufferFor(data TokenData) []byte {
|
|
// Compute the number days since year 2000
|
|
// Note: there is a one-off error if the token span across the end of a leap year
|
|
var daysSinceY2K uint32 = uint32((data.Timestamp.Year()-2000)*365 + data.Timestamp.YearDay())
|
|
//fmt.Printf("Days since Y2K = %d\n", daysSinceY2K)
|
|
|
|
// Pack the token data in a buffer
|
|
// - number of days since Y2K
|
|
// - username that generated the token
|
|
// - entitlement for the resulting token
|
|
usernameBytes := []byte(data.Username)
|
|
entitlementBytes := []byte(data.Entitlement)
|
|
bufferLen := len(usernameBytes) + len(entitlementBytes) + 5 // 4 bytes for daysSinceEpoch + one '\0' separator
|
|
var buffer []byte = make([]byte, bufferLen)
|
|
binary.LittleEndian.PutUint32(buffer, daysSinceY2K)
|
|
start, stop := 4, 4+len(usernameBytes)
|
|
copy(buffer[start:stop], usernameBytes)
|
|
start, stop = stop+1, stop+1+len(entitlementBytes)
|
|
copy(buffer[start:stop], entitlementBytes)
|
|
|
|
//fmt.Println(hex.EncodeToString(buffer))
|
|
|
|
return buffer
|
|
}
|
|
|
|
func (g *TokenGenerator) ValidateToken(data TokenData, token string, validity int) (bool, error) {
|
|
rawToken, err := base64.RawURLEncoding.DecodeString(token)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
hasher := hmac.New(g.Algorithm.New, g.AuthenticationKey)
|
|
for days := 0; days < validity; days = days + 1 {
|
|
attempt := data
|
|
attempt.Timestamp = data.Timestamp.Add(time.Hour * -24 * time.Duration(days))
|
|
buffer := getBufferFor(attempt)
|
|
hasher.Reset()
|
|
hasher.Write(buffer)
|
|
hash := hasher.Sum(nil)
|
|
if bytes.Compare(hash, rawToken) == 0 {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|