package stoken import ( "bytes" "crypto/ed25519" "crypto/rand" "encoding/base64" "encoding/hex" "errors" "github.com/ugorji/go/codec" "hash/crc64" "strings" ) const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" var b64 = base64.NewEncoding(alphabet).WithPadding(base64.NoPadding) type tokenData 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 Format(token string) (txt string, err error) { var payload interface{} var data []byte data, err = b64.DecodeString(strings.TrimFunc(token, trim)) if err != nil { return } buf := bytes.NewBuffer(nil) var cHandle codec.CborHandle var jHandle codec.JsonHandle jHandle.HTMLCharsAsIs = true jHandle.MapKeyAsString = true jHandle.Indent = 4 buf.Write(data) dec := codec.NewDecoder(buf, &cHandle) var td tokenData err = dec.Decode(&td) if err != nil { return } buf.Reset() buf.Write(td.Payload) err = dec.Decode(&payload) buf.Reset() buf.Write([]byte("SIGNATURE=" + hex.EncodeToString(td.Signature) + "\n")) enc := codec.NewEncoder(buf, &jHandle) err = enc.Encode(payload) txt = buf.String() return } func NewTokenCoderWithSeed(seed []byte, pubKeys ...[]byte) (tc TokenCoder, err error) { if len(seed) != ed25519.SeedSize { err = errors.New("Incorrect seed size") return } tc.pubKeys = make([]ed25519.PublicKey, len(pubKeys)+1, len(pubKeys)+1) 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 NewTokenCoder(pubKeys ...[]byte) (tc TokenCoder, err error) { seed := make([]byte, ed25519.SeedSize, ed25519.SeedSize) _, err = rand.Read(seed) if err != nil { return } return NewTokenCoderWithSeed(seed, pubKeys...) } func NewTokenCoderHexWithSeed(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 NewTokenCoderWithSeed(rawSeed, rawPubKeys...) } func NewTokenCoderHex(pubKeys ...string) (tc TokenCoder, err error) { rawPubKeys := make([][]byte, len(pubKeys), len(pubKeys)) for pos, pubKey := range pubKeys { rawPubKeys[pos], err = hex.DecodeString(pubKey) if err != nil { return } } return NewTokenCoder(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 td tokenData td.Signature = ed25519.Sign(tc.privKey, buf.Bytes()) td.Payload = buf.Bytes() buf.Reset() enc.Encode(td) 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 td tokenData err = dec.Decode(&td) if err != nil { return } ok := false for _, key := range tc.pubKeys { if ed25519.Verify(key, td.Payload, td.Signature) { ok = true break } } if !ok { err = errors.New("Signature validation failed") return } buf.Reset() buf.Write(td.Payload) hash := crc64.New(crc64.MakeTable(crc64.ISO)) hash.Write(td.Payload) sum = hex.EncodeToString(hash.Sum(nil)) err = dec.Decode(&payload) return }