commit a6c329054ef764aedaf6b83e7b43780b70e23267 Author: Roy Olav Purser Date: Sun Feb 20 20:01:20 2022 +0100 first commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6391a10 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.purser.it/roypur/stoken + +go 1.17 + +require github.com/ugorji/go/codec v1.2.6 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ff77558 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= +github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= +github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= +github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= diff --git a/sign.go b/sign.go new file mode 100644 index 0000000..746f407 --- /dev/null +++ b/sign.go @@ -0,0 +1,170 @@ +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 +}