blob: f40b92de6b397c3fd798333cc1f68a2419d52d66 [file] [log] [blame]
// 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
}