| // Copyright 2018 The etcd Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package auth |
| |
| import ( |
| "crypto/ecdsa" |
| "crypto/rsa" |
| "fmt" |
| "io/ioutil" |
| "time" |
| |
| jwt "github.com/dgrijalva/jwt-go" |
| ) |
| |
| const ( |
| optSignMethod = "sign-method" |
| optPublicKey = "pub-key" |
| optPrivateKey = "priv-key" |
| optTTL = "ttl" |
| ) |
| |
| var knownOptions = map[string]bool{ |
| optSignMethod: true, |
| optPublicKey: true, |
| optPrivateKey: true, |
| optTTL: true, |
| } |
| |
| var ( |
| // DefaultTTL will be used when a 'ttl' is not specified |
| DefaultTTL = 5 * time.Minute |
| ) |
| |
| type jwtOptions struct { |
| SignMethod jwt.SigningMethod |
| PublicKey []byte |
| PrivateKey []byte |
| TTL time.Duration |
| } |
| |
| // ParseWithDefaults will load options from the specified map or set defaults where appropriate |
| func (opts *jwtOptions) ParseWithDefaults(optMap map[string]string) error { |
| if opts.TTL == 0 && optMap[optTTL] == "" { |
| opts.TTL = DefaultTTL |
| } |
| |
| return opts.Parse(optMap) |
| } |
| |
| // Parse will load options from the specified map |
| func (opts *jwtOptions) Parse(optMap map[string]string) error { |
| var err error |
| if ttl := optMap[optTTL]; ttl != "" { |
| opts.TTL, err = time.ParseDuration(ttl) |
| if err != nil { |
| return err |
| } |
| } |
| |
| if file := optMap[optPublicKey]; file != "" { |
| opts.PublicKey, err = ioutil.ReadFile(file) |
| if err != nil { |
| return err |
| } |
| } |
| |
| if file := optMap[optPrivateKey]; file != "" { |
| opts.PrivateKey, err = ioutil.ReadFile(file) |
| if err != nil { |
| return err |
| } |
| } |
| |
| // signing method is a required field |
| method := optMap[optSignMethod] |
| opts.SignMethod = jwt.GetSigningMethod(method) |
| if opts.SignMethod == nil { |
| return ErrInvalidAuthMethod |
| } |
| |
| return nil |
| } |
| |
| // Key will parse and return the appropriately typed key for the selected signature method |
| func (opts *jwtOptions) Key() (interface{}, error) { |
| switch opts.SignMethod.(type) { |
| case *jwt.SigningMethodRSA, *jwt.SigningMethodRSAPSS: |
| return opts.rsaKey() |
| case *jwt.SigningMethodECDSA: |
| return opts.ecKey() |
| case *jwt.SigningMethodHMAC: |
| return opts.hmacKey() |
| default: |
| return nil, fmt.Errorf("unsupported signing method: %T", opts.SignMethod) |
| } |
| } |
| |
| func (opts *jwtOptions) hmacKey() (interface{}, error) { |
| if len(opts.PrivateKey) == 0 { |
| return nil, ErrMissingKey |
| } |
| return opts.PrivateKey, nil |
| } |
| |
| func (opts *jwtOptions) rsaKey() (interface{}, error) { |
| var ( |
| priv *rsa.PrivateKey |
| pub *rsa.PublicKey |
| err error |
| ) |
| |
| if len(opts.PrivateKey) > 0 { |
| priv, err = jwt.ParseRSAPrivateKeyFromPEM(opts.PrivateKey) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| if len(opts.PublicKey) > 0 { |
| pub, err = jwt.ParseRSAPublicKeyFromPEM(opts.PublicKey) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| if priv == nil { |
| if pub == nil { |
| // Neither key given |
| return nil, ErrMissingKey |
| } |
| // Public key only, can verify tokens |
| return pub, nil |
| } |
| |
| // both keys provided, make sure they match |
| if pub != nil && pub.E != priv.E && pub.N.Cmp(priv.N) != 0 { |
| return nil, ErrKeyMismatch |
| } |
| |
| return priv, nil |
| } |
| |
| func (opts *jwtOptions) ecKey() (interface{}, error) { |
| var ( |
| priv *ecdsa.PrivateKey |
| pub *ecdsa.PublicKey |
| err error |
| ) |
| |
| if len(opts.PrivateKey) > 0 { |
| priv, err = jwt.ParseECPrivateKeyFromPEM(opts.PrivateKey) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| if len(opts.PublicKey) > 0 { |
| pub, err = jwt.ParseECPublicKeyFromPEM(opts.PublicKey) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| if priv == nil { |
| if pub == nil { |
| // Neither key given |
| return nil, ErrMissingKey |
| } |
| // Public key only, can verify tokens |
| return pub, nil |
| } |
| |
| // both keys provided, make sure they match |
| if pub != nil && pub.Curve != priv.Curve && |
| pub.X.Cmp(priv.X) != 0 && pub.Y.Cmp(priv.Y) != 0 { |
| return nil, ErrKeyMismatch |
| } |
| |
| return priv, nil |
| } |