go mod and vendor/ update

This commit is contained in:
ston1th 2018-09-14 00:14:04 +02:00
commit 14e0611f06
704 changed files with 157354 additions and 39113 deletions

View file

@ -0,0 +1,24 @@
Copyright (C) 2018 Marius Schellenberger
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The names of the authors and/or contributors may not be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ston1th BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,61 @@
# jwt - a simple JSON Web Token library written in Go
## Usage
```
package main
import (
"fmt"
"git.giftfish.de/ston1th/jwt"
"log"
"time"
)
func main() {
// create a new signing instance
JWT, err := jwt.New(time.Hour, jwt.NewMemBlacklist(), nil)
if err != nil {
log.Fatal(err)
}
// create a new token
t := jwt.NewToken(jwt.Claims{
"username": "admin",
}, nil)
// sign the token
err = JWT.Sign(t)
if err != nil {
log.Fatal(err)
}
// print the token string
token := t.String()
fmt.Println(token)
// decode the token string
newToken, err := jwt.DecodeToken(token)
if err != nil {
log.Fatal(err)
}
// verify the decoded token
err = JWT.Verify(newToken)
if err != nil {
log.Fatal(err)
}
// add the token to the blacklist
err = JWT.Invalidate(newToken)
if err != nil {
log.Fatal(err)
}
// try to verify the blacklisted token, it should fail
err = JWT.Verify(newToken)
if err != nil {
log.Fatal(err)
}
}
```

View file

@ -0,0 +1,61 @@
// Copyright (C) 2018 Marius Schellenberger
package jwt
import "sync"
// Blacklist is the blacklisting storage interface
type Blacklist interface {
Add(string, int64)
Remove(string)
Check(string) bool
Map() BlacklistMap
}
// BlacklistMap is the blacklist map structure
type BlacklistMap map[string]int64
// MemBlacklist implements the Blacklist interface
type MemBlacklist struct {
// protects list
sync.RWMutex
list BlacklistMap
}
// NewMemBlacklist implements the Blacklist interface using an in-memory map
func NewMemBlacklist() *MemBlacklist {
return &MemBlacklist{list: make(BlacklistMap)}
}
// Add adds a new token signature with expiration time to the blacklist
func (mb *MemBlacklist) Add(sig string, exp int64) {
mb.Lock()
mb.list[sig] = exp
mb.Unlock()
}
// Remove deletes a token signature from the blacklist
func (mb *MemBlacklist) Remove(sig string) {
mb.Lock()
delete(mb.list, sig)
mb.Unlock()
}
// Check returns true if a token signature is blacklisted and false otherwise
func (mb *MemBlacklist) Check(sig string) (ok bool) {
mb.RLock()
_, ok = mb.list[sig]
mb.RUnlock()
return
}
// Map returns the blacklist in the form of a iterable map structure for cleanup
func (mb *MemBlacklist) Map() (list BlacklistMap) {
list = make(BlacklistMap)
mb.RLock()
for k, v := range mb.list {
list[k] = v
}
mb.RUnlock()
return
}

View file

@ -0,0 +1,87 @@
// Copyright (C) 2018 Marius Schellenberger
package jwt
// Claims is the claim type of the token
// The Claims map is not goroutine safe
type Claims map[string]interface{}
// Get returns a value from the claims map
func (c Claims) Get(key string) (v interface{}, ok bool) {
v, ok = c[key]
return
}
// GetString returns a string from the claims map
func (c Claims) GetString(key string) (s string) {
if v, ok := c.Get(key); ok && v != nil {
s, _ = v.(string)
}
return
}
// GetBool returns a bool from the claims map
func (c Claims) GetBool(key string) (b bool) {
if v, ok := c.Get(key); ok && v != nil {
b, _ = v.(bool)
}
return
}
// GetInt returns an int from the claims map
func (c Claims) GetInt(key string) (i int) {
if f, ok := c.getFloat64(key); ok {
return int(f)
}
if v, ok := c.Get(key); ok && v != nil {
i, _ = v.(int)
}
return
}
// GetInt64 returns an int64 from the claims map
func (c Claims) GetInt64(key string) (i int64) {
if f, ok := c.getFloat64(key); ok {
return int64(f)
}
if v, ok := c.Get(key); ok && v != nil {
i, _ = v.(int64)
}
return
}
// getFloat64 returns a float64 and ok from the claims map
func (c Claims) getFloat64(key string) (f float64, fok bool) {
if v, ok := c.Get(key); ok && v != nil {
f, fok = v.(float64)
}
return
}
// GetFloat64 returns a float64 from the claims map
func (c Claims) GetFloat64(key string) (f float64) {
f, _ = c.getFloat64(key)
return
}
// Set sets the value of key in the claims map, if not nil
func (c Claims) Set(key string, v interface{}) {
if c == nil {
return
}
c[key] = v
}
// Delete removes the key from the claims map
func (c Claims) Delete(key string) {
delete(c, key)
}
// Copy returns a new Claims map
func (c Claims) Copy() (n Claims) {
n = make(Claims)
for k, v := range c {
n[k] = v
}
return
}

View file

@ -0,0 +1,88 @@
// Copyright (C) 2018 Marius Schellenberger
package jwt
import (
"crypto/sha256"
"crypto/sha512"
"hash"
)
const (
HS256Name = "HS256"
HS384Name = "HS384"
HS512Name = "HS512"
)
// ParseHash returns the Hash type equal to the input string
func ParseHash(alg string) Hash {
switch alg {
case HS256Name:
return NewHS256()
case HS384Name:
return NewHS384()
case HS512Name:
return NewHS512()
}
return nil
}
// Hash is the hashsum interface for signing the jwt
type Hash interface {
Hash() hash.Hash
Alg() string
}
// HS256 implements the Hash interface with SHA256
type HS256 struct{}
// NewHS256 returns a new HS256 instance
func NewHS256() Hash {
return HS256{}
}
// Alg returns the algorithm name "HS256"
func (HS256) Alg() string {
return HS256Name
}
// Hash returns a new hash.Hash instance of the SHA256 algorithm
func (HS256) Hash() hash.Hash {
return sha256.New()
}
// HS384 implements the Hash interface with SHA384
type HS384 struct{}
// NewHS384 returns a new HS384 instance
func NewHS384() Hash {
return HS384{}
}
// Alg returns the algorithm name "HS384"
func (HS384) Alg() string {
return HS384Name
}
// Hash returns a new hash.Hash instance of the SHA384 algorithm
func (HS384) Hash() hash.Hash {
return sha512.New384()
}
// HS512 implements the Hash interface with SHA512
type HS512 struct{}
// NewHS512 returns a new HS512 instance
func NewHS512() Hash {
return HS512{}
}
// Alg returns the algorithm name "HS512"
func (HS512) Alg() string {
return HS512Name
}
// Hash returns a new hash.Hash instance of the SHA512 algorithm
func (HS512) Hash() hash.Hash {
return sha512.New()
}

View file

@ -0,0 +1,264 @@
// Copyright (C) 2018 Marius Schellenberger
// Package jwt provides a easy to use JSON Web Token and blacklisting library
package jwt
import (
"crypto/hmac"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"io"
"sync"
"time"
)
const (
// HTTPHeader is the default HTTP Authorization header name
HTTPHeader = "Authorization"
// DefaultExpiry is the default token expiration time
DefaultExpiry = time.Hour * 12
// KeySize is the secret key size
KeySize = 64
// Typ is the JWT type
Typ = "JWT"
// TypClaim is the typ claim name
TypClaim = "typ"
// AlgClaim is the alg claim name
AlgClaim = "alg"
// ExpClaim is the exp claim name
ExpClaim = "exp"
// NbfClaim is the nbf claim name
NbfClaim = "nbf"
// TokenSeparator is the tokens separator char
TokenSeparator = "."
)
// DefaultSecretReader is the default secret key generator
var DefaultSecretReader = rand.Reader
const jwtErr = "jwt: "
var (
ErrNoJWT = errors.New(jwtErr + "not a json web token")
ErrEmptyToken = errors.New(jwtErr + "token is empty")
ErrUnsupportedAlg = errors.New(jwtErr + "unsupported algorithm")
ErrInvalid = errors.New(jwtErr + "token validation failed")
ErrBlacklisted = errors.New(jwtErr + "token blacklisted")
ErrBlacklistNotEnabled = errors.New(jwtErr + "blacklisting is not enabled")
ErrNbf = errors.New(jwtErr + "token not valid yet")
ErrExp = errors.New(jwtErr + "token expired")
ErrMissingNbf = errors.New(jwtErr + "missing nbf claim")
ErrMissingExp = errors.New(jwtErr + "missing exp claim")
ErrMissingTokenParts = errors.New(jwtErr + "missing token parts")
ErrEmptySignature = errors.New(jwtErr + "token signature is empty")
ErrTokenIsNil = errors.New(jwtErr + "token is nil")
ErrInvalidKeySize = errors.New(jwtErr + "invalid secret key size")
)
// JWT represents the JSON Web Token signing and blacklisting infrastructure
type JWT struct {
key []byte
expiry time.Duration
blacklist Blacklist
stopOnce sync.Once
done chan struct{}
}
// New returns a new JWT object with the given expiry timeout.
// If the timeout is less or equal to zero the default expiry (12 hours) is used.
// The secret key size needs to be at least 64 bytes.
// If secret is nil the DefaultSecretReader is used.
// If blacklisting is enabled, the JWT object leaks a goroutine to garbage-collect expired blacklisted tokens.
// Call the Stop() method to exit the goroutine.
func New(expiry time.Duration, blacklist Blacklist, secret io.Reader) (*JWT, error) {
if expiry <= 0 {
expiry = DefaultExpiry
}
if secret == nil {
secret = DefaultSecretReader
}
secret = io.LimitReader(secret, KeySize)
key := make([]byte, KeySize)
i, err := secret.Read(key)
if err != nil {
return nil, errors.New(jwtErr + "secret reader error: " + err.Error())
}
if i < KeySize {
return nil, ErrInvalidKeySize
}
jwt := &JWT{
key: key,
expiry: expiry,
blacklist: blacklist,
}
if blacklist != nil {
jwt.done = make(chan struct{})
go jwt.clean()
}
return jwt, nil
}
// Expiry returns the configured expiry
func (jwt *JWT) Expiry() time.Duration {
return jwt.expiry
}
// sum calculates the HMAC hash sum of the token
func (jwt *JWT) sum(token string, h Hash) []byte {
mac := hmac.New(h.Hash, jwt.key)
mac.Write([]byte(token))
return mac.Sum(nil)
}
// Invalidate checks if a token is already blacklisted
// If the token is not blacklisted, it will get blacklisted
func (jwt *JWT) Invalidate(t *Token) error {
if jwt.blacklist == nil {
return ErrBlacklistNotEnabled
}
if t == nil {
return ErrTokenIsNil
}
if err := jwt.blacklisted(t.Sig()); err != nil {
return err
}
if t.Header.GetString(TypClaim) != Typ {
return ErrNoJWT
}
h := ParseHash(t.Header.GetString(AlgClaim))
if h == nil {
return ErrUnsupportedAlg
}
exp := t.Claims.GetInt64(ExpClaim)
switch {
case exp == 0:
return ErrMissingExp
case Now() > exp:
return ErrExp
}
if !hmac.Equal(jwt.sum(t.Data(), h), t.RawSig()) {
return ErrInvalid
}
jwt.blacklist.Add(t.Sig(), exp)
return nil
}
// blacklisted checks if a token is blacklisted
func (jwt *JWT) blacklisted(sig string) error {
if sig == "" {
return ErrEmptySignature
}
if jwt.blacklist.Check(sig) {
return ErrBlacklisted
}
return nil
}
// clean looks for expired blacklisted tokens and removes them
func (jwt *JWT) clean() {
for {
t := time.NewTimer(time.Hour)
select {
case <-t.C:
case <-jwt.done:
t.Stop()
return
}
now := Now()
for k, v := range jwt.blacklist.Map() {
if now > v {
jwt.blacklist.Remove(k)
}
}
}
}
// Sign will sign the provided token using the secret key
// If the 'exp' and 'nbf' claims do not exist, they will be written to the default values in UNIX format:
// 'exp': Now + expiry specified at New() or DefaultExpiry (UTC)
// 'nbf': Now (UTC)
func (jwt *JWT) Sign(t *Token) (err error) {
if t == nil {
return ErrTokenIsNil
}
if t.Header.GetString(TypClaim) != Typ {
return ErrNoJWT
}
h := ParseHash(t.Header.GetString(AlgClaim))
if h == nil {
return ErrUnsupportedAlg
}
now := time.Now()
if _, ok := t.Claims.Get(ExpClaim); !ok {
t.Claims.Set(ExpClaim, newExp(now, jwt.expiry))
}
if _, ok := t.Claims.Get(NbfClaim); !ok {
t.Claims.Set(NbfClaim, NewNbf(now))
}
head, err := json.Marshal(t.Header)
if err != nil {
return
}
claims, err := json.Marshal(t.Claims)
if err != nil {
return
}
t.data = base64.URLEncoding.EncodeToString(head) + TokenSeparator + base64.URLEncoding.EncodeToString(claims)
t.rawSignature = jwt.sum(t.data, h)
t.signature = base64.URLEncoding.EncodeToString(t.rawSignature)
t.raw = t.data + TokenSeparator + t.signature
return
}
// Verify will verify the provided token using the secret key
func (jwt *JWT) Verify(t *Token) error {
if t == nil {
return ErrTokenIsNil
}
if jwt.blacklist != nil {
if err := jwt.blacklisted(t.Sig()); err != nil {
return err
}
}
now := Now()
if t.Header.GetString(TypClaim) != Typ {
return ErrNoJWT
}
h := ParseHash(t.Header.GetString(AlgClaim))
if h == nil {
return ErrUnsupportedAlg
}
exp := t.Claims.GetInt64(ExpClaim)
switch {
case exp == 0:
return ErrMissingExp
case now > exp:
return ErrExp
}
nbf := t.Claims.GetInt64(NbfClaim)
switch {
case nbf == 0:
return ErrMissingNbf
case now < nbf:
return ErrNbf
}
if !hmac.Equal(jwt.sum(t.Data(), h), t.RawSig()) {
return ErrInvalid
}
return nil
}
// Stop will end the cleaner goroutine execution
func (jwt *JWT) Stop() error {
if jwt.blacklist == nil {
return ErrBlacklistNotEnabled
}
jwt.stopOnce.Do(func() {
close(jwt.done)
})
return nil
}

View file

@ -0,0 +1,22 @@
package jwt
import "time"
// Now returns the current time in UTC Unix format
func Now() int64 {
return NewNbf(time.Now())
}
// NewNbf returns a new 'not before' date
func NewNbf(t time.Time) int64 {
return t.UTC().Unix()
}
// NewExp returns a new expiration date
func NewExp(d time.Duration) int64 {
return newExp(time.Now(), d)
}
func newExp(t time.Time, d time.Duration) int64 {
return t.UTC().Add(d).Unix()
}

View file

@ -0,0 +1,99 @@
// Copyright (C) 2018 Marius Schellenberger
package jwt
import (
"encoding/base64"
"encoding/json"
"strings"
)
// Token represents a JWT token
type Token struct {
raw string
data string
signature string
rawSignature []byte
Header Claims
Claims Claims
}
// String returns the tokens encoded string
func (t *Token) String() string {
return t.raw
}
// Sig returns the tokens URLEncoded signature
func (t *Token) Sig() string {
return t.signature
}
// RawSig returns the tokens raw signature
func (t *Token) RawSig() []byte {
return t.rawSignature
}
// Data returns the first two URLEncoded token fields
func (t *Token) Data() string {
return t.data
}
// NewToken returns a new *Token using the provided hash algorithm and claims
// If claims is nil, an empty map is used
// If hash is nil, HS256 is used
func NewToken(claims Claims, hash Hash) *Token {
if claims == nil {
claims = make(Claims)
}
if hash == nil {
hash = NewHS256()
}
return &Token{
Header: Claims{
AlgClaim: hash.Alg(),
TypClaim: Typ,
},
Claims: claims,
}
}
// DecodeToken decodes a raw string token into a *Token object
func DecodeToken(token string) (t *Token, err error) {
if token == "" {
return nil, ErrEmptyToken
}
parts := strings.Split(token, TokenSeparator)
if len(parts) < 3 {
return nil, ErrMissingTokenParts
}
if len(parts) > 3 {
return nil, ErrNoJWT
}
for _, v := range parts {
if v == "" {
return nil, ErrNoJWT
}
}
t = new(Token)
header, err := base64.URLEncoding.DecodeString(parts[0])
if err != nil {
return
}
err = json.Unmarshal(header, &t.Header)
if err != nil {
return
}
claims, err := base64.URLEncoding.DecodeString(parts[1])
if err != nil {
return
}
err = json.Unmarshal(claims, &t.Claims)
if err != nil {
return
}
t.rawSignature, err = base64.URLEncoding.DecodeString(parts[2])
t.signature = parts[2]
t.data = parts[0] + TokenSeparator + parts[1]
t.raw = token
return
}