github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/auth/token/token.go (about)

     1  package token
     2  
     3  import (
     4  	"context"
     5  	"crypto/ed25519"
     6  	"crypto/rsa"
     7  	"crypto/x509"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"sort"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/golang-jwt/jwt"
    16  	golog "github.com/ipfs/go-log"
    17  	"github.com/libp2p/go-libp2p-core/crypto"
    18  	"github.com/qri-io/qfs"
    19  	"github.com/qri-io/qri/auth/key"
    20  	"github.com/qri-io/qri/profile"
    21  )
    22  
    23  var (
    24  	// Timestamp is a replacable function for getting the current time,
    25  	// can be overridden for tests
    26  	Timestamp = func() time.Time { return time.Now() }
    27  	// ErrTokenNotFound is returned by stores that cannot find an access token
    28  	// for a given key
    29  	ErrTokenNotFound = errors.New("access token not found")
    30  	// ErrInvalidToken indicates an access token is invalid
    31  	ErrInvalidToken = errors.New("invalid access token")
    32  	// DefaultTokenTTL is the default
    33  	DefaultTokenTTL = time.Hour * 24 * 14
    34  
    35  	log = golog.Logger("token")
    36  )
    37  
    38  func init() {
    39  	golog.SetLogLevel("token", "error")
    40  }
    41  
    42  // Token abstracts a json web token
    43  type Token = jwt.Token
    44  
    45  // Claims is a JWT Claims object
    46  type Claims struct {
    47  	*jwt.StandardClaims
    48  	ClientType ClientType `json:"clientType"`
    49  }
    50  
    51  // Parse will parse, validate and return a token
    52  func Parse(tokenString string, tokens Source) (*Token, error) {
    53  	return jwt.Parse(tokenString, tokens.VerificationKey)
    54  }
    55  
    56  // ParseWithClaims will parse, validate and return a token with claims
    57  func ParseWithClaims(tokenString string, claims *Claims, tokens Source) (*Token, error) {
    58  	return jwt.ParseWithClaims(tokenString, claims, tokens.VerificationKey)
    59  }
    60  
    61  // NewPrivKeyAuthToken creates a JWT token string suitable for making requests
    62  // authenticated as the given private key
    63  func NewPrivKeyAuthToken(pk crypto.PrivKey, profileID string, ttl time.Duration) (string, error) {
    64  	signingMethod, err := jwtSigningMethod(pk)
    65  	if err != nil {
    66  		return "", err
    67  	}
    68  
    69  	t := jwt.New(signingMethod)
    70  
    71  	id, err := key.IDFromPrivKey(pk)
    72  	if err != nil {
    73  		return "", err
    74  	}
    75  
    76  	rawPrivBytes, err := pk.Raw()
    77  	if err != nil {
    78  		return "", err
    79  	}
    80  
    81  	var signKey interface{}
    82  
    83  	switch pk.Type() {
    84  	case crypto.RSA:
    85  		// TODO(b5) - detect if key is encoded as PEM block, here we're assuming it is
    86  		signKey, err = x509.ParsePKCS1PrivateKey(rawPrivBytes)
    87  		if err != nil {
    88  			return "", err
    89  		}
    90  	case crypto.Ed25519:
    91  		signKey = ed25519.PrivateKey(rawPrivBytes)
    92  	default:
    93  		return "", fmt.Errorf("unsupported key type for token creation: %q", pk.Type())
    94  	}
    95  
    96  	var exp int64
    97  	if ttl != time.Duration(0) {
    98  		exp = Timestamp().Add(ttl).In(time.UTC).Unix()
    99  	}
   100  
   101  	// set our claims
   102  	t.Claims = &Claims{
   103  		StandardClaims: &jwt.StandardClaims{
   104  			Issuer:  id,
   105  			Subject: profileID,
   106  			// set the expire time
   107  			// see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4
   108  			ExpiresAt: exp,
   109  		},
   110  		ClientType: UserClient,
   111  	}
   112  
   113  	return t.SignedString(signKey)
   114  }
   115  
   116  // ParseAuthToken will parse, validate and return a token
   117  func ParseAuthToken(ctx context.Context, tokenString string, keystore key.Store) (*Token, error) {
   118  	claims := &Claims{}
   119  	return jwt.ParseWithClaims(tokenString, claims, func(t *Token) (interface{}, error) {
   120  		pid, err := key.DecodeID(claims.Issuer)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		pubKey := keystore.PubKey(ctx, pid)
   125  		if pubKey == nil {
   126  			return nil, fmt.Errorf("cannot verify key. missing public key for id %s", claims.Issuer)
   127  		}
   128  		rawPubBytes, err := pubKey.Raw()
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  
   133  		switch pubKey.Type() {
   134  		case crypto.RSA:
   135  			verifyKeyiface, err := x509.ParsePKIXPublicKey(rawPubBytes)
   136  			if err != nil {
   137  				return nil, err
   138  			}
   139  
   140  			verifyKey, ok := verifyKeyiface.(*rsa.PublicKey)
   141  			if !ok {
   142  				return nil, fmt.Errorf("public key is not an RSA key. got type: %T", verifyKeyiface)
   143  			}
   144  			return verifyKey, nil
   145  		case crypto.Ed25519:
   146  			return ed25519.PublicKey(rawPubBytes), nil
   147  		default:
   148  			return nil, fmt.Errorf("unsupported key type: %q", pubKey.Type())
   149  		}
   150  	})
   151  }
   152  
   153  // Source creates tokens, and provides a verification key for all tokens
   154  // it creates
   155  //
   156  // implementations of Source must conform to the assertion test defined
   157  // in the spec subpackage
   158  type Source interface {
   159  	CreateToken(pro *profile.Profile, ttl time.Duration) (string, error)
   160  	CreateTokenWithClaims(claims *Claims, ttl time.Duration) (string, error)
   161  	// VerifyKey returns the verification key for a given token
   162  	VerificationKey(t *Token) (interface{}, error)
   163  }
   164  
   165  type pkSource struct {
   166  	pk            crypto.PrivKey
   167  	signingMethod jwt.SigningMethod
   168  
   169  	verifyKey interface{} // one of: *rsa.PublicKey, *edsa.PublicKey
   170  	signKey   interface{} // one of: *rsa.PrivateKey,
   171  }
   172  
   173  // assert pkSource implements Source at compile time
   174  var _ Source = (*pkSource)(nil)
   175  
   176  // NewPrivKeySource creates an authentication interface backed by a single
   177  // private key. Intended for a node running as remote, or providing a public API
   178  func NewPrivKeySource(privKey crypto.PrivKey) (Source, error) {
   179  	rawPrivBytes, err := privKey.Raw()
   180  	if err != nil {
   181  		return nil, fmt.Errorf("getting private key bytes: %w", err)
   182  	}
   183  
   184  	var (
   185  		methodStr = ""
   186  		keyType   = privKey.Type()
   187  		signKey   interface{}
   188  		verifyKey interface{}
   189  	)
   190  
   191  	switch keyType {
   192  	case crypto.RSA:
   193  		methodStr = "RS256"
   194  		// TODO(b5) - detect if key is encoded as PEM block, here we're assuming it is
   195  		signKey, err = x509.ParsePKCS1PrivateKey(rawPrivBytes)
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  		rawPubBytes, err := privKey.GetPublic().Raw()
   200  		if err != nil {
   201  			return nil, fmt.Errorf("getting raw public key bytes: %w", err)
   202  		}
   203  		verifyKeyiface, err := x509.ParsePKIXPublicKey(rawPubBytes)
   204  		if err != nil {
   205  			return nil, fmt.Errorf("parsing public key bytes: %w", err)
   206  		}
   207  		var ok bool
   208  		verifyKey, ok = verifyKeyiface.(*rsa.PublicKey)
   209  		if !ok {
   210  			return nil, fmt.Errorf("public key is not an RSA key. got type: %T", verifyKeyiface)
   211  		}
   212  	case crypto.Ed25519:
   213  		methodStr = "EdDSA"
   214  		signKey = ed25519.PrivateKey(rawPrivBytes)
   215  		rawPubBytes, err := privKey.GetPublic().Raw()
   216  		if err != nil {
   217  			return nil, fmt.Errorf("getting raw public key bytes: %w", err)
   218  		}
   219  		verifyKey = ed25519.PublicKey(rawPubBytes)
   220  	default:
   221  		return nil, fmt.Errorf("unsupported key type for token creation: %q", keyType)
   222  	}
   223  
   224  	return &pkSource{
   225  		pk:            privKey,
   226  		signingMethod: jwt.GetSigningMethod(methodStr),
   227  		verifyKey:     verifyKey,
   228  		signKey:       signKey,
   229  	}, nil
   230  }
   231  
   232  // CreateToken returns a new JWT token
   233  func (a *pkSource) CreateToken(pro *profile.Profile, ttl time.Duration) (string, error) {
   234  	// set our claims
   235  	claims := &Claims{
   236  		StandardClaims: &jwt.StandardClaims{
   237  			Subject: pro.ID.Encode(),
   238  			Issuer:  pro.ID.Encode(),
   239  		},
   240  		ClientType: UserClient,
   241  	}
   242  
   243  	return a.CreateTokenWithClaims(claims, ttl)
   244  }
   245  
   246  // CreateToken returns a new JWT token from provided claims
   247  func (a *pkSource) CreateTokenWithClaims(claims *Claims, ttl time.Duration) (string, error) {
   248  	if claims == nil {
   249  		return "", fmt.Errorf("empty token claims")
   250  	}
   251  	// create a signer for rsa 256
   252  	t := jwt.New(a.signingMethod)
   253  
   254  	var exp int64
   255  	if ttl != time.Duration(0) {
   256  		exp = Timestamp().Add(ttl).In(time.UTC).Unix()
   257  	}
   258  	// set the expire time
   259  	// see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4
   260  	claims.StandardClaims.ExpiresAt = exp
   261  	t.Claims = claims
   262  
   263  	// Creat token string
   264  	return t.SignedString(a.signKey)
   265  }
   266  
   267  // VerifyKey returns the verification key
   268  // its packaged as an interface for easy extensibility in the future
   269  func (a *pkSource) VerificationKey(t *Token) (interface{}, error) {
   270  	if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
   271  		return nil, fmt.Errorf("Unexpected signing method: %v", t.Header["alg"])
   272  	}
   273  	return a.verifyKey, nil
   274  }
   275  
   276  // Store is a store intended for clients, who need to persist secret jwts
   277  // given to them by other remotes for API access. It deals in raw,
   278  // string-formatted json web tokens, which are more useful when working with
   279  // APIs, but validates the tokens are well-formed when placed in the store
   280  //
   281  // implementations of Store must conform to the assertion test defined
   282  // in the spec subpackage
   283  type Store interface {
   284  	PutToken(ctx context.Context, key, rawToken string) error
   285  	RawToken(ctx context.Context, key string) (rawToken string, err error)
   286  	DeleteToken(ctx context.Context, key string) (err error)
   287  	ListTokens(ctx context.Context, offset, limit int) (results []RawToken, err error)
   288  }
   289  
   290  // RawToken is a struct that binds a key to a raw token string
   291  type RawToken struct {
   292  	Key string
   293  	Raw string
   294  }
   295  
   296  // RawTokens is a list of tokens that implements sorting by keys
   297  type RawTokens []RawToken
   298  
   299  func (rts RawTokens) Len() int           { return len(rts) }
   300  func (rts RawTokens) Less(a, b int) bool { return rts[a].Key < rts[b].Key }
   301  func (rts RawTokens) Swap(i, j int)      { rts[i], rts[j] = rts[j], rts[i] }
   302  
   303  type qfsStore struct {
   304  	path string
   305  	fs   qfs.Filesystem
   306  
   307  	toksLk sync.Mutex
   308  	toks   map[string]string
   309  }
   310  
   311  var _ Store = (*qfsStore)(nil)
   312  
   313  // NewStore creates a token store with a qfs.Filesystem
   314  func NewStore(filepath string, fs qfs.Filesystem) (Store, error) {
   315  	toks := map[string]string{}
   316  	if f, err := fs.Get(context.Background(), filepath); err == nil {
   317  		rawToks := []RawToken{}
   318  		if err := json.NewDecoder(f).Decode(&rawToks); err != nil {
   319  			return nil, fmt.Errorf("invalid token store file: %w", err)
   320  		}
   321  		for _, t := range rawToks {
   322  			toks[t.Key] = t.Raw
   323  		}
   324  	} else {
   325  		if err.Error() == "path not found" {
   326  			// TODO(arqu): handle Not Found
   327  		} else {
   328  			return nil, fmt.Errorf("error creating token store: %w", err)
   329  		}
   330  	}
   331  
   332  	return &qfsStore{
   333  		path: filepath,
   334  		fs:   fs,
   335  		toks: toks,
   336  	}, nil
   337  }
   338  
   339  func (st *qfsStore) PutToken(ctx context.Context, key string, raw string) error {
   340  	p := &jwt.Parser{
   341  		UseJSONNumber:        true,
   342  		SkipClaimsValidation: false,
   343  	}
   344  	if _, _, err := p.ParseUnverified(raw, &Claims{}); err != nil {
   345  		return fmt.Errorf("%w: %s", ErrInvalidToken, err)
   346  	}
   347  
   348  	st.toksLk.Lock()
   349  	defer st.toksLk.Unlock()
   350  
   351  	st.toks[key] = raw
   352  	return st.save(ctx)
   353  }
   354  
   355  func (st *qfsStore) RawToken(ctx context.Context, key string) (rawToken string, err error) {
   356  	t, ok := st.toks[key]
   357  	if !ok {
   358  		return "", ErrTokenNotFound
   359  	}
   360  	return t, nil
   361  }
   362  
   363  func (st *qfsStore) DeleteToken(ctx context.Context, key string) (err error) {
   364  	st.toksLk.Lock()
   365  	defer st.toksLk.Unlock()
   366  
   367  	if _, ok := st.toks[key]; !ok {
   368  		return ErrTokenNotFound
   369  	}
   370  	delete(st.toks, key)
   371  	return st.save(ctx)
   372  }
   373  
   374  func (st *qfsStore) ListTokens(ctx context.Context, offset, limit int) ([]RawToken, error) {
   375  	results := make([]RawToken, 0, limit+1)
   376  
   377  	toks := st.toRawTokens()
   378  	for i := 0; i < len(toks); i++ {
   379  		if offset > 0 {
   380  			offset--
   381  			continue
   382  		}
   383  		results = append(results, toks[i])
   384  		if limit > 0 && len(results) == limit {
   385  			break
   386  		}
   387  	}
   388  
   389  	return results, nil
   390  }
   391  
   392  func (st *qfsStore) toRawTokens() RawTokens {
   393  	toks := make(RawTokens, len(st.toks))
   394  	i := 0
   395  	for key, t := range st.toks {
   396  		toks[i] = RawToken{
   397  			Key: key,
   398  			Raw: t,
   399  		}
   400  		i++
   401  	}
   402  	sort.Sort(toks)
   403  	return toks
   404  }
   405  
   406  func (st *qfsStore) save(ctx context.Context) error {
   407  	data, err := json.MarshalIndent(st.toRawTokens(), "", "  ")
   408  	if err != nil {
   409  		return err
   410  	}
   411  	f := qfs.NewMemfileBytes(st.path, data)
   412  	path, err := st.fs.Put(ctx, f)
   413  	if err != nil {
   414  		return err
   415  	}
   416  	st.path = path
   417  	return nil
   418  }
   419  
   420  func jwtSigningMethod(pk crypto.PrivKey) (jwt.SigningMethod, error) {
   421  	keyType := pk.Type().String()
   422  	switch keyType {
   423  	case "RSA":
   424  		return jwt.GetSigningMethod("RS256"), nil
   425  	case "Ed25519":
   426  		return jwt.GetSigningMethod("EdDSA"), nil
   427  	default:
   428  		return nil, fmt.Errorf("unsupported key type for token creation: %q", keyType)
   429  	}
   430  }