From 8ccc74b677f0d6babfbdfec90ba5fd9e11e95208 Mon Sep 17 00:00:00 2001 From: itsscb Date: Thu, 28 Sep 2023 23:12:50 +0200 Subject: [PATCH] Change paseto package to go-paseto (#45) --- api/main_test.go | 2 +- api/server.go | 2 +- app.env | 2 +- development_privatekey.asc | 1 + go.mod | 7 ++-- go.sum | 17 +++------- token/main_test.go | 16 +++++++++ token/paseto_generate_keypair.go | 32 ++++++++++++++++++ token/paseto_maker.go | 56 ++++++++++++++++++++++++-------- token/paseto_maker_test.go | 6 ++-- token/payload.go | 4 +-- util/config.go | 2 +- 12 files changed, 107 insertions(+), 40 deletions(-) create mode 100644 development_privatekey.asc create mode 100644 token/main_test.go create mode 100644 token/paseto_generate_keypair.go diff --git a/api/main_test.go b/api/main_test.go index 1004d18..e198485 100644 --- a/api/main_test.go +++ b/api/main_test.go @@ -14,7 +14,7 @@ var config util.Config func TestMain(m *testing.M) { config = util.Config{ Environment: "production", - TokenSymmetricKey: "12345678901234567890123456789012", + TokenPrivateKeyHex: "099c0b96725b99e95719c92aec580809ac58fc14be2105ed2656f1f6c464593d8cacd6c7bed924b9cf207ab3cff1c59be4e5865260c4dafa29699244bd4ea2de", AccessTokenDuration: time.Minute * 1, RefreshTokenDuration: time.Minute * 2, } diff --git a/api/server.go b/api/server.go index 8d9b43d..14aa857 100644 --- a/api/server.go +++ b/api/server.go @@ -21,7 +21,7 @@ type Server struct { // NewServer creates a new HTTP server and sets up routing func NewServer(config util.Config, store db.Store) (*Server, error) { - tokenMaker, err := token.NewPasetoMaker(config.TokenSymmetricKey) + tokenMaker, err := token.NewPasetoMaker(config.TokenPrivateKeyHex) if err != nil { return nil, fmt.Errorf("cannot create token maker: %w", err) } diff --git a/app.env b/app.env index 396a54d..3db94b4 100644 --- a/app.env +++ b/app.env @@ -5,4 +5,4 @@ ENVIRONMENT=development LOG_OUTPUT=text ACCESS_TOKEN_DURATION=15m REFRESH_TOKEN_DURATION=24h -TOKEN_SYMMETRIC_KEY=12345678901234567890123456789012 \ No newline at end of file +TOKEN_PRIVATEKEY_HEX=099c0b96725b99e95719c92aec580809ac58fc14be2105ed2656f1f6c464593d8cacd6c7bed924b9cf207ab3cff1c59be4e5865260c4dafa29699244bd4ea2de \ No newline at end of file diff --git a/development_privatekey.asc b/development_privatekey.asc new file mode 100644 index 0000000..9e24611 --- /dev/null +++ b/development_privatekey.asc @@ -0,0 +1 @@ +c2e4d86dd824e33696b3d3208c11c93eff2a64f06deb8514e0ab821070f2d504f6dc25b460fbfd04f2d7e3aaf87fa1e934d8a4ed210726921aa1b51274a9bc58 \ No newline at end of file diff --git a/go.mod b/go.mod index 0ffa4a0..1fb03f2 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,10 @@ go 1.21 toolchain go1.21.1 require ( - github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 + aidanwoods.dev/go-paseto v1.5.0 github.com/gin-gonic/gin v1.9.1 github.com/google/uuid v1.1.2 github.com/lib/pq v1.10.9 - github.com/o1egl/paseto v1.0.0 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 go.uber.org/mock v0.3.0 @@ -18,8 +17,7 @@ require ( ) require ( - github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect - github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect + aidanwoods.dev/go-result v0.1.0 // indirect github.com/bytedance/sonic v1.10.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.0 // indirect @@ -41,7 +39,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect diff --git a/go.sum b/go.sum index 2633f41..52c8813 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +aidanwoods.dev/go-paseto v1.5.0 h1:FKrHrip6HfZfuzLuz2NVnM7wQ3Ql+mKcWWcgDr3Mb1g= +aidanwoods.dev/go-paseto v1.5.0/go.mod h1:9J13iCMdWrkfK1AxAg9QDHLaDMYSEP1ldbFiR+DfmVc= +aidanwoods.dev/go-result v0.1.0 h1:y/BMIRX6q3HwaorX1Wzrjo3WUdiYeyWbvGe18hKS3K8= +aidanwoods.dev/go-result v0.1.0/go.mod h1:yridkWghM7AXSFA6wzx0IbsurIm1Lhuro3rYef8FBHM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -38,13 +42,6 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= -github.com/aead/chacha20poly1305 v0.0.0-20170617001512-233f39982aeb/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU= -github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 h1:1DcvRPZOdbQRg5nAHt2jrc5QbV0AGuhDdfQI6gXjiFE= -github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29/go.mod h1:UzH9IX1MMqOcwhoNOIjmTQeAxrFgzs50j4golQtXXxU= -github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= -github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= @@ -193,12 +190,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/o1egl/paseto v1.0.0 h1:bwpvPu2au176w4IBlhbyUv/S5VPptERIA99Oap5qUd0= -github.com/o1egl/paseto v1.0.0/go.mod h1:5HxsZPmw/3RI2pAwGo1HhOOwSdvBpcuVzO7uDkm+CLU= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -252,7 +245,6 @@ go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -351,7 +343,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/token/main_test.go b/token/main_test.go new file mode 100644 index 0000000..28b7804 --- /dev/null +++ b/token/main_test.go @@ -0,0 +1,16 @@ +package token + +import ( + "os" + "testing" + + "aidanwoods.dev/go-paseto" +) + +var devPrivateKeyHex string + +func TestMain(m *testing.M) { + devPrivateKeyHex = paseto.NewV4AsymmetricSecretKey().ExportHex() + + os.Exit(m.Run()) +} diff --git a/token/paseto_generate_keypair.go b/token/paseto_generate_keypair.go new file mode 100644 index 0000000..57b5de9 --- /dev/null +++ b/token/paseto_generate_keypair.go @@ -0,0 +1,32 @@ +package token + +import ( + "log" + "os" + + "aidanwoods.dev/go-paseto" +) + +const ( + devPrivateKeyFile = "development_privatekey.asc" +) + +func GenerateKeyPair() (err error) { + secretKey := paseto.NewV4AsymmetricSecretKey() + + if err = os.WriteFile(devPrivateKeyFile, []byte(secretKey.ExportHex()), 0666); err != nil { + log.Fatalf("could not create development_privatekey.asc: %v", err) + return err + } + + return err +} + +func GetPrivateKey() (paseto.V4AsymmetricSecretKey, error) { + f, err := os.ReadFile(devPrivateKeyFile) + if err != nil { + return paseto.V4AsymmetricSecretKey{}, err + } + + return paseto.NewV4AsymmetricSecretKeyFromHex(string(f)) +} diff --git a/token/paseto_maker.go b/token/paseto_maker.go index b00c3f9..03a7a59 100644 --- a/token/paseto_maker.go +++ b/token/paseto_maker.go @@ -1,28 +1,30 @@ package token import ( - "fmt" "time" - "github.com/aead/chacha20poly1305" - "github.com/o1egl/paseto" + "aidanwoods.dev/go-paseto" + "github.com/google/uuid" ) // PasetoMaker is a PASETO token maker type PasetoMaker struct { - paseto *paseto.V2 - symmetricKey []byte + privateKey paseto.V4AsymmetricSecretKey + publicKey paseto.V4AsymmetricPublicKey + parser paseto.Parser } // NewPasetoMaker creates a new PasetoMaker -func NewPasetoMaker(symmetricKey string) (Maker, error) { - if len(symmetricKey) != chacha20poly1305.KeySize { - return nil, fmt.Errorf("invalid key size: must be exactly %d characters", chacha20poly1305.KeySize) +func NewPasetoMaker(privateKeyHex string) (Maker, error) { + privateKey, err := paseto.NewV4AsymmetricSecretKeyFromHex(privateKeyHex) + if err != nil { + return nil, err } maker := &PasetoMaker{ - paseto: paseto.NewV2(), - symmetricKey: []byte(symmetricKey), + privateKey: privateKey, + publicKey: privateKey.Public(), + parser: paseto.NewParser(), } return maker, nil @@ -35,19 +37,47 @@ func (maker *PasetoMaker) CreateToken(email string, duration time.Duration) (str return "", payload, err } - token, err := maker.paseto.Encrypt(maker.symmetricKey, payload, nil) - return token, payload, err + token := paseto.NewToken() + token.SetNotBefore(time.Now()) + token.SetIssuedAt(payload.IssuedAt) + token.SetExpiration(payload.ExpiredAt) + token.SetString("id", payload.ID.String()) + token.SetString("email", payload.Email) + + signed := token.V4Sign(maker.privateKey, nil) + return signed, payload, err } // VerifyToken checks if the token is valid or not func (maker *PasetoMaker) VerifyToken(token string) (*Payload, error) { payload := &Payload{} - err := maker.paseto.Decrypt(token, maker.symmetricKey, payload, nil) + t, err := maker.parser.ParseV4Public(maker.publicKey, token, nil) + if err != nil { + return nil, err + } + + payload.ExpiredAt, err = t.GetExpiration() if err != nil { return nil, ErrInvalidToken } + payload.IssuedAt, err = t.GetIssuedAt() + if err != nil { + return nil, ErrInvalidToken + } + + payload.Email, err = t.GetString("email") + if err != nil { + return nil, ErrInvalidToken + } + + uid, err := t.GetString("id") + if err != nil { + return nil, ErrInvalidToken + } + + payload.ID = uuid.MustParse(uid) err = payload.Valid() if err != nil { return nil, err diff --git a/token/paseto_maker_test.go b/token/paseto_maker_test.go index 8247c65..3c53aa4 100644 --- a/token/paseto_maker_test.go +++ b/token/paseto_maker_test.go @@ -9,11 +9,11 @@ import ( ) func TestPasetoMaker(t *testing.T) { - maker, err := NewPasetoMaker(util.RandomString(32)) + maker, err := NewPasetoMaker(devPrivateKeyHex) require.NoError(t, err) email := util.RandomEmail() - duration := time.Minute + duration := time.Minute * 2 issuedAt := time.Now() expiredAt := issuedAt.Add(duration) @@ -34,7 +34,7 @@ func TestPasetoMaker(t *testing.T) { } func TestExpiredPasetoToken(t *testing.T) { - maker, err := NewPasetoMaker(util.RandomString(32)) + maker, err := NewPasetoMaker(devPrivateKeyHex) require.NoError(t, err) token, payload, err := maker.CreateToken(util.RandomEmail(), -time.Minute) diff --git a/token/payload.go b/token/payload.go index 6a34c07..55d2b7f 100644 --- a/token/payload.go +++ b/token/payload.go @@ -9,8 +9,8 @@ import ( // Different types of error returned by the VerifyToken function var ( - ErrInvalidToken = errors.New("token is invalid") - ErrExpiredToken = errors.New("token has expired") + ErrInvalidToken = errors.New("this token is invalid") + ErrExpiredToken = errors.New("this token has expired") ) // Payload contains the payload data of the token diff --git a/util/config.go b/util/config.go index a4da802..2a38f1d 100644 --- a/util/config.go +++ b/util/config.go @@ -12,7 +12,7 @@ type Config struct { ServerAddress string `mapstructure:"SERVER_ADDRESS"` Environment string `mapstructure:"ENVIRONMENT"` LogOutput string `mapstructure:"LOG_OUTPUT"` - TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"` + TokenPrivateKeyHex string `mapstructure:"TOKEN_PRIVATEKEY_HEX"` AccessTokenDuration time.Duration `mapstructure:"ACCESS_TOKEN_DURATION"` RefreshTokenDuration time.Duration `mapstructure:"REFRESH_TOKEN_DURATION"` }