github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/controller/pkg/usertokens/pkitokens/jwt.go (about) 1 package pkitokens 2 3 import ( 4 "context" 5 "crypto" 6 "fmt" 7 "io/ioutil" 8 "net/url" 9 10 jwt "github.com/dgrijalva/jwt-go" 11 "go.aporeto.io/enforcerd/trireme-lib/controller/pkg/usertokens/common" 12 ) 13 14 // PKIJWTVerifier is a generic JWT PKI verifier. It assumes that the tokens have 15 // been signed by a private key, and it validates them with the provide public key. 16 // This is a simple and stateless verifier that doesn't depend on central server 17 // for validating the tokens. The public key is provided out-of-band. 18 type PKIJWTVerifier struct { 19 JWTCertPEM []byte 20 keys []crypto.PublicKey 21 RedirectURL string 22 } 23 24 // NewVerifierFromFile assumes that the input is provided as file path. 25 func NewVerifierFromFile(jwtcertPath string, redirectURI string, redirectOnFail, redirectOnNoToken bool) (*PKIJWTVerifier, error) { 26 jwtCertPEM, err := ioutil.ReadFile(jwtcertPath) 27 if err != nil { 28 return nil, fmt.Errorf("failed to read JWT signing certificates or public keys from file: %s", err) 29 } 30 return NewVerifierFromPEM(jwtCertPEM, redirectURI, redirectOnFail, redirectOnNoToken) 31 } 32 33 // NewVerifierFromPEM assumes that the input is a PEM byte array. 34 func NewVerifierFromPEM(jwtCertPEM []byte, redirectURI string, redirectOnFail, redirectOnNoToken bool) (*PKIJWTVerifier, error) { 35 keys, err := parsePublicKeysFromPEM(jwtCertPEM) 36 // pay attention to the return format of parsePublicKeysFromPEM 37 // when checking for an error here 38 if keys == nil && err != nil { 39 return nil, fmt.Errorf("failed to read JWT signing certificates or public keys from PEM: %s", err) 40 } 41 return &PKIJWTVerifier{ 42 JWTCertPEM: jwtCertPEM, 43 keys: keys, 44 RedirectURL: redirectURI, 45 }, nil 46 } 47 48 // NewVerifier creates a new verifier from the provided configuration. 49 func NewVerifier(v *PKIJWTVerifier) (*PKIJWTVerifier, error) { 50 if len(v.JWTCertPEM) == 0 { 51 return v, nil 52 } 53 keys, err := parsePublicKeysFromPEM(v.JWTCertPEM) 54 // pay attention to the return format of parsePublicKeysFromPEM 55 // when checking for an error here 56 if keys == nil && err != nil { 57 return nil, fmt.Errorf("failed to parse JWT signing certificates or public keys from PEM: %s", err) 58 } 59 v.keys = keys 60 return v, nil 61 } 62 63 // Validate parses a generic JWT token and flattens the claims in a normalized form. It 64 // assumes that any of the JWT signing certs or public keys will validate the token. 65 func (j *PKIJWTVerifier) Validate(ctx context.Context, tokenString string) ([]string, bool, string, error) { 66 if len(tokenString) == 0 { 67 return []string{}, false, tokenString, fmt.Errorf("Empty token") 68 } 69 if len(j.keys) == 0 { 70 return []string{}, false, tokenString, fmt.Errorf("No public keys loaded into verifier") 71 } 72 73 // iterate over all public keys that we have and try to validate the token 74 // the first one to succeed will be used 75 var errs []error 76 for _, key := range j.keys { 77 claims := &jwt.MapClaims{} 78 token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { 79 switch token.Method.(type) { 80 case *jwt.SigningMethodECDSA: 81 if isECDSAPublicKey(key) { 82 return key, nil 83 } 84 case *jwt.SigningMethodRSA: 85 if isRSAPublicKey(key) { 86 return key, nil 87 } 88 default: 89 return nil, fmt.Errorf("unsupported signing method '%T'", token.Method) 90 } 91 return nil, fmt.Errorf("signing method '%T' and public key type '%T' mismatch", token.Method, key) 92 }) 93 94 // cover all error cases after parsing/verifying 95 if err != nil { 96 errs = append(errs, err) 97 continue 98 } 99 if token == nil { 100 errs = append(errs, fmt.Errorf("no token was parsed")) 101 continue 102 } 103 if !token.Valid { 104 errs = append(errs, fmt.Errorf("token failed to verify against public key")) 105 continue 106 } 107 108 // return successful on match/verification with the first key 109 attributes := []string{} 110 for k, v := range *claims { 111 attributes = append(attributes, common.FlattenClaim(k, v)...) 112 } 113 return attributes, false, tokenString, nil 114 } 115 116 // generate a detailed error 117 var detailedError string 118 for i, err := range errs { 119 detailedError += err.Error() 120 if i+1 < len(errs) { 121 detailedError += "; " 122 } 123 } 124 return []string{}, false, tokenString, fmt.Errorf("Invalid token - errors: [%s]", detailedError) 125 } 126 127 // VerifierType returns the type of the verifier. 128 func (j *PKIJWTVerifier) VerifierType() common.JWTType { 129 return common.PKI 130 } 131 132 // Callback is called by an IDP. Not implemented here. No central authorizer for the tokens. 133 func (j *PKIJWTVerifier) Callback(ctx context.Context, u *url.URL) (string, string, int, error) { 134 return "", "", 0, nil 135 } 136 137 // IssueRedirect issues a redirect. Not implemented. There is no need for a redirect. 138 func (j *PKIJWTVerifier) IssueRedirect(originURL string) string { 139 return "" 140 }