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 }