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  }