github.com/BTBurke/caddy-jwt@v3.7.1+incompatible/keys.go (about)

     1  package jwt
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"time"
     8  
     9  	"github.com/dgrijalva/jwt-go"
    10  )
    11  
    12  const ENV_PUBLIC_KEY = "JWT_PUBLIC_KEY"
    13  const ENV_SECRET = "JWT_SECRET"
    14  
    15  // KeyBackend provides a generic interface for providing key material for HS, RS, and ES algorithms
    16  type KeyBackend interface {
    17  	ProvideKey(token *jwt.Token) (interface{}, error)
    18  }
    19  
    20  // LazyPublicKeyBackend contains state to manage lazy key loading for RS and ES family algorithms
    21  type LazyPublicKeyBackend struct {
    22  	filename  string
    23  	modTime   time.Time
    24  	publicKey interface{}
    25  }
    26  
    27  // NewLazyPublicKeyFileBackend returns a new LazyPublicKeyBackend
    28  func NewLazyPublicKeyFileBackend(value string) (*LazyPublicKeyBackend, error) {
    29  	if len(value) <= 0 {
    30  		return nil, fmt.Errorf("empty filename for public key provided")
    31  	}
    32  	return &LazyPublicKeyBackend{
    33  		filename: value,
    34  	}, nil
    35  }
    36  
    37  // ProvideKey will lazily load a secret key in a file, using a cached value if the key
    38  // material has not changed.  An error is returned if the token does not match the
    39  // expected signing algorithm.
    40  func (instance *LazyPublicKeyBackend) ProvideKey(token *jwt.Token) (interface{}, error) {
    41  	err := instance.loadIfRequired()
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	if err := AssertPublicKeyAndTokenCombination(instance.publicKey, token); err != nil {
    46  		return nil, err
    47  	}
    48  	return instance.publicKey, nil
    49  }
    50  
    51  func (instance *LazyPublicKeyBackend) loadIfRequired() error {
    52  	finfo, err := os.Stat(instance.filename)
    53  	if os.IsNotExist(err) {
    54  		return fmt.Errorf("public key file '%s' does not exist", instance.filename)
    55  	}
    56  	if instance.publicKey == nil || !finfo.ModTime().Equal(instance.modTime) {
    57  		instance.publicKey, err = ReadPublicKeyFile(instance.filename)
    58  		if err != nil {
    59  			return fmt.Errorf("could not load public key file '%s': %v", instance.filename, err)
    60  		}
    61  		if instance.publicKey == nil {
    62  			return fmt.Errorf("no public key contained in file '%s'", instance.filename)
    63  		}
    64  	}
    65  	return nil
    66  }
    67  
    68  // LazyHmacKeyBackend contains state to manage lazy key loading for HS family algorithms
    69  type LazyHmacKeyBackend struct {
    70  	filename string
    71  	modTime  time.Time
    72  	secret   []byte
    73  }
    74  
    75  // NewLazyHmacKeyBackend creates a new LazyHmacKeyBackend
    76  func NewLazyHmacKeyBackend(value string) (*LazyHmacKeyBackend, error) {
    77  	if len(value) <= 0 {
    78  		return nil, fmt.Errorf("empty filename for secret provided")
    79  	}
    80  	return &LazyHmacKeyBackend{
    81  		filename: value,
    82  	}, nil
    83  }
    84  
    85  // ProvideKey will lazily load a secret key in a file, using a cached value if the key
    86  // material has not changed.  An error is returned if the token does not match the
    87  // expected signing algorithm.
    88  func (instance *LazyHmacKeyBackend) ProvideKey(token *jwt.Token) (interface{}, error) {
    89  	err := instance.loadIfRequired()
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	if err := AssertHmacToken(token); err != nil {
    94  		return nil, err
    95  	}
    96  	return instance.secret, nil
    97  }
    98  
    99  func (instance *LazyHmacKeyBackend) loadIfRequired() error {
   100  	finfo, err := os.Stat(instance.filename)
   101  	if os.IsNotExist(err) {
   102  		return fmt.Errorf("secret file '%s' does not exist", instance.filename)
   103  	}
   104  	if instance.secret == nil || !finfo.ModTime().Equal(instance.modTime) {
   105  		instance.secret, err = ioutil.ReadFile(instance.filename)
   106  		if err != nil {
   107  			return fmt.Errorf("could not load secret file '%s': %v", instance.filename, err)
   108  		}
   109  		if instance.secret == nil {
   110  			return fmt.Errorf("no secret contained in file '%s'", instance.filename)
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  // NewDefaultKeyBackends will read from the environment and return key backends based on
   117  // values from environment variables JWT_SECRET or JWT_PUBLIC_KEY.  An error is returned if
   118  // the keys are not able to be parsed or if an inconsistent configuration is found.
   119  func NewDefaultKeyBackends() ([]KeyBackend, error) {
   120  	result := []KeyBackend{}
   121  
   122  	secret := os.Getenv(ENV_SECRET)
   123  	if len(secret) > 0 {
   124  		result = append(result, &HmacKeyBackend{
   125  			secret: []byte(secret),
   126  		})
   127  	}
   128  
   129  	envPubKey := os.Getenv(ENV_PUBLIC_KEY)
   130  	if len(envPubKey) > 0 {
   131  		pub, err := ParsePublicKey([]byte(envPubKey))
   132  		if err != nil {
   133  			return nil, fmt.Errorf("public key provided in environment variable %s could not be read: %v", ENV_PUBLIC_KEY, err)
   134  		}
   135  		result = append(result, &PublicKeyBackend{
   136  			publicKey: pub,
   137  		})
   138  	}
   139  
   140  	if len(result) == 0 {
   141  		return nil, nil
   142  	}
   143  	if len(result) > 1 {
   144  		return nil, fmt.Errorf("cannot configure both HMAC and RSA/ECDSA tokens on the same site")
   145  	}
   146  
   147  	return result, nil
   148  }
   149  
   150  // PublicKeyBackend is an RSA or ECDSA key provider
   151  type PublicKeyBackend struct {
   152  	publicKey interface{}
   153  }
   154  
   155  // ProvideKey will asssert that the token signing algorithm and the configured key match
   156  func (instance *PublicKeyBackend) ProvideKey(token *jwt.Token) (interface{}, error) {
   157  	if err := AssertPublicKeyAndTokenCombination(instance.publicKey, token); err != nil {
   158  		return nil, err
   159  	}
   160  	return instance.publicKey, nil
   161  }
   162  
   163  // HmacKeyBacked is an HMAC-SHA key provider
   164  type HmacKeyBackend struct {
   165  	secret []byte
   166  }
   167  
   168  // ProvideKey will assert that the token signing algorithm and the configured key match
   169  func (instance *HmacKeyBackend) ProvideKey(token *jwt.Token) (interface{}, error) {
   170  	if err := AssertHmacToken(token); err != nil {
   171  		return nil, err
   172  	}
   173  	return instance.secret, nil
   174  }
   175  
   176  // NoopKeyBackend always returns an error when no key signing method is specified
   177  type NoopKeyBackend struct{}
   178  
   179  // ProvideKey always returns an error when no key signing method is specified
   180  func (instance *NoopKeyBackend) ProvideKey(token *jwt.Token) (interface{}, error) {
   181  	return nil, fmt.Errorf("there is no keybackend available")
   182  }