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 }