github.com/greenpau/go-authcrunch@v1.1.4/pkg/kms/keystore_test.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kms
    16  
    17  import (
    18  	"fmt"
    19  	jwtlib "github.com/golang-jwt/jwt/v4"
    20  	"github.com/greenpau/go-authcrunch/internal/tests"
    21  	"github.com/greenpau/go-authcrunch/pkg/errors"
    22  	"github.com/greenpau/go-authcrunch/pkg/requests"
    23  	"github.com/greenpau/go-authcrunch/pkg/user"
    24  	"testing"
    25  	"time"
    26  )
    27  
    28  type TestUserClaims struct {
    29  	Roles         []string               `json:"roles,omitempty" xml:"roles" yaml:"roles,omitempty"`
    30  	Role          string                 `json:"role,omitempty" xml:"role" yaml:"role,omitempty"`
    31  	Groups        []string               `json:"groups,omitempty" xml:"groups" yaml:"groups,omitempty"`
    32  	Group         string                 `json:"group,omitempty" xml:"group" yaml:"group,omitempty"`
    33  	Organizations []string               `json:"org,omitempty" xml:"org" yaml:"org,omitempty"`
    34  	Address       string                 `json:"addr,omitempty" xml:"addr" yaml:"addr,omitempty"`
    35  	AppMetadata   map[string]interface{} `json:"app_metadata,omitempty" xml:"app_metadata" yaml:"app_metadata,omitempty"`
    36  	jwtlib.StandardClaims
    37  }
    38  
    39  func TestKeystoreOperators(t *testing.T) {
    40  	testcases := []struct {
    41  		name            string
    42  		config          string
    43  		signTokenName   string
    44  		signAlgorithm   string
    45  		verifyTokenName string
    46  		sign            bool
    47  		user            *user.User
    48  		claims          *TestUserClaims
    49  		roles           []string
    50  		addr            string
    51  		operatorErr     bool
    52  		operatorSignErr bool
    53  		err             error
    54  		shouldErr       bool
    55  	}{
    56  		{
    57  			name:   "user with roles claims and ip address",
    58  			config: `crypto key sign-verify foobar`,
    59  			claims: &TestUserClaims{
    60  				Roles: []string{"admin", "editor", "viewer"},
    61  				StandardClaims: jwtlib.StandardClaims{
    62  					ExpiresAt: time.Now().Add(10 * time.Minute).Unix(),
    63  					IssuedAt:  time.Now().Add(10 * time.Minute * -1).Unix(),
    64  					NotBefore: time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
    65  					Subject:   "smithj@outlook.com",
    66  				},
    67  			},
    68  			roles: []string{"admin", "editor", "viewer"},
    69  			addr:  "127.0.0.1",
    70  		},
    71  		{
    72  			name:   "user with groups claims and ip address",
    73  			config: `crypto key sign-verify foobar`,
    74  			claims: &TestUserClaims{
    75  				Groups: []string{"admin", "editor", "viewer"},
    76  				StandardClaims: jwtlib.StandardClaims{
    77  					ExpiresAt: time.Now().Add(10 * time.Minute).Unix(),
    78  					IssuedAt:  time.Now().Add(10 * time.Minute * -1).Unix(),
    79  					NotBefore: time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
    80  					Subject:   "smithj@outlook.com",
    81  				},
    82  			},
    83  			roles: []string{"admin", "editor", "viewer"},
    84  			addr:  "127.0.0.1",
    85  		},
    86  		{
    87  			name:   "user with role claim and ip address",
    88  			config: `crypto key sign-verify foobar`,
    89  			claims: &TestUserClaims{
    90  				Role:    "admin",
    91  				Address: "192.168.1.1",
    92  				StandardClaims: jwtlib.StandardClaims{
    93  					ExpiresAt: time.Now().Add(10 * time.Minute).Unix(),
    94  					IssuedAt:  time.Now().Add(10 * time.Minute * -1).Unix(),
    95  					NotBefore: time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
    96  					Subject:   "smithj@outlook.com",
    97  				},
    98  			},
    99  			roles: []string{"admin"},
   100  			addr:  "192.168.1.1",
   101  		},
   102  		{
   103  			name:   "user with group claim and ip address",
   104  			config: `crypto key sign-verify foobar`,
   105  			claims: &TestUserClaims{
   106  				Group:   "admin",
   107  				Address: "192.168.1.1",
   108  				StandardClaims: jwtlib.StandardClaims{
   109  					ExpiresAt: time.Now().Add(10 * time.Minute).Unix(),
   110  					IssuedAt:  time.Now().Add(10 * time.Minute * -1).Unix(),
   111  					NotBefore: time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
   112  					Subject:   "smithj@outlook.com",
   113  				},
   114  			},
   115  			roles: []string{"admin"},
   116  			addr:  "192.168.1.1",
   117  		},
   118  		{
   119  			name:   "user with expired token",
   120  			config: `crypto key sign-verify foobar`,
   121  			claims: &TestUserClaims{
   122  				Roles: []string{"admin", "editor", "viewer"},
   123  				StandardClaims: jwtlib.StandardClaims{
   124  					ExpiresAt: time.Now().Add(5 * time.Minute * -1).Unix(),
   125  					IssuedAt:  time.Now().Add(10 * time.Minute * -1).Unix(),
   126  					NotBefore: time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
   127  					Subject:   "smithj@outlook.com",
   128  				},
   129  			},
   130  			roles:     []string{"admin", "editor", "viewer"},
   131  			addr:      "127.0.0.1",
   132  			shouldErr: true,
   133  			err:       errors.ErrCryptoKeyStoreParseTokenExpired,
   134  		},
   135  		{
   136  			name:   "user with not yet ready token",
   137  			config: `crypto key sign-verify foobar`,
   138  			claims: &TestUserClaims{
   139  				Roles: []string{"admin", "editor", "viewer"},
   140  				AppMetadata: map[string]interface{}{
   141  					"authorization": map[string]interface{}{
   142  						"roles": []interface{}{
   143  							1, 2, 3,
   144  						},
   145  					},
   146  				},
   147  				StandardClaims: jwtlib.StandardClaims{
   148  					ExpiresAt: time.Now().Add(20 * time.Minute).Unix(),
   149  					IssuedAt:  time.Now().Add(10 * time.Minute * -1).Unix(),
   150  					NotBefore: time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
   151  					Subject:   "smithj@outlook.com",
   152  				},
   153  			},
   154  			roles:     []string{"admin", "editor", "viewer"},
   155  			addr:      "127.0.0.1",
   156  			shouldErr: true,
   157  			err:       errors.ErrCryptoKeyStoreTokenData,
   158  		},
   159  		{
   160  			name:      "nil keys",
   161  			shouldErr: true,
   162  			err:       errors.ErrCryptoKeyStoreAddKeyNil,
   163  		},
   164  		{
   165  			name:            "token name mismatch",
   166  			config:          `crypto key sign-verify foobar`,
   167  			verifyTokenName: `foobar`,
   168  			claims: &TestUserClaims{
   169  				Group:   "admin",
   170  				Address: "192.168.1.1",
   171  				StandardClaims: jwtlib.StandardClaims{
   172  					ExpiresAt: time.Now().Add(10 * time.Minute).Unix(),
   173  					IssuedAt:  time.Now().Add(10 * time.Minute * -1).Unix(),
   174  					NotBefore: time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
   175  					Subject:   "smithj@outlook.com",
   176  				},
   177  			},
   178  			roles:     []string{"admin"},
   179  			addr:      "192.168.1.1",
   180  			shouldErr: true,
   181  			err:       errors.ErrCryptoKeyStoreParseTokenFailed,
   182  		},
   183  		{
   184  			name:            "failed verification",
   185  			config:          `crypto key sign-verify foobar`,
   186  			sign:            true,
   187  			signAlgorithm:   "HS512",
   188  			verifyTokenName: `foobar`,
   189  			user:            newTestUser(),
   190  			operatorErr:     true,
   191  			shouldErr:       true,
   192  			err:             errors.ErrCryptoKeyStoreParseTokenFailed,
   193  		},
   194  		{
   195  			name:            "failed signing due to algo mismatch",
   196  			config:          `crypto key sign-verify foobar`,
   197  			sign:            true,
   198  			signAlgorithm:   "RS512",
   199  			verifyTokenName: `foobar`,
   200  			user:            newTestUser(),
   201  			operatorErr:     true,
   202  			operatorSignErr: true,
   203  			shouldErr:       true,
   204  			err:             errors.ErrUnsupportedSigningMethod.WithArgs("RS512"),
   205  		},
   206  		{
   207  			name:            "failed signing due to token name mismatch",
   208  			config:          `crypto key sign-verify foobar`,
   209  			sign:            true,
   210  			signAlgorithm:   "RS512",
   211  			signTokenName:   `foobar`,
   212  			user:            newTestUser(),
   213  			operatorErr:     true,
   214  			operatorSignErr: true,
   215  			shouldErr:       true,
   216  			err:             errors.ErrCryptoKeyStoreSignTokenFailed,
   217  		},
   218  	}
   219  	for _, tc := range testcases {
   220  		t.Run(tc.name, func(t *testing.T) {
   221  			var signedToken string
   222  			var msgs []string
   223  			var keys []*CryptoKey
   224  			msgs = append(msgs, fmt.Sprintf("test name: %s", tc.name))
   225  			if tc.config != "" {
   226  				configs, err := ParseCryptoKeyConfigs(tc.config)
   227  				if err != nil {
   228  					t.Fatalf("failed parsing configs: %v", err)
   229  				}
   230  				keys, err = GetKeysFromConfigs(configs)
   231  				if err != nil {
   232  					t.Fatalf("failed getting keys from configs: %v", err)
   233  				}
   234  			} else {
   235  				keys = []*CryptoKey{nil}
   236  			}
   237  
   238  			ks := NewCryptoKeyStore()
   239  			if err := ks.AddKeys(keys); err != nil {
   240  				if !tc.operatorErr {
   241  					if tests.EvalErrWithLog(t, err, "add keys", tc.shouldErr, tc.err, msgs) {
   242  						return
   243  					}
   244  					t.Fatalf("failed adding keys to crypto key store: %v", err)
   245  				}
   246  			}
   247  
   248  			privKey := keys[0]
   249  			// pubKey := keys[0]
   250  			// if err := ks.SignToken(privKey.Sign.Token.Name, privKey.Sign.Token.DefaultMethod, usr); err != nil {
   251  			//   t.Fatal(err)
   252  			// }
   253  			if tc.signTokenName == "" {
   254  				tc.signTokenName = privKey.Sign.Token.Name
   255  			}
   256  
   257  			if tc.sign {
   258  				err := ks.SignToken(tc.signTokenName, tc.signAlgorithm, tc.user)
   259  				if tc.operatorSignErr {
   260  					if tests.EvalErrWithLog(t, err, "sign token", tc.shouldErr, tc.err, msgs) {
   261  						return
   262  					}
   263  				}
   264  			} else {
   265  				token := jwtlib.NewWithClaims(jwtlib.SigningMethodHS512, tc.claims)
   266  				var err error
   267  				signedToken, err = token.SignedString(privKey.Sign.Secret)
   268  				if err != nil {
   269  					t.Fatalf("failed signing claims: %s", err)
   270  				}
   271  			}
   272  
   273  			msgs = append(msgs, fmt.Sprintf("signed token: %s", signedToken))
   274  
   275  			if tc.verifyTokenName == "" {
   276  				tc.verifyTokenName = privKey.Sign.Token.Name
   277  			}
   278  
   279  			ar := requests.NewAuthorizationRequest()
   280  			ar.ID = "TEST_REQUEST_ID"
   281  			ar.SessionID = "TEST_SESSION_ID"
   282  			ar.Token.Name = tc.verifyTokenName
   283  			ar.Token.Payload = signedToken
   284  			usr, err := ks.ParseToken(ar)
   285  			if tests.EvalErrWithLog(t, err, "parse token", tc.shouldErr, tc.err, msgs) {
   286  				return
   287  			}
   288  
   289  			msgs = append(msgs, fmt.Sprintf("parsed claims: %v", usr.Claims))
   290  			msgs = append(msgs, fmt.Sprintf("roles: %v", usr.Claims.Roles))
   291  			if len(tc.roles) > 0 {
   292  				tests.EvalObjectsWithLog(t, "roles", tc.roles, usr.Claims.Roles, msgs)
   293  			}
   294  		})
   295  	}
   296  }
   297  
   298  func TestCryptoKeyStoreAutoGenerate(t *testing.T) {
   299  	var testcases = []struct {
   300  		name      string
   301  		tag       string
   302  		algorithm string
   303  		shouldErr bool
   304  		err       error
   305  	}{
   306  		{
   307  			name:      "generate es512 key pair",
   308  			tag:       "default",
   309  			algorithm: "ES512",
   310  			// shouldErr: true,
   311  			//err:       fmt.Errorf(`kms: file "foo" is not supported due to extension type`),
   312  		},
   313  	}
   314  	for _, tc := range testcases {
   315  		t.Run(tc.name, func(t *testing.T) {
   316  			msgs := []string{fmt.Sprintf("test name: %s", tc.name)}
   317  			msgs = append(msgs, fmt.Sprintf("algorithm: %s", tc.algorithm))
   318  			ks := NewCryptoKeyStore()
   319  			err := ks.AutoGenerate(tc.tag, tc.algorithm)
   320  			if tests.EvalErrWithLog(t, err, nil, tc.shouldErr, tc.err, msgs) {
   321  				return
   322  			}
   323  		})
   324  	}
   325  }