github.com/greenpau/go-authcrunch@v1.1.4/pkg/kms/keystore.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 "strings" 19 20 jwtlib "github.com/golang-jwt/jwt/v4" 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 "go.uber.org/zap" 25 ) 26 27 var ( 28 reservedTokenNames = map[string]bool{ 29 "access_token": true, 30 "jwt_access_token": true, 31 "bearer": true, 32 } 33 ) 34 35 // CryptoKeyStore constains keys assembled for a specific purpose, i.e. signing or 36 // validation. 37 type CryptoKeyStore struct { 38 keys []*CryptoKey 39 signKeys []*CryptoKey 40 verifyKeys []*CryptoKey 41 logger *zap.Logger 42 defaults map[string]interface{} 43 } 44 45 // NewCryptoKeyStore returns a new instance of CryptoKeyStore 46 func NewCryptoKeyStore() *CryptoKeyStore { 47 ks := &CryptoKeyStore{} 48 ks.defaults = make(map[string]interface{}) 49 return ks 50 } 51 52 // SetLogger adds a logger to CryptoKeyStore. 53 func (ks *CryptoKeyStore) SetLogger(logger *zap.Logger) { 54 ks.logger = logger 55 } 56 57 // AddDefaults adds default settings to CryptoKeyStore. 58 func (ks *CryptoKeyStore) AddDefaults(m map[string]interface{}) error { 59 if m == nil { 60 return nil 61 } 62 if ks.defaults == nil { 63 ks.defaults = make(map[string]interface{}) 64 } 65 for k, v := range m { 66 switch k { 67 case "token_name": 68 ks.defaults[k] = v.(string) 69 case "token_lifetime": 70 ks.defaults[k] = int(v.(float64)) 71 default: 72 ks.defaults[k] = v 73 } 74 } 75 return nil 76 } 77 78 // AutoGenerate auto-generates public-private key pair capable of both 79 // signing and verifying tokens. 80 func (ks *CryptoKeyStore) AutoGenerate(tag, algo string) error { 81 cfg := &CryptoKeyConfig{ 82 ID: "0", 83 Usage: "sign-verify", 84 TokenName: "access_token", 85 Source: "config", 86 TokenLifetime: 900, 87 parsed: true, 88 } 89 90 if ks.defaults != nil { 91 if _, exists := ks.defaults["token_name"]; exists { 92 cfg.TokenName = ks.defaults["token_name"].(string) 93 } 94 if _, exists := ks.defaults["token_lifetime"]; exists { 95 cfg.TokenLifetime = ks.defaults["token_lifetime"].(int) 96 } 97 } 98 99 if len(ks.keys) > 0 { 100 return errors.ErrCryptoKeyStoreAutoGenerateNotAvailable 101 } 102 103 key, err := generateKey(cfg, tag, algo) 104 if err != nil { 105 return err 106 } 107 108 key.enableUsage() 109 ks.keys = append(ks.keys, key) 110 ks.signKeys = append(ks.signKeys, key) 111 ks.verifyKeys = append(ks.verifyKeys, key) 112 return nil 113 } 114 115 // GetKeys returns CryptoKey instances from CryptoKeyStore. 116 func (ks *CryptoKeyStore) GetKeys() []*CryptoKey { 117 return ks.keys 118 } 119 120 // GetSignKeys returns CryptoKey instances with key signing capabilities 121 // from CryptoKeyStore. 122 func (ks *CryptoKeyStore) GetSignKeys() []*CryptoKey { 123 return ks.signKeys 124 } 125 126 // GetVerifyKeys returns CryptoKey instances with key verification capabilities 127 // from CryptoKeyStore. 128 func (ks *CryptoKeyStore) GetVerifyKeys() []*CryptoKey { 129 return ks.verifyKeys 130 } 131 132 // AddKeysWithConfigs adds CryptoKey instances by providing their 133 // configurations to CryptoKeyStore. 134 func (ks *CryptoKeyStore) AddKeysWithConfigs(cfgs []*CryptoKeyConfig) error { 135 keys, err := GetKeysFromConfigs(cfgs) 136 if err != nil { 137 return err 138 } 139 for _, k := range keys { 140 if err := ks.AddKey(k); err != nil { 141 return err 142 } 143 } 144 return nil 145 } 146 147 // HasVerifyKeys returns true if CryptoKeyStore has key verification 148 // capabilities. 149 func (ks *CryptoKeyStore) HasVerifyKeys() error { 150 if len(ks.verifyKeys) > 0 { 151 return nil 152 } 153 return errors.ErrCryptoKeyStoreNoVerifyKeysFound 154 } 155 156 // HasSignKeys returns true if CryptoKeyStore has key signing 157 // capabilities. 158 func (ks *CryptoKeyStore) HasSignKeys() error { 159 if len(ks.signKeys) > 0 { 160 return nil 161 } 162 return errors.ErrCryptoKeyStoreNoSignKeysFound 163 } 164 165 // AddKeys adds CryptoKey instances to CryptoKeyStore. 166 func (ks *CryptoKeyStore) AddKeys(keys []*CryptoKey) error { 167 for _, k := range keys { 168 if err := ks.AddKey(k); err != nil { 169 return err 170 } 171 } 172 return nil 173 } 174 175 // AddKey adds CryptoKey instance to CryptoKeyStore. 176 func (ks *CryptoKeyStore) AddKey(k *CryptoKey) error { 177 if k == nil { 178 return errors.ErrCryptoKeyStoreAddKeyNil 179 } 180 if k.Sign != nil { 181 if k.Sign.Capable { 182 ks.signKeys = append(ks.signKeys, k) 183 } 184 } 185 if k.Verify != nil { 186 if k.Verify.Capable { 187 ks.verifyKeys = append(ks.verifyKeys, k) 188 } 189 } 190 if k.Verify == nil && k.Sign == nil { 191 return errors.ErrCryptoKeyStoreAddKeyNil 192 } 193 ks.keys = append(ks.keys, k) 194 return nil 195 } 196 197 // ParseToken parses JWT token and returns User instance. 198 func (ks *CryptoKeyStore) ParseToken(ar *requests.AuthorizationRequest) (*user.User, error) { 199 for _, k := range ks.verifyKeys { 200 if _, exists := reservedTokenNames[ar.Token.Name]; !exists { 201 if ar.Token.Name != k.Verify.Token.Name { 202 continue 203 } 204 } 205 parsedToken, err := jwtlib.Parse(ar.Token.Payload, k.ProvideKey) 206 if err != nil && !strings.Contains(err.Error(), "is expired") { 207 continue 208 } 209 210 userData := make(map[string]interface{}) 211 errData := make(map[string]interface{}) 212 for k, v := range parsedToken.Claims.(jwtlib.MapClaims) { 213 switch k { 214 case "iss": 215 if strings.HasPrefix(v.(string), "http") { 216 ar.Redirect.AuthURL = strings.TrimSuffix(v.(string), "authorization-code-callback") 217 } 218 case "mail", "email": 219 errData["email"] = v.(string) 220 ar.Redirect.LoginHint = v.(string) 221 case "sub", "name", "jti": 222 errData[k] = v.(string) 223 } 224 userData[k] = v 225 } 226 227 if err != nil { 228 ar.Response.User = errData 229 return nil, errors.ErrCryptoKeyStoreParseTokenExpired 230 } 231 232 usr, err := user.NewUser(userData) 233 if err != nil { 234 return usr, errors.ErrCryptoKeyStoreTokenData 235 } 236 return usr, nil 237 } 238 return nil, errors.ErrCryptoKeyStoreParseTokenFailed 239 } 240 241 // SignToken signs user claims and add signed token to user identity. 242 func (ks *CryptoKeyStore) SignToken(tokenName, signMethod interface{}, usr *user.User) error { 243 for _, k := range ks.signKeys { 244 if tokenName != nil { 245 if tokenName.(string) != k.Sign.Token.Name { 246 continue 247 } 248 } 249 response, err := k.sign(signMethod, usr.AsMap()) 250 if err != nil { 251 return err 252 } 253 usr.Token = response.(string) 254 usr.TokenName = k.Sign.Token.Name 255 return nil 256 } 257 return errors.ErrCryptoKeyStoreSignTokenFailed 258 } 259 260 // GetTokenLifetime returns lifetime for a signed token. 261 func (ks *CryptoKeyStore) GetTokenLifetime(tokenName, signMethod interface{}) int { 262 for _, k := range ks.signKeys { 263 if tokenName != nil { 264 if tokenName.(string) != k.Sign.Token.Name { 265 continue 266 } 267 } 268 return k.Sign.Token.MaxLifetime 269 } 270 return 900 271 }