package stoken import ( "bytes" "crypto/ed25519" "crypto/rand" "encoding/base64" "encoding/hex" "errors" "fmt" "github.com/ugorji/go/codec" "hash/crc64" "strings" ) const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" var b64 = base64.NewEncoding(alphabet).WithPadding(base64.NoPadding) type linkData struct { Signature []byte `codec:"s"` Payload []byte `codec:"p"` } type TokenCoder struct { valid bool privKey ed25519.PrivateKey pubKeys []ed25519.PublicKey } func (tc TokenCoder) PublicKey() ed25519.PublicKey { if tc.valid { pos := len(tc.pubKeys) - 1 return tc.pubKeys[pos] } return nil } func (tc TokenCoder) Seed() []byte { if tc.valid { return tc.privKey.Seed() } return nil } func (tc TokenCoder) PublicKeyHex() string { return hex.EncodeToString(tc.PublicKey()) } func (tc TokenCoder) SeedHex() string { return hex.EncodeToString(tc.Seed()) } func NewTokenCoder(seed []byte, pubKeys ...[]byte) (tc TokenCoder, err error) { tc.pubKeys = make([]ed25519.PublicKey, len(pubKeys)+1, len(pubKeys)+1) if len(seed) == 0 { seed = make([]byte, ed25519.SeedSize, ed25519.SeedSize) _, err = rand.Read(seed) if err != nil { return } } if len(seed) != ed25519.SeedSize { err = errors.New("Incorrect seed size") return } tc.privKey = ed25519.NewKeyFromSeed(seed) tc.pubKeys[len(pubKeys)] = tc.privKey.Public().(ed25519.PublicKey) for pos, key := range pubKeys { if len(key) != ed25519.PublicKeySize { err = errors.New("Incorrect public key size") return } tc.pubKeys[pos] = ed25519.PublicKey(key) } tc.valid = true return } func NewTokenCoderHex(seed string, pubKeys ...string) (tc TokenCoder, err error) { var rawSeed []byte rawPubKeys := make([][]byte, len(pubKeys), len(pubKeys)) rawSeed, err = hex.DecodeString(seed) if err != nil { return } for pos, pubKey := range pubKeys { rawPubKeys[pos], err = hex.DecodeString(pubKey) if err != nil { return } } return NewTokenCoder(rawSeed, rawPubKeys...) } func trim(r rune) bool { return !strings.ContainsRune(alphabet, r) } func (tc TokenCoder) Encode(payload interface{}) (token string, err error) { if !tc.valid { err = errors.New("Invalid TokenCoder") return } buf := bytes.NewBuffer(nil) var handle codec.CborHandle enc := codec.NewEncoder(buf, &handle) enc.Encode(payload) var ld linkData ld.Signature = ed25519.Sign(tc.privKey, buf.Bytes()) ld.Payload = buf.Bytes() buf.Reset() enc.Encode(ld) token = b64.EncodeToString(buf.Bytes()) return } func (tc TokenCoder) Decode(token string, payload interface{}) (sum string, err error) { if !tc.valid { err = errors.New("Invalid TokenCoder") return } var data []byte data, err = b64.DecodeString(strings.TrimFunc(token, trim)) if err != nil { return } buf := bytes.NewBuffer(nil) var handle codec.CborHandle buf.Write(data) dec := codec.NewDecoder(buf, &handle) var ld linkData err = dec.Decode(&ld) if err != nil { return } ok := false for _, key := range tc.pubKeys { if ed25519.Verify(key, ld.Payload, ld.Signature) { ok = true break } } if !ok { err = errors.New("Signature validation failed") return } buf.Reset() buf.Write(ld.Payload) hash := crc64.New(crc64.MakeTable(crc64.ISO)) hash.Write(ld.Payload) sum = hex.EncodeToString(hash.Sum(nil)) err = dec.Decode(&payload) return }