diff --git a/go.mod b/go.mod index 6838c43..97f3f71 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,13 @@ replace github.com/asaskevich/EventBus v0.0.0-20200330115301-33b3bc6a7ddc => git // Main dependencies require ( - github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2 github.com/asaskevich/EventBus v0.0.0-20200330115301-33b3bc6a7ddc github.com/brunomvsouza/singleflight v0.4.0 github.com/defval/di v1.12.0 github.com/etherlabsio/healthcheck/v2 v2.0.0 github.com/getsentry/raven-go v0.2.1-0.20190419175539-919484f041ea github.com/go-playground/validator/v10 v10.17.0 + github.com/golang-jwt/jwt/v5 v5.2.0 github.com/gorilla/mux v1.8.1 github.com/jellydator/ttlcache/v3 v3.1.1 github.com/mediocregopher/radix/v4 v4.1.4 diff --git a/go.sum b/go.sum index f03799a..3a1ef38 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2 h1:koK7z0nSsRiRiBWwa+E714Puh+DO+ZRdIyAXiXzL+lg= -github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= github.com/brunomvsouza/singleflight v0.4.0 h1:9dNcTeYoXSus3xbZEM0EEZ11EcCRjUZOvVW8rnDMG5Y= github.com/brunomvsouza/singleflight v0.4.0/go.mod h1:8RYo9j5WQRupmsnUz5DlUWZxDLNi+t9Zhj3EZFmns7I= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= @@ -31,6 +29,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= diff --git a/internal/cmd/token.go b/internal/cmd/token.go index 519e20b..df20ef3 100644 --- a/internal/cmd/token.go +++ b/internal/cmd/token.go @@ -2,9 +2,8 @@ package cmd import ( "fmt" - "log" - "ely.by/chrly/internal/http" + "ely.by/chrly/internal/security" "github.com/spf13/cobra" ) @@ -12,20 +11,22 @@ import ( var tokenCmd = &cobra.Command{ Use: "token", Short: "Creates a new token, which allows to interact with Chrly API", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { container := shouldGetContainer() - var auth *http.JwtAuth + var auth *security.Jwt err := container.Resolve(&auth) if err != nil { - log.Fatal(err) + return err } - token, err := auth.NewToken(http.SkinScope) + token, err := auth.NewToken(security.ProfileScope) if err != nil { - log.Fatalf("Unable to create new token. The error is %v\n", err) + return fmt.Errorf("Unable to create a new token. The error is %v\n", err) } - fmt.Printf("%s\n", token) + fmt.Println(token) + + return nil }, } diff --git a/internal/di/di.go b/internal/di/di.go index a6c058c..4771b79 100644 --- a/internal/di/di.go +++ b/internal/di/di.go @@ -3,7 +3,7 @@ package di import "github.com/defval/di" func New() (*di.Container, error) { - container, err := di.New( + return di.New( config, dispatcher, logger, @@ -12,11 +12,6 @@ func New() (*di.Container, error) { handlers, profilesDi, server, - signer, + securityDiOptions, ) - if err != nil { - return nil, err - } - - return container, nil } diff --git a/internal/di/signer.go b/internal/di/security.go similarity index 56% rename from internal/di/signer.go rename to internal/di/security.go index 2dfcc1a..bec4856 100644 --- a/internal/di/signer.go +++ b/internal/di/security.go @@ -1,29 +1,36 @@ package di import ( + "crypto/rand" + "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/pem" - "errors" "strings" "ely.by/chrly/internal/http" - . "ely.by/chrly/internal/signer" + "ely.by/chrly/internal/security" "github.com/defval/di" "github.com/spf13/viper" ) -var signer = di.Options( +var securityDiOptions = di.Options( di.Provide(newTexturesSigner, di.As(new(http.TexturesSigner)), ), ) -func newTexturesSigner(config *viper.Viper) (*Signer, error) { +func newTexturesSigner(config *viper.Viper) (*security.Signer, error) { keyStr := config.GetString("chrly.signing.key") if keyStr == "" { - return nil, errors.New("chrly.signing.key must be set in order to sign textures") + // TODO: log a message about the generated signing key and the way to specify it permanently + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + + return security.NewSigner(privateKey), nil } var keyBytes []byte @@ -40,10 +47,10 @@ func newTexturesSigner(config *viper.Viper) (*Signer, error) { } rawPem, _ := pem.Decode(keyBytes) - key, err := x509.ParsePKCS1PrivateKey(rawPem.Bytes) + privateKey, err := x509.ParsePKCS1PrivateKey(rawPem.Bytes) if err != nil { return nil, err } - return &Signer{Key: key}, nil + return security.NewSigner(privateKey), nil } diff --git a/internal/di/server.go b/internal/di/server.go index 435c9d8..6fa8521 100644 --- a/internal/di/server.go +++ b/internal/di/server.go @@ -12,6 +12,7 @@ import ( "github.com/spf13/viper" . "ely.by/chrly/internal/http" + "ely.by/chrly/internal/security" ) var server = di.Options( @@ -19,16 +20,13 @@ var server = di.Options( di.Provide(newServer), ) -func newAuthenticator(config *viper.Viper, emitter Emitter) (*JwtAuth, error) { +func newAuthenticator(config *viper.Viper) (*security.Jwt, error) { key := config.GetString("chrly.secret") if key == "" { return nil, errors.New("chrly.secret must be set in order to use authenticator") } - return &JwtAuth{ - Key: []byte(key), - Emitter: emitter, - }, nil + return security.NewJwt([]byte(key)), nil } type serverParams struct { diff --git a/internal/http/http.go b/internal/http/http.go index cc1de21..9f32ed9 100644 --- a/internal/http/http.go +++ b/internal/http/http.go @@ -86,6 +86,8 @@ type Authenticator interface { Authenticate(req *http.Request) error } +// The current middleware implementation doesn't check the scope assigned to the token. +// For now there is only one scope and at this moment I don't want to spend time on it func CreateAuthenticationMiddleware(checker Authenticator) mux.MiddlewareFunc { return func(handler http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { @@ -137,12 +139,3 @@ func apiForbidden(resp http.ResponseWriter, reason string) { }) _, _ = resp.Write(result) } - -func apiNotFound(resp http.ResponseWriter, reason string) { - resp.WriteHeader(http.StatusNotFound) - resp.Header().Set("Content-Type", "application/json") - result, _ := json.Marshal([]interface{}{ - reason, - }) - _, _ = resp.Write(result) -} diff --git a/internal/http/jwt.go b/internal/http/jwt.go deleted file mode 100644 index 94f196b..0000000 --- a/internal/http/jwt.go +++ /dev/null @@ -1,78 +0,0 @@ -package http - -import ( - "errors" - "net/http" - "strings" - "time" - - "github.com/SermoDigital/jose/crypto" - "github.com/SermoDigital/jose/jws" -) - -var hashAlg = crypto.SigningMethodHS256 - -const scopesClaim = "scopes" - -type Scope string - -var ( - SkinScope = Scope("skin") -) - -type JwtAuth struct { - Emitter - Key []byte -} - -func (t *JwtAuth) NewToken(scopes ...Scope) ([]byte, error) { - if len(t.Key) == 0 { - return nil, errors.New("signing key not available") - } - - claims := jws.Claims{} - claims.Set(scopesClaim, scopes) - claims.SetIssuedAt(time.Now()) - encoder := jws.NewJWT(claims, hashAlg) - token, err := encoder.Serialize(t.Key) - if err != nil { - return nil, err - } - - return token, nil -} - -func (t *JwtAuth) Authenticate(req *http.Request) error { - if len(t.Key) == 0 { - return t.emitErr(errors.New("Signing key not set")) - } - - bearerToken := req.Header.Get("Authorization") - if bearerToken == "" { - return t.emitErr(errors.New("Authentication header not presented")) - } - - if !strings.EqualFold(bearerToken[0:7], "BEARER ") { - return t.emitErr(errors.New("Cannot recognize JWT token in passed value")) - } - - tokenStr := bearerToken[7:] - token, err := jws.ParseJWT([]byte(tokenStr)) - if err != nil { - return t.emitErr(errors.New("Cannot parse passed JWT token")) - } - - err = token.Validate(t.Key, hashAlg) - if err != nil { - return t.emitErr(errors.New("JWT token have invalid signature. It may be corrupted or expired")) - } - - t.Emit("authentication:success") - - return nil -} - -func (t *JwtAuth) emitErr(err error) error { - t.Emit("authentication:error", err) - return err -} diff --git a/internal/http/jwt_test.go b/internal/http/jwt_test.go deleted file mode 100644 index 7e710b6..0000000 --- a/internal/http/jwt_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package http - -import ( - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNTE2NjU4MTkzIiwic2NvcGVzIjoic2tpbiJ9.agbBS0qdyYMBaVfTZJAZcTTRgW1Y0kZty4H3N2JHBO8" - -func TestJwtAuth_NewToken(t *testing.T) { - t.Run("success", func(t *testing.T) { - jwt := &JwtAuth{Key: []byte("secret")} - token, err := jwt.NewToken(SkinScope) - assert.Nil(t, err) - assert.NotNil(t, token) - }) - - t.Run("key not provided", func(t *testing.T) { - jwt := &JwtAuth{} - token, err := jwt.NewToken(SkinScope) - assert.Error(t, err, "signing key not available") - assert.Nil(t, token) - }) -} - -func TestJwtAuth_Authenticate(t *testing.T) { - t.Run("success", func(t *testing.T) { - emitter := &emitterMock{} - emitter.On("Emit", "authentication:success") - - req := httptest.NewRequest("POST", "http://localhost", nil) - req.Header.Add("Authorization", "Bearer "+jwt) - jwt := &JwtAuth{Key: []byte("secret"), Emitter: emitter} - - err := jwt.Authenticate(req) - assert.Nil(t, err) - - emitter.AssertExpectations(t) - }) - - t.Run("request without auth header", func(t *testing.T) { - emitter := &emitterMock{} - emitter.On("Emit", "authentication:error", mock.MatchedBy(func(err error) bool { - assert.Error(t, err, "Authentication header not presented") - return true - })) - - req := httptest.NewRequest("POST", "http://localhost", nil) - jwt := &JwtAuth{Key: []byte("secret"), Emitter: emitter} - - err := jwt.Authenticate(req) - assert.Error(t, err, "Authentication header not presented") - - emitter.AssertExpectations(t) - }) - - t.Run("no bearer token prefix", func(t *testing.T) { - emitter := &emitterMock{} - emitter.On("Emit", "authentication:error", mock.MatchedBy(func(err error) bool { - assert.Error(t, err, "Cannot recognize JWT token in passed value") - return true - })) - - req := httptest.NewRequest("POST", "http://localhost", nil) - req.Header.Add("Authorization", "this is not jwt") - jwt := &JwtAuth{Key: []byte("secret"), Emitter: emitter} - - err := jwt.Authenticate(req) - assert.Error(t, err, "Cannot recognize JWT token in passed value") - - emitter.AssertExpectations(t) - }) - - t.Run("bearer token but not jwt", func(t *testing.T) { - emitter := &emitterMock{} - emitter.On("Emit", "authentication:error", mock.MatchedBy(func(err error) bool { - assert.Error(t, err, "Cannot parse passed JWT token") - return true - })) - - req := httptest.NewRequest("POST", "http://localhost", nil) - req.Header.Add("Authorization", "Bearer thisIs.Not.Jwt") - jwt := &JwtAuth{Key: []byte("secret"), Emitter: emitter} - - err := jwt.Authenticate(req) - assert.Error(t, err, "Cannot parse passed JWT token") - - emitter.AssertExpectations(t) - }) - - t.Run("when secret is not set", func(t *testing.T) { - emitter := &emitterMock{} - emitter.On("Emit", "authentication:error", mock.MatchedBy(func(err error) bool { - assert.Error(t, err, "Signing key not set") - return true - })) - - req := httptest.NewRequest("POST", "http://localhost", nil) - req.Header.Add("Authorization", "Bearer "+jwt) - jwt := &JwtAuth{Emitter: emitter} - - err := jwt.Authenticate(req) - assert.Error(t, err, "Signing key not set") - - emitter.AssertExpectations(t) - }) - - t.Run("invalid signature", func(t *testing.T) { - emitter := &emitterMock{} - emitter.On("Emit", "authentication:error", mock.MatchedBy(func(err error) bool { - assert.Error(t, err, "JWT token have invalid signature. It may be corrupted or expired") - return true - })) - - req := httptest.NewRequest("POST", "http://localhost", nil) - req.Header.Add("Authorization", "Bearer "+jwt) - jwt := &JwtAuth{Key: []byte("this is another secret"), Emitter: emitter} - - err := jwt.Authenticate(req) - assert.Error(t, err, "JWT token have invalid signature. It may be corrupted or expired") - - emitter.AssertExpectations(t) - }) -} diff --git a/internal/security/jwt.go b/internal/security/jwt.go new file mode 100644 index 0000000..878958c --- /dev/null +++ b/internal/security/jwt.go @@ -0,0 +1,82 @@ +package security + +import ( + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/golang-jwt/jwt/v5" + + "ely.by/chrly/internal/version" +) + +var now = time.Now +var signingMethod = jwt.SigningMethodHS256 + +const scopesClaim = "scopes" + +type Scope string + +const ( + ProfileScope Scope = "profiles" +) + +func NewJwt(key []byte) *Jwt { + return &Jwt{ + Key: key, + } +} + +type Jwt struct { + Key []byte +} + +func (t *Jwt) NewToken(scopes ...Scope) (string, error) { + if len(scopes) == 0 { + return "", errors.New("you must specify at least one scope") + } + + token := jwt.NewWithClaims(signingMethod, jwt.MapClaims{ + "iss": "chrly", + "iat": now().Unix(), + scopesClaim: scopes, + }) + token.Header["v"] = version.MajorVersion + + return token.SignedString(t.Key) +} + +// Keep those names generic in order to reuse them in future for alternative authentication methods +var MissingAuthenticationError = errors.New("authentication value not provided") +var InvalidTokenError = errors.New("passed authentication value is invalid") + +func (t *Jwt) Authenticate(req *http.Request) error { + bearerToken := req.Header.Get("Authorization") + if bearerToken == "" { + return MissingAuthenticationError + } + + if !strings.HasPrefix(strings.ToLower(bearerToken), "bearer ") { + return InvalidTokenError + } + + tokenStr := bearerToken[7:] + token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + + return t.Key, nil + }) + if err != nil { + return errors.Join(InvalidTokenError, err) + } + + if _, vHeaderExists := token.Header["v"]; !vHeaderExists { + return errors.Join(InvalidTokenError, errors.New("missing v header")) + } + + return nil +} diff --git a/internal/security/jwt_test.go b/internal/security/jwt_test.go new file mode 100644 index 0000000..40b53ea --- /dev/null +++ b/internal/security/jwt_test.go @@ -0,0 +1,75 @@ +package security + +import ( + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +const jwtString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsInYiOjV9.eyJpYXQiOjE3MDY3ODY3NzUsImlzcyI6ImNocmx5Iiwic2NvcGVzIjpbInByb2ZpbGVzIl19.LrXrKo5iRFFHCDlMsVDhmJJheZqxbxuEVXB4XswHFKY" + +func TestJwtAuth_NewToken(t *testing.T) { + jwt := NewJwt([]byte("secret")) + now = func() time.Time { + return time.Date(2024, 2, 1, 11, 26, 15, 0, time.UTC) + } + + t.Run("with scope", func(t *testing.T) { + token, err := jwt.NewToken(ProfileScope, "custom-scope") + require.NoError(t, err) + require.Equal(t, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsInYiOjV9.eyJpYXQiOjE3MDY3ODY3NzUsImlzcyI6ImNocmx5Iiwic2NvcGVzIjpbInByb2ZpbGVzIiwiY3VzdG9tLXNjb3BlIl19.Iq673YyWWkJZjIkBmKYRN8Lx9qoD39S_e-MegG0aORM", token) + }) + + t.Run("no scopes", func(t *testing.T) { + token, err := jwt.NewToken() + require.Error(t, err) + require.Empty(t, token) + }) +} + +func TestJwtAuth_Authenticate(t *testing.T) { + jwt := NewJwt([]byte("secret")) + t.Run("success", func(t *testing.T) { + req := httptest.NewRequest("POST", "http://localhost", nil) + req.Header.Add("Authorization", "Bearer "+jwtString) + err := jwt.Authenticate(req) + require.NoError(t, err) + }) + + t.Run("request without auth header", func(t *testing.T) { + req := httptest.NewRequest("POST", "http://localhost", nil) + err := jwt.Authenticate(req) + require.ErrorIs(t, err, MissingAuthenticationError) + }) + + t.Run("no bearer token prefix", func(t *testing.T) { + req := httptest.NewRequest("POST", "http://localhost", nil) + req.Header.Add("Authorization", "trash") + err := jwt.Authenticate(req) + require.ErrorIs(t, err, InvalidTokenError) + }) + + t.Run("bearer token but not jwt", func(t *testing.T) { + req := httptest.NewRequest("POST", "http://localhost", nil) + req.Header.Add("Authorization", "Bearer seems.like.jwt") + err := jwt.Authenticate(req) + require.ErrorIs(t, err, InvalidTokenError) + }) + + t.Run("invalid signature", func(t *testing.T) { + req := httptest.NewRequest("POST", "http://localhost", nil) + req.Header.Add("Authorization", "Bearer "+jwtString+"123") + err := jwt.Authenticate(req) + require.ErrorIs(t, err, InvalidTokenError) + }) + + t.Run("missing v header", func(t *testing.T) { + req := httptest.NewRequest("POST", "http://localhost", nil) + req.Header.Add("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MDY3ODY3NzUsImlzcyI6ImNocmx5Iiwic2NvcGVzIjpbInByb2ZpbGVzIl19.zOX2ZKyU37kjwt1p9uCHxALxWQD2UC0wWcAcNvBXGq0") + err := jwt.Authenticate(req) + require.ErrorIs(t, err, InvalidTokenError) + require.ErrorContains(t, err, "missing v header") + }) +} diff --git a/internal/signer/signer.go b/internal/security/signer.go similarity index 74% rename from internal/signer/signer.go rename to internal/security/signer.go index 963a241..991118c 100644 --- a/internal/signer/signer.go +++ b/internal/security/signer.go @@ -1,4 +1,4 @@ -package signer +package security import ( "crypto" @@ -6,37 +6,35 @@ import ( "crypto/rsa" "crypto/sha1" "encoding/base64" - "errors" ) var randomReader = rand.Reader +func NewSigner(key *rsa.PrivateKey) *Signer { + return &Signer{Key: key} +} + type Signer struct { Key *rsa.PrivateKey } func (s *Signer) SignTextures(textures string) (string, error) { - if s.Key == nil { - return "", errors.New("Key is empty") - } - message := []byte(textures) messageHash := sha1.New() - _, _ = messageHash.Write(message) - messageHashSum := messageHash.Sum(nil) + _, err := messageHash.Write(message) + if err != nil { + return "", err + } + messageHashSum := messageHash.Sum(nil) signature, err := rsa.SignPKCS1v15(randomReader, s.Key, crypto.SHA1, messageHashSum) if err != nil { - panic(err) + return "", err } return base64.StdEncoding.EncodeToString(signature), nil } func (s *Signer) GetPublicKey() (*rsa.PublicKey, error) { - if s.Key == nil { - return nil, errors.New("Key is empty") - } - return &s.Key.PublicKey, nil } diff --git a/internal/security/signer_test.go b/internal/security/signer_test.go new file mode 100644 index 0000000..e6891f7 --- /dev/null +++ b/internal/security/signer_test.go @@ -0,0 +1,43 @@ +package security + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "testing" + + "github.com/stretchr/testify/require" +) + +type ConstantReader struct { +} + +func (c *ConstantReader) Read(p []byte) (int, error) { + return 1, nil +} + +func TestSigner_SignTextures(t *testing.T) { + randomReader = &ConstantReader{} + + rawKey, _ := pem.Decode([]byte("-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnmUDlzHBQH3DpYef5WCO32\nTDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQJAItaxSHTe6PKbyEU/9pxj\nONdhYRYwVLLo56gnMYhkyoEqaaMsfov8hhoepkYZBMvZFB2bDOsQ2SaJ+E2eiBO4\nAQIhAPssS0+BR9w0bOdmjGqmdE9NrN5UJQcOW13s29+6QzUBAiEA2vWOepA5Apiu\npEA3pwoGdkVCrNSnnKjDQzDXBnpd3/cCIEFNd9sY4qUG4FWdXN6RnmXL7Sj0uZfH\nDMwzu8rEM5sBAiEAhvdoDNqLmbMdq3c+FsPSOeL1d21Zp/JK8kbPtFmHNf8CIQDV\n6FSZDwvWfuxaM7BsycQONkjDBTPNu+lqctJBGnBv3A==\n-----END RSA PRIVATE KEY-----\n")) + key, _ := x509.ParsePKCS1PrivateKey(rawKey.Bytes) + + signer := NewSigner(key) + + signature, err := signer.SignTextures("eyJ0aW1lc3RhbXAiOjE2MTQzMDcxMzQsInByb2ZpbGVJZCI6ImZmYzhmZGM5NTgyNDUwOWU4YTU3Yzk5Yjk0MGZiOTk2IiwicHJvZmlsZU5hbWUiOiJFcmlja1NrcmF1Y2giLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly9lbHkuYnkvc3RvcmFnZS9za2lucy82OWM2NzQwZDI5OTNlNWQ2ZjZhN2ZjOTI0MjBlZmMyOS5wbmcifX0sImVseSI6dHJ1ZX0") + require.NoError(t, err) + require.Equal(t, "IyHCxTP5ITquEXTHcwCtLd08jWWy16JwlQeWg8naxhoAVQecHGRdzHRscuxtdq/446kmeox7h4EfRN2A2ZLL+A==", signature) +} + +func TestSigner_GetPublicKey(t *testing.T) { + randomReader = &ConstantReader{} + + rawKey, _ := pem.Decode([]byte("-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnmUDlzHBQH3DpYef5WCO32\nTDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQJAItaxSHTe6PKbyEU/9pxj\nONdhYRYwVLLo56gnMYhkyoEqaaMsfov8hhoepkYZBMvZFB2bDOsQ2SaJ+E2eiBO4\nAQIhAPssS0+BR9w0bOdmjGqmdE9NrN5UJQcOW13s29+6QzUBAiEA2vWOepA5Apiu\npEA3pwoGdkVCrNSnnKjDQzDXBnpd3/cCIEFNd9sY4qUG4FWdXN6RnmXL7Sj0uZfH\nDMwzu8rEM5sBAiEAhvdoDNqLmbMdq3c+FsPSOeL1d21Zp/JK8kbPtFmHNf8CIQDV\n6FSZDwvWfuxaM7BsycQONkjDBTPNu+lqctJBGnBv3A==\n-----END RSA PRIVATE KEY-----\n")) + key, _ := x509.ParsePKCS1PrivateKey(rawKey.Bytes) + + signer := NewSigner(key) + + publicKey, err := signer.GetPublicKey() + require.NoError(t, err) + require.IsType(t, &rsa.PublicKey{}, publicKey) +} diff --git a/internal/signer/signer_test.go b/internal/signer/signer_test.go deleted file mode 100644 index 6fc8fea..0000000 --- a/internal/signer/signer_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package signer - -import ( - "crypto/rsa" - "crypto/x509" - "encoding/pem" - - "testing" - - assert "github.com/stretchr/testify/require" -) - -type ConstantReader struct { -} - -func (c *ConstantReader) Read(p []byte) (int, error) { - return 1, nil -} - -func TestSigner_SignTextures(t *testing.T) { - randomReader = &ConstantReader{} - - t.Run("sign textures", func(t *testing.T) { - rawKey, _ := pem.Decode([]byte("-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnmUDlzHBQH3DpYef5WCO32\nTDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQJAItaxSHTe6PKbyEU/9pxj\nONdhYRYwVLLo56gnMYhkyoEqaaMsfov8hhoepkYZBMvZFB2bDOsQ2SaJ+E2eiBO4\nAQIhAPssS0+BR9w0bOdmjGqmdE9NrN5UJQcOW13s29+6QzUBAiEA2vWOepA5Apiu\npEA3pwoGdkVCrNSnnKjDQzDXBnpd3/cCIEFNd9sY4qUG4FWdXN6RnmXL7Sj0uZfH\nDMwzu8rEM5sBAiEAhvdoDNqLmbMdq3c+FsPSOeL1d21Zp/JK8kbPtFmHNf8CIQDV\n6FSZDwvWfuxaM7BsycQONkjDBTPNu+lqctJBGnBv3A==\n-----END RSA PRIVATE KEY-----\n")) - key, _ := x509.ParsePKCS1PrivateKey(rawKey.Bytes) - - signer := &Signer{key} - - signature, err := signer.SignTextures("eyJ0aW1lc3RhbXAiOjE2MTQzMDcxMzQsInByb2ZpbGVJZCI6ImZmYzhmZGM5NTgyNDUwOWU4YTU3Yzk5Yjk0MGZiOTk2IiwicHJvZmlsZU5hbWUiOiJFcmlja1NrcmF1Y2giLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly9lbHkuYnkvc3RvcmFnZS9za2lucy82OWM2NzQwZDI5OTNlNWQ2ZjZhN2ZjOTI0MjBlZmMyOS5wbmcifX0sImVseSI6dHJ1ZX0") - assert.NoError(t, err) - assert.Equal(t, "IyHCxTP5ITquEXTHcwCtLd08jWWy16JwlQeWg8naxhoAVQecHGRdzHRscuxtdq/446kmeox7h4EfRN2A2ZLL+A==", signature) - }) - - t.Run("empty key", func(t *testing.T) { - signer := &Signer{} - - signature, err := signer.SignTextures("hello world") - assert.Error(t, err, "Key is empty") - assert.Empty(t, signature) - }) -} - -func TestSigner_GetPublicKey(t *testing.T) { - randomReader = &ConstantReader{} - - t.Run("get public key", func(t *testing.T) { - rawKey, _ := pem.Decode([]byte("-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBANbUpVCZkMKpfvYZ08W3lumdAaYxLBnmUDlzHBQH3DpYef5WCO32\nTDU6feIJ58A0lAywgtZ4wwi2dGHOz/1hAvcCAwEAAQJAItaxSHTe6PKbyEU/9pxj\nONdhYRYwVLLo56gnMYhkyoEqaaMsfov8hhoepkYZBMvZFB2bDOsQ2SaJ+E2eiBO4\nAQIhAPssS0+BR9w0bOdmjGqmdE9NrN5UJQcOW13s29+6QzUBAiEA2vWOepA5Apiu\npEA3pwoGdkVCrNSnnKjDQzDXBnpd3/cCIEFNd9sY4qUG4FWdXN6RnmXL7Sj0uZfH\nDMwzu8rEM5sBAiEAhvdoDNqLmbMdq3c+FsPSOeL1d21Zp/JK8kbPtFmHNf8CIQDV\n6FSZDwvWfuxaM7BsycQONkjDBTPNu+lqctJBGnBv3A==\n-----END RSA PRIVATE KEY-----\n")) - key, _ := x509.ParsePKCS1PrivateKey(rawKey.Bytes) - - signer := &Signer{key} - - publicKey, err := signer.GetPublicKey() - assert.NoError(t, err) - assert.IsType(t, &rsa.PublicKey{}, publicKey) - }) - - t.Run("empty key", func(t *testing.T) { - signer := &Signer{} - - publicKey, err := signer.GetPublicKey() - assert.Error(t, err, "Key is empty") - assert.Nil(t, publicKey) - }) -} diff --git a/internal/version/version.go b/internal/version/version.go index 7fc7cff..ca7bc20 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -1,5 +1,7 @@ package version +const MajorVersion = 5 + var ( version = "undefined" commit = "unknown"