Rework security module, replace JWT library, invalidate JWT tokens signed for Chrly v4, generate RSA key in runtime when not provided via configuration

This commit is contained in:
ErickSkrauch 2024-02-01 12:11:39 +01:00
parent 11340289ad
commit 10c11bc060
No known key found for this signature in database
GPG Key ID: 669339FCBB30EE0E
15 changed files with 246 additions and 321 deletions

2
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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
},
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
})
}

82
internal/security/jwt.go Normal file
View File

@ -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
}

View File

@ -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")
})
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
})
}

View File

@ -1,5 +1,7 @@
package version
const MajorVersion = 5
var (
version = "undefined"
commit = "unknown"