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.
218 lines
4.4 KiB
218 lines
4.4 KiB
package stoken |
|
|
|
import ( |
|
"bytes" |
|
"crypto/ed25519" |
|
"crypto/rand" |
|
"encoding/base64" |
|
"encoding/hex" |
|
"encoding/json" |
|
"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 handle codec.CborHandle |
|
|
|
buf.Write(data) |
|
dec := codec.NewDecoder(buf, &handle) |
|
|
|
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 := json.NewEncoder(buf) |
|
enc.SetEscapeHTML(false) |
|
enc.SetIndent("", "\t") |
|
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 |
|
}
|
|
|