forked from mirror/jwt
Compare commits
1 Commits
main
...
using-erro
Author | SHA1 | Date |
---|---|---|
Christian Banse | 957802ced4 |
|
@ -12,7 +12,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- uses: reviewdog/action-staticcheck@v1
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
@ -25,26 +25,16 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go: [1.17, 1.18, 1.19]
|
||||
go: [1.15, 1.16, 1.17]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: "${{ matrix.go }}"
|
||||
check-latest: true
|
||||
cache: true
|
||||
- name: Check Go code formatting
|
||||
run: |
|
||||
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
|
||||
gofmt -s -l .
|
||||
echo "Please format Go code by running: go fmt ./..."
|
||||
exit 1
|
||||
fi
|
||||
- name: Build
|
||||
run: |
|
||||
go install github.com/mfridman/tparse@latest
|
||||
go vet ./...
|
||||
go test -v -race -count=1 -json -coverpkg=$(go list ./...) ./... | tparse -follow -notests
|
||||
go test -v ./...
|
||||
go build ./...
|
||||
|
|
|
@ -2,18 +2,18 @@
|
|||
|
||||
Starting from [v4.0.0](https://github.com/golang-jwt/jwt/releases/tag/v4.0.0), the import path will be:
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
|
||||
The `/v4` version will be backwards compatible with existing `v3.x.y` tags in this repo, as well as
|
||||
`github.com/dgrijalva/jwt-go`. For most users this should be a drop-in replacement, if you're having
|
||||
troubles migrating, please open an issue.
|
||||
|
||||
You can replace all occurrences of `github.com/dgrijalva/jwt-go` or `github.com/golang-jwt/jwt` with `git.internal/re/jwt/v4`, either manually or by using tools such as `sed` or `gofmt`.
|
||||
You can replace all occurrences of `github.com/dgrijalva/jwt-go` or `github.com/golang-jwt/jwt` with `github.com/golang-jwt/jwt/v4`, either manually or by using tools such as `sed` or `gofmt`.
|
||||
|
||||
And then you'd typically run:
|
||||
|
||||
```
|
||||
go get git.internal/re/jwt/v4
|
||||
go get github.com/golang-jwt/jwt/v4
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
|
|
50
README.md
50
README.md
|
@ -1,7 +1,7 @@
|
|||
# jwt-go
|
||||
|
||||
[![build](https://github.com/golang-jwt/jwt/actions/workflows/build.yml/badge.svg)](https://github.com/golang-jwt/jwt/actions/workflows/build.yml)
|
||||
[![Go Reference](https://pkg.go.dev/badge/git.internal/re/jwt/v4.svg)](https://pkg.go.dev/git.internal/re/jwt/v4)
|
||||
[![Go Reference](https://pkg.go.dev/badge/github.com/golang-jwt/jwt/v4.svg)](https://pkg.go.dev/github.com/golang-jwt/jwt/v4)
|
||||
|
||||
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](https://datatracker.ietf.org/doc/html/rfc7519).
|
||||
|
||||
|
@ -36,41 +36,19 @@ The part in the middle is the interesting bit. It's called the Claims and conta
|
|||
|
||||
This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own.
|
||||
|
||||
## Installation Guidelines
|
||||
|
||||
1. To install the jwt package, you first need to have [Go](https://go.dev/doc/install) installed, then you can use the command below to add `jwt-go` as a dependency in your Go program.
|
||||
|
||||
```sh
|
||||
go get -u git.internal/re/jwt/v4
|
||||
```
|
||||
|
||||
2. Import it in your code:
|
||||
|
||||
```go
|
||||
import "git.internal/re/jwt/v4"
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See [the project documentation](https://pkg.go.dev/git.internal/re/jwt/v4) for examples of usage:
|
||||
See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt) for examples of usage:
|
||||
|
||||
* [Simple example of parsing and validating a token](https://pkg.go.dev/git.internal/re/jwt/v4#example-Parse-Hmac)
|
||||
* [Simple example of building and signing a token](https://pkg.go.dev/git.internal/re/jwt/v4#example-New-Hmac)
|
||||
* [Directory of Examples](https://pkg.go.dev/git.internal/re/jwt/v4#pkg-examples)
|
||||
* [Simple example of parsing and validating a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-Parse-Hmac)
|
||||
* [Simple example of building and signing a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-New-Hmac)
|
||||
* [Directory of Examples](https://pkg.go.dev/github.com/golang-jwt/jwt#pkg-examples)
|
||||
|
||||
## Extensions
|
||||
|
||||
This library publishes all the necessary components for adding your own signing methods or key functions. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod` or provide a `jwt.Keyfunc`.
|
||||
This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`.
|
||||
|
||||
A common use case would be integrating with different 3rd party signature providers, like key management services from various cloud providers or Hardware Security Modules (HSMs) or to implement additional standards.
|
||||
|
||||
| Extension | Purpose | Repo |
|
||||
| --------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
|
||||
| GCP | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS) | https://github.com/someone1/gcp-jwt-go |
|
||||
| AWS | Integrates with AWS Key Management Service, KMS | https://github.com/matelang/jwt-go-aws-kms |
|
||||
| JWKS | Provides support for JWKS ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) as a `jwt.Keyfunc` | https://github.com/MicahParks/keyfunc |
|
||||
|
||||
*Disclaimer*: Unless otherwise specified, these integrations are maintained by third parties and should not be considered as a primary offer by any of the mentioned cloud providers
|
||||
Here's an example of an extension that integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS): https://github.com/someone1/gcp-jwt-go
|
||||
|
||||
## Compliance
|
||||
|
||||
|
@ -96,7 +74,7 @@ A token is simply a JSON object that is signed by its author. this tells you exa
|
|||
* The author of the token was in the possession of the signing secret
|
||||
* The data has not been modified since it was signed
|
||||
|
||||
It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. The companion project https://github.com/golang-jwt/jwe aims at a (very) experimental implementation of the JWE standard.
|
||||
It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library.
|
||||
|
||||
### Choosing a Signing Method
|
||||
|
||||
|
@ -110,10 +88,10 @@ Asymmetric signing methods, such as RSA, use different keys for signing and veri
|
|||
|
||||
Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones:
|
||||
|
||||
* The [HMAC signing method](https://pkg.go.dev/git.internal/re/jwt/v4#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation
|
||||
* The [RSA signing method](https://pkg.go.dev/git.internal/re/jwt/v4#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation
|
||||
* The [ECDSA signing method](https://pkg.go.dev/git.internal/re/jwt/v4#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation
|
||||
* The [EdDSA signing method](https://pkg.go.dev/git.internal/re/jwt/v4#SigningMethodEd25519) (`Ed25519`) expect `ed25519.PrivateKey` for signing and `ed25519.PublicKey` for validation
|
||||
* The [HMAC signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation
|
||||
* The [RSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation
|
||||
* The [ECDSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation
|
||||
* The [EdDSA signing method](https://pkg.go.dev/github.com/golang-jwt/jwt#SigningMethodEd25519) (`Ed25519`) expect `ed25519.PrivateKey` for signing and `ed25519.PublicKey` for validation
|
||||
|
||||
### JWT and OAuth
|
||||
|
||||
|
@ -131,8 +109,6 @@ This library uses descriptive error messages whenever possible. If you are not g
|
|||
|
||||
## More
|
||||
|
||||
Documentation can be found [on pkg.go.dev](https://pkg.go.dev/git.internal/re/jwt/v4).
|
||||
Documentation can be found [on pkg.go.dev](https://pkg.go.dev/github.com/golang-jwt/jwt).
|
||||
|
||||
The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation.
|
||||
|
||||
[golang-jwt](https://github.com/orgs/golang-jwt) incorporates a modified version of the JWT logo, which is distributed under the terms of the [MIT License](https://github.com/jsonwebtoken/jsonwebtoken.github.io/blob/master/LICENSE.txt).
|
||||
|
|
19
SECURITY.md
19
SECURITY.md
|
@ -1,19 +0,0 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
As of February 2022 (and until this document is updated), the latest version `v4` is supported.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you think you found a vulnerability, and even if you are not sure, please report it to jwt-go-security@googlegroups.com or one of the other [golang-jwt maintainers](https://github.com/orgs/golang-jwt/people). Please try be explicit, describe steps to reproduce the security issue with code example(s).
|
||||
|
||||
You will receive a response within a timely manner. If the issue is confirmed, we will do our best to release a patch as soon as possible given the complexity of the problem.
|
||||
|
||||
## Public Discussions
|
||||
|
||||
Please avoid publicly discussing a potential security vulnerability.
|
||||
|
||||
Let's take this offline and find a solution first, this limits the potential impact as much as possible.
|
||||
|
||||
We appreciate your help!
|
24
claims.go
24
claims.go
|
@ -56,17 +56,17 @@ func (c RegisteredClaims) Valid() error {
|
|||
// default value in Go, let's not fail the verification for them.
|
||||
if !c.VerifyExpiresAt(now, false) {
|
||||
delta := now.Sub(c.ExpiresAt.Time)
|
||||
vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
|
||||
vErr.Inner = fmt.Errorf("token is expired by %v", delta)
|
||||
vErr.Errors |= ValidationErrorExpired
|
||||
}
|
||||
|
||||
if !c.VerifyIssuedAt(now, false) {
|
||||
vErr.Inner = ErrTokenUsedBeforeIssued
|
||||
vErr.Inner = fmt.Errorf("token used before issued")
|
||||
vErr.Errors |= ValidationErrorIssuedAt
|
||||
}
|
||||
|
||||
if !c.VerifyNotBefore(now, false) {
|
||||
vErr.Inner = ErrTokenNotValidYet
|
||||
vErr.Inner = fmt.Errorf("token is not valid yet")
|
||||
vErr.Errors |= ValidationErrorNotValidYet
|
||||
}
|
||||
|
||||
|
@ -113,12 +113,6 @@ func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool) bool {
|
|||
return verifyNbf(&c.NotBefore.Time, cmp, req)
|
||||
}
|
||||
|
||||
// VerifyIssuer compares the iss claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *RegisteredClaims) VerifyIssuer(cmp string, req bool) bool {
|
||||
return verifyIss(c.Issuer, cmp, req)
|
||||
}
|
||||
|
||||
// StandardClaims are a structured version of the JWT Claims Set, as referenced at
|
||||
// https://datatracker.ietf.org/doc/html/rfc7519#section-4. They do not follow the
|
||||
// specification exactly, since they were based on an earlier draft of the
|
||||
|
@ -149,17 +143,17 @@ func (c StandardClaims) Valid() error {
|
|||
// default value in Go, let's not fail the verification for them.
|
||||
if !c.VerifyExpiresAt(now, false) {
|
||||
delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
|
||||
vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
|
||||
vErr.Inner = fmt.Errorf("token is expired by %v", delta)
|
||||
vErr.Errors |= ValidationErrorExpired
|
||||
}
|
||||
|
||||
if !c.VerifyIssuedAt(now, false) {
|
||||
vErr.Inner = ErrTokenUsedBeforeIssued
|
||||
vErr.Inner = fmt.Errorf("token used before issued")
|
||||
vErr.Errors |= ValidationErrorIssuedAt
|
||||
}
|
||||
|
||||
if !c.VerifyNotBefore(now, false) {
|
||||
vErr.Inner = ErrTokenNotValidYet
|
||||
vErr.Inner = fmt.Errorf("token is not valid yet")
|
||||
vErr.Errors |= ValidationErrorNotValidYet
|
||||
}
|
||||
|
||||
|
@ -265,5 +259,9 @@ func verifyIss(iss string, cmp string, required bool) bool {
|
|||
if iss == "" {
|
||||
return !required
|
||||
}
|
||||
return subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0
|
||||
if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,4 +16,4 @@ To simply display a token, use:
|
|||
|
||||
You can install this tool with the following command:
|
||||
|
||||
go install git.internal/re/jwt/v4/cmd/jwt
|
||||
go install github.com/golang-jwt/jwt/v4/cmd/jwt
|
|
@ -3,7 +3,6 @@
|
|||
//
|
||||
// Example usage:
|
||||
// The following will create and sign a token, then verify it and output the original claims.
|
||||
//
|
||||
// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify -
|
||||
package main
|
||||
|
||||
|
@ -12,12 +11,13 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -91,7 +91,7 @@ func loadData(p string) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
return io.ReadAll(rdr)
|
||||
return ioutil.ReadAll(rdr)
|
||||
}
|
||||
|
||||
// Print a json object in accordance with the prophecy (or the command line options)
|
||||
|
|
|
@ -2,11 +2,11 @@ package jwt_test
|
|||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
var ecdsaTestData = []struct {
|
||||
|
@ -55,7 +55,7 @@ func TestECDSAVerify(t *testing.T) {
|
|||
for _, data := range ecdsaTestData {
|
||||
var err error
|
||||
|
||||
key, _ := os.ReadFile(data.keys["public"])
|
||||
key, _ := ioutil.ReadFile(data.keys["public"])
|
||||
|
||||
var ecdsaKey *ecdsa.PublicKey
|
||||
if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil {
|
||||
|
@ -78,7 +78,7 @@ func TestECDSAVerify(t *testing.T) {
|
|||
func TestECDSASign(t *testing.T) {
|
||||
for _, data := range ecdsaTestData {
|
||||
var err error
|
||||
key, _ := os.ReadFile(data.keys["private"])
|
||||
key, _ := ioutil.ReadFile(data.keys["private"])
|
||||
|
||||
var ecdsaKey *ecdsa.PrivateKey
|
||||
if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil {
|
||||
|
@ -90,6 +90,7 @@ func TestECDSASign(t *testing.T) {
|
|||
toSign := strings.Join(parts[0:2], ".")
|
||||
method := jwt.GetSigningMethod(data.alg)
|
||||
sig, err := method.Sign(toSign, ecdsaKey)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("[%v] Error signing token: %v", data.name, err)
|
||||
}
|
||||
|
@ -107,7 +108,7 @@ func TestECDSASign(t *testing.T) {
|
|||
|
||||
func BenchmarkECDSAParsing(b *testing.B) {
|
||||
for _, data := range ecdsaTestData {
|
||||
key, _ := os.ReadFile(data.keys["private"])
|
||||
key, _ := ioutil.ReadFile(data.keys["private"])
|
||||
|
||||
b.Run(data.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
@ -125,7 +126,7 @@ func BenchmarkECDSAParsing(b *testing.B) {
|
|||
|
||||
func BenchmarkECDSASigning(b *testing.B) {
|
||||
for _, data := range ecdsaTestData {
|
||||
key, _ := os.ReadFile(data.keys["private"])
|
||||
key, _ := ioutil.ReadFile(data.keys["private"])
|
||||
|
||||
ecdsaKey, err := jwt.ParseECPrivateKeyFromPEM(key)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package jwt_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
var ed25519TestData = []struct {
|
||||
|
@ -38,7 +38,7 @@ func TestEd25519Verify(t *testing.T) {
|
|||
for _, data := range ed25519TestData {
|
||||
var err error
|
||||
|
||||
key, _ := os.ReadFile(data.keys["public"])
|
||||
key, _ := ioutil.ReadFile(data.keys["public"])
|
||||
|
||||
ed25519Key, err := jwt.ParseEdPublicKeyFromPEM(key)
|
||||
if err != nil {
|
||||
|
@ -62,7 +62,7 @@ func TestEd25519Verify(t *testing.T) {
|
|||
func TestEd25519Sign(t *testing.T) {
|
||||
for _, data := range ed25519TestData {
|
||||
var err error
|
||||
key, _ := os.ReadFile(data.keys["private"])
|
||||
key, _ := ioutil.ReadFile(data.keys["private"])
|
||||
|
||||
ed25519Key, err := jwt.ParseEdPrivateKeyFromPEM(key)
|
||||
if err != nil {
|
||||
|
|
48
errors.go
48
errors.go
|
@ -9,18 +9,6 @@ var (
|
|||
ErrInvalidKey = errors.New("key is invalid")
|
||||
ErrInvalidKeyType = errors.New("key is of invalid type")
|
||||
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
|
||||
|
||||
ErrTokenMalformed = errors.New("token is malformed")
|
||||
ErrTokenUnverifiable = errors.New("token is unverifiable")
|
||||
ErrTokenSignatureInvalid = errors.New("token signature is invalid")
|
||||
|
||||
ErrTokenInvalidAudience = errors.New("token has invalid audience")
|
||||
ErrTokenExpired = errors.New("token is expired")
|
||||
ErrTokenUsedBeforeIssued = errors.New("token used before issued")
|
||||
ErrTokenInvalidIssuer = errors.New("token has invalid issuer")
|
||||
ErrTokenNotValidYet = errors.New("token is not valid yet")
|
||||
ErrTokenInvalidId = errors.New("token has invalid id")
|
||||
ErrTokenInvalidClaims = errors.New("token has invalid claims")
|
||||
)
|
||||
|
||||
// The errors that might occur when parsing and validating a token
|
||||
|
@ -74,39 +62,3 @@ func (e *ValidationError) Unwrap() error {
|
|||
func (e *ValidationError) valid() bool {
|
||||
return e.Errors == 0
|
||||
}
|
||||
|
||||
// Is checks if this ValidationError is of the supplied error. We are first checking for the exact error message
|
||||
// by comparing the inner error message. If that fails, we compare using the error flags. This way we can use
|
||||
// custom error messages (mainly for backwards compatability) and still leverage errors.Is using the global error variables.
|
||||
func (e *ValidationError) Is(err error) bool {
|
||||
// Check, if our inner error is a direct match
|
||||
if errors.Is(errors.Unwrap(e), err) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Otherwise, we need to match using our error flags
|
||||
switch err {
|
||||
case ErrTokenMalformed:
|
||||
return e.Errors&ValidationErrorMalformed != 0
|
||||
case ErrTokenUnverifiable:
|
||||
return e.Errors&ValidationErrorUnverifiable != 0
|
||||
case ErrTokenSignatureInvalid:
|
||||
return e.Errors&ValidationErrorSignatureInvalid != 0
|
||||
case ErrTokenInvalidAudience:
|
||||
return e.Errors&ValidationErrorAudience != 0
|
||||
case ErrTokenExpired:
|
||||
return e.Errors&ValidationErrorExpired != 0
|
||||
case ErrTokenUsedBeforeIssued:
|
||||
return e.Errors&ValidationErrorIssuedAt != 0
|
||||
case ErrTokenInvalidIssuer:
|
||||
return e.Errors&ValidationErrorIssuer != 0
|
||||
case ErrTokenNotValidYet:
|
||||
return e.Errors&ValidationErrorNotValidYet != 0
|
||||
case ErrTokenInvalidId:
|
||||
return e.Errors&ValidationErrorId != 0
|
||||
case ErrTokenInvalidClaims:
|
||||
return e.Errors&ValidationErrorClaimsInvalid != 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
// Example (atypical) using the RegisteredClaims type by itself to parse a token.
|
||||
|
@ -95,23 +95,33 @@ func ExampleParseWithClaims_customClaimsType() {
|
|||
|
||||
// An example of parsing the error types using bitfield checks
|
||||
func ExampleParse_errorChecking() {
|
||||
var (
|
||||
token *jwt.Token
|
||||
ve *jwt.ValidationError
|
||||
err error
|
||||
)
|
||||
|
||||
// Token from another example. This token is expired
|
||||
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
|
||||
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
token, err = jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte("AllYourBase"), nil
|
||||
})
|
||||
|
||||
if token.Valid {
|
||||
fmt.Println("You look nice today")
|
||||
} else if errors.Is(err, jwt.ErrTokenMalformed) {
|
||||
} else if errors.As(err, &ve) {
|
||||
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
|
||||
fmt.Println("That's not even a token")
|
||||
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
|
||||
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
|
||||
// Token is either expired or not active yet
|
||||
fmt.Println("Timing is everything")
|
||||
} else {
|
||||
fmt.Println("Couldn't handle this token:", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Couldn't handle this token:", err)
|
||||
}
|
||||
|
||||
// Output: Timing is everything
|
||||
}
|
||||
|
|
8
go.mod
8
go.mod
|
@ -1,7 +1,3 @@
|
|||
module git.internal/re/jwt/v4
|
||||
module github.com/golang-jwt/jwt/v4
|
||||
|
||||
go 1.16
|
||||
|
||||
retract (
|
||||
v4.4.0 // Contains a backwards incompatible change to the Claims interface.
|
||||
)
|
||||
go 1.15
|
||||
|
|
|
@ -2,10 +2,10 @@ package jwt_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
// For HMAC signing method, the key can be any []byte. It is recommended to generate
|
||||
|
@ -15,7 +15,7 @@ var hmacSampleSecret []byte
|
|||
|
||||
func init() {
|
||||
// Load sample key data
|
||||
if keyData, e := os.ReadFile("test/hmacTestKey"); e == nil {
|
||||
if keyData, e := ioutil.ReadFile("test/hmacTestKey"); e == nil {
|
||||
hmacSampleSecret = keyData
|
||||
} else {
|
||||
panic(e)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package jwt_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
var hmacTestData = []struct {
|
||||
|
@ -46,7 +46,7 @@ var hmacTestData = []struct {
|
|||
}
|
||||
|
||||
// Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1
|
||||
var hmacTestKey, _ = os.ReadFile("test/hmacTestKey")
|
||||
var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey")
|
||||
|
||||
func TestHMACVerify(t *testing.T) {
|
||||
for _, data := range hmacTestData {
|
||||
|
|
|
@ -8,16 +8,16 @@ import (
|
|||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"git.internal/re/jwt/v4/request"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4/request"
|
||||
)
|
||||
|
||||
// location of the files used for signing and verification
|
||||
|
@ -34,13 +34,13 @@ var (
|
|||
|
||||
// read the key files before starting http handlers
|
||||
func init() {
|
||||
signBytes, err := os.ReadFile(privKeyPath)
|
||||
signBytes, err := ioutil.ReadFile(privKeyPath)
|
||||
fatal(err)
|
||||
|
||||
signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
|
||||
fatal(err)
|
||||
|
||||
verifyBytes, err := os.ReadFile(pubKeyPath)
|
||||
verifyBytes, err := ioutil.ReadFile(pubKeyPath)
|
||||
fatal(err)
|
||||
|
||||
verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
|
||||
|
@ -73,7 +73,7 @@ type CustomerInfo struct {
|
|||
}
|
||||
|
||||
type CustomClaimsExample struct {
|
||||
jwt.RegisteredClaims
|
||||
*jwt.RegisteredClaims
|
||||
TokenType string
|
||||
CustomerInfo
|
||||
}
|
||||
|
@ -113,6 +113,7 @@ func Example_getTokenViaHTTP() {
|
|||
}
|
||||
|
||||
func Example_useTokenViaHTTP() {
|
||||
|
||||
// Make a sample token
|
||||
// In a real world situation, this token will have been acquired from
|
||||
// some other API call (see Example_getTokenViaHTTP)
|
||||
|
@ -141,7 +142,7 @@ func createToken(user string) (string, error) {
|
|||
|
||||
// set our claims
|
||||
t.Claims = &CustomClaimsExample{
|
||||
jwt.RegisteredClaims{
|
||||
&jwt.RegisteredClaims{
|
||||
// set the expire time
|
||||
// see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 1)),
|
||||
|
@ -196,6 +197,7 @@ func restrictedHandler(w http.ResponseWriter, r *http.Request) {
|
|||
// we also only use its public counter part to verify
|
||||
return verifyKey, nil
|
||||
}, request.WithClaims(&CustomClaimsExample{}))
|
||||
|
||||
// If the token is missing or invalid, return error
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
|
|
|
@ -126,19 +126,16 @@ func (m MapClaims) Valid() error {
|
|||
now := TimeFunc().Unix()
|
||||
|
||||
if !m.VerifyExpiresAt(now, false) {
|
||||
// TODO(oxisto): this should be replaced with ErrTokenExpired
|
||||
vErr.Inner = errors.New("Token is expired")
|
||||
vErr.Errors |= ValidationErrorExpired
|
||||
}
|
||||
|
||||
if !m.VerifyIssuedAt(now, false) {
|
||||
// TODO(oxisto): this should be replaced with ErrTokenUsedBeforeIssued
|
||||
vErr.Inner = errors.New("Token used before issued")
|
||||
vErr.Errors |= ValidationErrorIssuedAt
|
||||
}
|
||||
|
||||
if !m.VerifyNotBefore(now, false) {
|
||||
// TODO(oxisto): this should be replaced with ErrTokenNotValidYet
|
||||
vErr.Inner = errors.New("Token is not valid yet")
|
||||
vErr.Errors |= ValidationErrorNotValidYet
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
var noneTestData = []struct {
|
||||
|
|
|
@ -42,13 +42,6 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
|
|||
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
|
||||
}
|
||||
|
||||
// ParseWithClaims parses, validates, and verifies like Parse, but supplies a default object implementing the Claims
|
||||
// interface. This provides default values which can be overridden and allows a caller to use their own type, rather
|
||||
// than the default MapClaims implementation of Claims.
|
||||
//
|
||||
// Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims),
|
||||
// make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the
|
||||
// proper memory for it before passing in the overall claims, otherwise you might run into a panic.
|
||||
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
|
||||
token, parts, err := p.ParseUnverified(tokenString, claims)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package jwt
|
||||
|
||||
// ParserOption is used to implement functional-style options that modify the behavior of the parser. To add
|
||||
// ParserOption is used to implement functional-style options that modify the behaviour of the parser. To add
|
||||
// new options, just create a function (ideally beginning with With or Without) that returns an anonymous function that
|
||||
// takes a *Parser type as input and manipulates its configuration accordingly.
|
||||
type ParserOption func(*Parser)
|
||||
|
@ -13,7 +13,7 @@ func WithValidMethods(methods []string) ParserOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithJSONNumber is an option to configure the underlying JSON parser with UseNumber
|
||||
// WithJSONNumber is an option to configure the underyling JSON parser with UseNumber
|
||||
func WithJSONNumber() ParserOption {
|
||||
return func(p *Parser) {
|
||||
p.UseJSONNumber = true
|
||||
|
|
154
parser_test.go
154
parser_test.go
|
@ -10,8 +10,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"git.internal/re/jwt/v4/test"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4/test"
|
||||
)
|
||||
|
||||
var errKeyFuncError error = fmt.Errorf("error loading key")
|
||||
|
@ -42,6 +42,7 @@ func init() {
|
|||
// Load private keys
|
||||
jwtTestRSAPrivateKey = test.LoadRSAPrivateKeyFromDisk("test/sample_key")
|
||||
jwtTestEC256PrivateKey = test.LoadECPrivateKeyFromDisk("test/ec256-private.pem")
|
||||
|
||||
}
|
||||
|
||||
var jwtTestData = []struct {
|
||||
|
@ -51,7 +52,6 @@ var jwtTestData = []struct {
|
|||
claims jwt.Claims
|
||||
valid bool
|
||||
errors uint32
|
||||
err []error
|
||||
parser *jwt.Parser
|
||||
signingMethod jwt.SigningMethod // The method to sign the JWT token for test purpose
|
||||
}{
|
||||
|
@ -63,7 +63,6 @@ var jwtTestData = []struct {
|
|||
true,
|
||||
0,
|
||||
nil,
|
||||
nil,
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
{
|
||||
|
@ -73,7 +72,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar", "exp": float64(time.Now().Unix() - 100)},
|
||||
false,
|
||||
jwt.ValidationErrorExpired,
|
||||
[]error{jwt.ErrTokenExpired},
|
||||
nil,
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -84,7 +82,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)},
|
||||
false,
|
||||
jwt.ValidationErrorNotValidYet,
|
||||
[]error{jwt.ErrTokenNotValidYet},
|
||||
nil,
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -95,7 +92,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)},
|
||||
false,
|
||||
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
|
||||
[]error{jwt.ErrTokenNotValidYet},
|
||||
nil,
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -106,7 +102,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar"},
|
||||
false,
|
||||
jwt.ValidationErrorSignatureInvalid,
|
||||
[]error{jwt.ErrTokenSignatureInvalid, rsa.ErrVerification},
|
||||
nil,
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -117,7 +112,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar"},
|
||||
false,
|
||||
jwt.ValidationErrorUnverifiable,
|
||||
[]error{jwt.ErrTokenUnverifiable},
|
||||
nil,
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -128,7 +122,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar"},
|
||||
false,
|
||||
jwt.ValidationErrorSignatureInvalid,
|
||||
[]error{jwt.ErrTokenSignatureInvalid},
|
||||
nil,
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -139,7 +132,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar"},
|
||||
false,
|
||||
jwt.ValidationErrorUnverifiable,
|
||||
[]error{jwt.ErrTokenUnverifiable, errKeyFuncError},
|
||||
nil,
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -150,7 +142,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar"},
|
||||
false,
|
||||
jwt.ValidationErrorSignatureInvalid,
|
||||
[]error{jwt.ErrTokenSignatureInvalid},
|
||||
&jwt.Parser{ValidMethods: []string{"HS256"}},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -161,7 +152,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar"},
|
||||
true,
|
||||
0,
|
||||
nil,
|
||||
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -172,7 +162,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar"},
|
||||
false,
|
||||
jwt.ValidationErrorSignatureInvalid,
|
||||
[]error{jwt.ErrTokenSignatureInvalid},
|
||||
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}},
|
||||
jwt.SigningMethodES256,
|
||||
},
|
||||
|
@ -183,7 +172,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar"},
|
||||
true,
|
||||
0,
|
||||
nil,
|
||||
&jwt.Parser{ValidMethods: []string{"HS256", "ES256"}},
|
||||
jwt.SigningMethodES256,
|
||||
},
|
||||
|
@ -194,7 +182,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": json.Number("123.4")},
|
||||
true,
|
||||
0,
|
||||
nil,
|
||||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -207,7 +194,6 @@ var jwtTestData = []struct {
|
|||
},
|
||||
true,
|
||||
0,
|
||||
nil,
|
||||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -218,7 +204,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar", "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
|
||||
false,
|
||||
jwt.ValidationErrorExpired,
|
||||
[]error{jwt.ErrTokenExpired},
|
||||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -229,7 +214,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
|
||||
false,
|
||||
jwt.ValidationErrorNotValidYet,
|
||||
[]error{jwt.ErrTokenNotValidYet},
|
||||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -240,7 +224,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100)), "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
|
||||
false,
|
||||
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
|
||||
[]error{jwt.ErrTokenNotValidYet},
|
||||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -251,7 +234,6 @@ var jwtTestData = []struct {
|
|||
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
|
||||
true,
|
||||
0,
|
||||
nil,
|
||||
&jwt.Parser{UseJSONNumber: true, SkipClaimsValidation: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -264,7 +246,6 @@ var jwtTestData = []struct {
|
|||
},
|
||||
true,
|
||||
0,
|
||||
nil,
|
||||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -277,7 +258,6 @@ var jwtTestData = []struct {
|
|||
},
|
||||
true,
|
||||
0,
|
||||
nil,
|
||||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -290,7 +270,6 @@ var jwtTestData = []struct {
|
|||
},
|
||||
true,
|
||||
0,
|
||||
nil,
|
||||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -303,7 +282,6 @@ var jwtTestData = []struct {
|
|||
},
|
||||
false,
|
||||
jwt.ValidationErrorMalformed,
|
||||
[]error{jwt.ErrTokenMalformed},
|
||||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -316,7 +294,6 @@ var jwtTestData = []struct {
|
|||
},
|
||||
false,
|
||||
jwt.ValidationErrorMalformed,
|
||||
[]error{jwt.ErrTokenMalformed},
|
||||
&jwt.Parser{UseJSONNumber: true},
|
||||
jwt.SigningMethodRS256,
|
||||
},
|
||||
|
@ -337,9 +314,11 @@ func signToken(claims jwt.Claims, signingMethod jwt.SigningMethod) string {
|
|||
}
|
||||
|
||||
func TestParser_Parse(t *testing.T) {
|
||||
|
||||
// Iterate over test data set and run tests
|
||||
for _, data := range jwtTestData {
|
||||
t.Run(data.name, func(t *testing.T) {
|
||||
|
||||
// If the token string is blank, use helper function to generate string
|
||||
if data.tokenString == "" {
|
||||
data.tokenString = signToken(data.claims, data.signingMethod)
|
||||
|
@ -349,7 +328,7 @@ func TestParser_Parse(t *testing.T) {
|
|||
var token *jwt.Token
|
||||
var ve *jwt.ValidationError
|
||||
var err error
|
||||
parser := data.parser
|
||||
var parser = data.parser
|
||||
if parser == nil {
|
||||
parser = new(jwt.Parser)
|
||||
}
|
||||
|
@ -396,22 +375,6 @@ func TestParser_Parse(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if data.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("[%v] Expecting error(s). Didn't get one.", data.name)
|
||||
} else {
|
||||
all := false
|
||||
for _, e := range data.err {
|
||||
all = errors.Is(err, e)
|
||||
}
|
||||
|
||||
if !all {
|
||||
t.Errorf("[%v] Errors don't match expectation. %v should contain all of %v", data.name, err, data.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if data.valid {
|
||||
if token.Signature == "" {
|
||||
t.Errorf("[%v] Signature is left unpopulated after parsing", data.name)
|
||||
|
@ -426,6 +389,7 @@ func TestParser_Parse(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParser_ParseUnverified(t *testing.T) {
|
||||
|
||||
// Iterate over test data set and run tests
|
||||
for _, data := range jwtTestData {
|
||||
// Skip test data, that intentionally contains malformed tokens, as they would lead to an error
|
||||
|
@ -442,7 +406,7 @@ func TestParser_ParseUnverified(t *testing.T) {
|
|||
// Parse the token
|
||||
var token *jwt.Token
|
||||
var err error
|
||||
parser := data.parser
|
||||
var parser = data.parser
|
||||
if parser == nil {
|
||||
parser = new(jwt.Parser)
|
||||
}
|
||||
|
@ -485,7 +449,6 @@ var setPaddingTestData = []struct {
|
|||
tokenString string
|
||||
claims jwt.Claims
|
||||
paddedDecode bool
|
||||
strictDecode bool
|
||||
signingMethod jwt.SigningMethod
|
||||
keyfunc jwt.Keyfunc
|
||||
valid bool
|
||||
|
@ -544,108 +507,19 @@ var setPaddingTestData = []struct {
|
|||
keyfunc: paddedKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
// DecodeStrict tests, DecodePaddingAllowed=false
|
||||
{
|
||||
name: "Validated non-padded token with padding disabled, non-strict decode, non-tweaked signature",
|
||||
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
|
||||
"g",
|
||||
claims: nil,
|
||||
paddedDecode: false,
|
||||
strictDecode: false,
|
||||
signingMethod: jwt.SigningMethodRS256,
|
||||
keyfunc: defaultKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Validated non-padded token with padding disabled, non-strict decode, tweaked signature",
|
||||
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
|
||||
"h",
|
||||
claims: nil,
|
||||
paddedDecode: false,
|
||||
strictDecode: false,
|
||||
signingMethod: jwt.SigningMethodRS256,
|
||||
keyfunc: defaultKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Validated non-padded token with padding disabled, strict decode, non-tweaked signature",
|
||||
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
|
||||
"g",
|
||||
claims: nil,
|
||||
paddedDecode: false,
|
||||
strictDecode: true,
|
||||
signingMethod: jwt.SigningMethodRS256,
|
||||
keyfunc: defaultKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Error for non-padded token with padding disabled, strict decode, tweaked signature",
|
||||
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ.bI15h-7mN0f-2diX5I4ErgNQy1uM-rJS5Sz7O0iTWtWSBxY1h6wy8Ywxe5EZTEO6GiIfk7Lk-72Ex-c5aA40QKhPwWB9BJ8O_LfKpezUVBOn0jRItDnVdsk4ccl2zsOVkbA4U4QvdrSbOYMbwoRHzDXfTFpoeMWtn3ez0aENJ8dh4E1echHp5ByI9Pu2aBsvM1WVcMt_BySweCL3f4T7jNZeXDr7Txd00yUd2gdsHYPjXorOvsgaBKN5GLsWd1zIY5z-2gCC8CRSN-IJ4NNX5ifh7l-bOXE2q7szTqa9pvyE9y6TQJhNMSE2FotRce_TOPBWgGpQ-K2I7E8x7wZ8O" +
|
||||
"h",
|
||||
claims: nil,
|
||||
paddedDecode: false,
|
||||
strictDecode: true,
|
||||
signingMethod: jwt.SigningMethodRS256,
|
||||
keyfunc: defaultKeyFunc,
|
||||
valid: false,
|
||||
},
|
||||
// DecodeStrict tests, DecodePaddingAllowed=true
|
||||
{
|
||||
name: "Validated padded token with padding enabled, non-strict decode, non-tweaked signature",
|
||||
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
|
||||
"w==",
|
||||
claims: nil,
|
||||
paddedDecode: true,
|
||||
strictDecode: false,
|
||||
signingMethod: jwt.SigningMethodES256,
|
||||
keyfunc: paddedKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Validated padded token with padding enabled, non-strict decode, tweaked signature",
|
||||
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
|
||||
"x==",
|
||||
claims: nil,
|
||||
paddedDecode: true,
|
||||
strictDecode: false,
|
||||
signingMethod: jwt.SigningMethodES256,
|
||||
keyfunc: paddedKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Validated padded token with padding enabled, strict decode, non-tweaked signature",
|
||||
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
|
||||
"w==",
|
||||
claims: nil,
|
||||
paddedDecode: true,
|
||||
strictDecode: true,
|
||||
signingMethod: jwt.SigningMethodES256,
|
||||
keyfunc: paddedKeyFunc,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Error for padded token with padding enabled, strict decode, tweaked signature",
|
||||
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3gr" +
|
||||
"x==",
|
||||
claims: nil,
|
||||
paddedDecode: true,
|
||||
strictDecode: true,
|
||||
signingMethod: jwt.SigningMethodES256,
|
||||
keyfunc: paddedKeyFunc,
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Extension of Parsing, this is to test out functionality specific to switching codecs with padding.
|
||||
func TestSetPadding(t *testing.T) {
|
||||
for _, data := range setPaddingTestData {
|
||||
t.Run(data.name, func(t *testing.T) {
|
||||
jwt.DecodePaddingAllowed = data.paddedDecode
|
||||
jwt.DecodeStrict = data.strictDecode
|
||||
|
||||
// If the token string is blank, use helper function to generate string
|
||||
jwt.DecodePaddingAllowed = data.paddedDecode
|
||||
|
||||
if data.tokenString == "" {
|
||||
data.tokenString = signToken(data.claims, data.signingMethod)
|
||||
|
||||
}
|
||||
|
||||
// Parse the token
|
||||
|
@ -664,13 +538,15 @@ func TestSetPadding(t *testing.T) {
|
|||
err,
|
||||
)
|
||||
}
|
||||
|
||||
})
|
||||
jwt.DecodePaddingAllowed = false
|
||||
jwt.DecodeStrict = false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParseUnverified(b *testing.B) {
|
||||
|
||||
// Iterate over test data set and run tests
|
||||
for _, data := range jwtTestData {
|
||||
// If the token string is blank, use helper function to generate string
|
||||
|
@ -679,7 +555,7 @@ func BenchmarkParseUnverified(b *testing.B) {
|
|||
}
|
||||
|
||||
// Parse the token
|
||||
parser := data.parser
|
||||
var parser = data.parser
|
||||
if parser == nil {
|
||||
parser = new(jwt.Parser)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package request
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Errors
|
||||
|
@ -59,7 +58,7 @@ func (e MultiExtractor) ExtractToken(req *http.Request) (string, error) {
|
|||
for _, extractor := range e {
|
||||
if tok, err := extractor.ExtractToken(req); tok != "" {
|
||||
return tok, nil
|
||||
} else if !errors.Is(err, ErrNoTokenInRequest) {
|
||||
} else if err != ErrNoTokenInRequest {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
@ -80,18 +79,3 @@ func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// BearerExtractor extracts a token from the Authorization header.
|
||||
// The header is expected to match the format "Bearer XX", where "XX" is the
|
||||
// JWT token.
|
||||
type BearerExtractor struct{}
|
||||
|
||||
func (e BearerExtractor) ExtractToken(req *http.Request) (string, error) {
|
||||
tokenHeader := req.Header.Get("Authorization")
|
||||
// The usual convention is for "Bearer" to be title-cased. However, there's no
|
||||
// strict rule around this, and it's best to follow the robustness principle here.
|
||||
if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), "bearer ") {
|
||||
return "", ErrNoTokenInRequest
|
||||
}
|
||||
return tokenHeader[7:], nil
|
||||
}
|
||||
|
|
|
@ -89,23 +89,3 @@ func makeExampleRequest(method, path string, headers map[string]string, urlArgs
|
|||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func TestBearerExtractor(t *testing.T) {
|
||||
request := makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearer ToKen"}, nil)
|
||||
token, err := BearerExtractor{}.ExtractToken(request)
|
||||
if err != nil || token != "ToKen" {
|
||||
t.Errorf("ExtractToken did not return token, returned: %v, %v", token, err)
|
||||
}
|
||||
|
||||
request = makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "Bearo ToKen"}, nil)
|
||||
token, err = BearerExtractor{}.ExtractToken(request)
|
||||
if err == nil || token != "" {
|
||||
t.Errorf("ExtractToken did not return error, returned: %v, %v", token, err)
|
||||
}
|
||||
|
||||
request = makeExampleRequest("POST", "https://example.com/", map[string]string{"Authorization": "BeArEr HeLO"}, nil)
|
||||
token, err = BearerExtractor{}.ExtractToken(request)
|
||||
if err != nil || token != "HeLO" {
|
||||
t.Errorf("ExtractToken did not return token, returned: %v, %v", token, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package request
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
// ParseFromRequest extracts and parses a JWT token from an HTTP request.
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"git.internal/re/jwt/v4/test"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4/test"
|
||||
)
|
||||
|
||||
var requestTestData = []struct {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
//go:build go1.4
|
||||
// +build go1.4
|
||||
|
||||
package jwt
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
//go:build go1.4
|
||||
// +build go1.4
|
||||
|
||||
package jwt_test
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"git.internal/re/jwt/v4/test"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4/test"
|
||||
)
|
||||
|
||||
var rsaPSSTestData = []struct {
|
||||
|
@ -54,7 +53,7 @@ var rsaPSSTestData = []struct {
|
|||
func TestRSAPSSVerify(t *testing.T) {
|
||||
var err error
|
||||
|
||||
key, _ := os.ReadFile("test/sample_key.pub")
|
||||
key, _ := ioutil.ReadFile("test/sample_key.pub")
|
||||
var rsaPSSKey *rsa.PublicKey
|
||||
if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil {
|
||||
t.Errorf("Unable to parse RSA public key: %v", err)
|
||||
|
@ -77,7 +76,7 @@ func TestRSAPSSVerify(t *testing.T) {
|
|||
func TestRSAPSSSign(t *testing.T) {
|
||||
var err error
|
||||
|
||||
key, _ := os.ReadFile("test/sample_key")
|
||||
key, _ := ioutil.ReadFile("test/sample_key")
|
||||
var rsaPSSKey *rsa.PrivateKey
|
||||
if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil {
|
||||
t.Errorf("Unable to parse RSA private key: %v", err)
|
||||
|
|
32
rsa_test.go
32
rsa_test.go
|
@ -1,47 +1,52 @@
|
|||
package jwt_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
var rsaTestData = []struct {
|
||||
name string
|
||||
tokenString string
|
||||
alg string
|
||||
claims map[string]interface{}
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
"Basic RS256",
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
|
||||
"RS256",
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Basic RS384",
|
||||
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
|
||||
"RS384",
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"Basic RS512",
|
||||
"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ",
|
||||
"RS512",
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"basic invalid: foo => bar",
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
|
||||
"RS256",
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
func TestRSAVerify(t *testing.T) {
|
||||
keyData, _ := os.ReadFile("test/sample_key.pub")
|
||||
keyData, _ := ioutil.ReadFile("test/sample_key.pub")
|
||||
key, _ := jwt.ParseRSAPublicKeyFromPEM(keyData)
|
||||
|
||||
for _, data := range rsaTestData {
|
||||
|
@ -59,7 +64,7 @@ func TestRSAVerify(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRSASign(t *testing.T) {
|
||||
keyData, _ := os.ReadFile("test/sample_key")
|
||||
keyData, _ := ioutil.ReadFile("test/sample_key")
|
||||
key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData)
|
||||
|
||||
for _, data := range rsaTestData {
|
||||
|
@ -78,7 +83,7 @@ func TestRSASign(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) {
|
||||
key, _ := os.ReadFile("test/sample_key.pub")
|
||||
key, _ := ioutil.ReadFile("test/sample_key.pub")
|
||||
parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -92,7 +97,7 @@ func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRSAWithPreParsedPrivateKey(t *testing.T) {
|
||||
key, _ := os.ReadFile("test/sample_key")
|
||||
key, _ := ioutil.ReadFile("test/sample_key")
|
||||
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -109,9 +114,9 @@ func TestRSAWithPreParsedPrivateKey(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRSAKeyParsing(t *testing.T) {
|
||||
key, _ := os.ReadFile("test/sample_key")
|
||||
secureKey, _ := os.ReadFile("test/privateSecure.pem")
|
||||
pubKey, _ := os.ReadFile("test/sample_key.pub")
|
||||
key, _ := ioutil.ReadFile("test/sample_key")
|
||||
secureKey, _ := ioutil.ReadFile("test/privateSecure.pem")
|
||||
pubKey, _ := ioutil.ReadFile("test/sample_key.pub")
|
||||
badKey := []byte("All your base are belong to key")
|
||||
|
||||
// Test parsePrivateKey
|
||||
|
@ -147,10 +152,11 @@ func TestRSAKeyParsing(t *testing.T) {
|
|||
if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil {
|
||||
t.Errorf("Parsed invalid key as valid private key: %v", k)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkRSAParsing(b *testing.B) {
|
||||
key, _ := os.ReadFile("test/sample_key")
|
||||
key, _ := ioutil.ReadFile("test/sample_key")
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
@ -164,7 +170,7 @@ func BenchmarkRSAParsing(b *testing.B) {
|
|||
}
|
||||
|
||||
func BenchmarkRS256Signing(b *testing.B) {
|
||||
key, _ := os.ReadFile("test/sample_key")
|
||||
key, _ := ioutil.ReadFile("test/sample_key")
|
||||
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
@ -174,7 +180,7 @@ func BenchmarkRS256Signing(b *testing.B) {
|
|||
}
|
||||
|
||||
func BenchmarkRS384Signing(b *testing.B) {
|
||||
key, _ := os.ReadFile("test/sample_key")
|
||||
key, _ := ioutil.ReadFile("test/sample_key")
|
||||
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
@ -184,7 +190,7 @@ func BenchmarkRS384Signing(b *testing.B) {
|
|||
}
|
||||
|
||||
func BenchmarkRS512Signing(b *testing.B) {
|
||||
key, _ := os.ReadFile("test/sample_key")
|
||||
key, _ := ioutil.ReadFile("test/sample_key")
|
||||
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
|
|
@ -3,13 +3,13 @@ package test
|
|||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
func LoadRSAPrivateKeyFromDisk(location string) *rsa.PrivateKey {
|
||||
keyData, e := os.ReadFile(location)
|
||||
keyData, e := ioutil.ReadFile(location)
|
||||
if e != nil {
|
||||
panic(e.Error())
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ func LoadRSAPrivateKeyFromDisk(location string) *rsa.PrivateKey {
|
|||
}
|
||||
|
||||
func LoadRSAPublicKeyFromDisk(location string) *rsa.PublicKey {
|
||||
keyData, e := os.ReadFile(location)
|
||||
keyData, e := ioutil.ReadFile(location)
|
||||
if e != nil {
|
||||
panic(e.Error())
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func MakeSampleToken(c jwt.Claims, method jwt.SigningMethod, key interface{}) st
|
|||
}
|
||||
|
||||
func LoadECPrivateKeyFromDisk(location string) crypto.PrivateKey {
|
||||
keyData, e := os.ReadFile(location)
|
||||
keyData, e := ioutil.ReadFile(location)
|
||||
if e != nil {
|
||||
panic(e.Error())
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ func LoadECPrivateKeyFromDisk(location string) crypto.PrivateKey {
|
|||
}
|
||||
|
||||
func LoadECPublicKeyFromDisk(location string) crypto.PublicKey {
|
||||
keyData, e := os.ReadFile(location)
|
||||
keyData, e := ioutil.ReadFile(location)
|
||||
if e != nil {
|
||||
panic(e.Error())
|
||||
}
|
||||
|
|
34
token.go
34
token.go
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
|
||||
// DecodePaddingAllowed will switch the codec used for decoding JWTs respectively. Note that the JWS RFC7515
|
||||
// states that the tokens will utilize a Base64url encoding with no padding. Unfortunately, some implementations
|
||||
// of JWT are producing non-standard tokens, and thus require support for decoding. Note that this is a global
|
||||
|
@ -14,12 +15,6 @@ import (
|
|||
// To use the non-recommended decoding, set this boolean to `true` prior to using this package.
|
||||
var DecodePaddingAllowed bool
|
||||
|
||||
// DecodeStrict will switch the codec used for decoding JWTs into strict mode.
|
||||
// In this mode, the decoder requires that trailing padding bits are zero, as described in RFC 4648 section 3.5.
|
||||
// Note that this is a global variable, and updating it will change the behavior on a package level, and is also NOT go-routine safe.
|
||||
// To use strict decoding, set this boolean to `true` prior to using this package.
|
||||
var DecodeStrict bool
|
||||
|
||||
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
|
||||
// You can override it to use another time value. This is useful for testing or if your
|
||||
// server uses a different time zone than your tokens.
|
||||
|
@ -79,19 +74,22 @@ func (t *Token) SignedString(key interface{}) (string, error) {
|
|||
// the SignedString.
|
||||
func (t *Token) SigningString() (string, error) {
|
||||
var err error
|
||||
parts := make([]string, 2)
|
||||
for i := range parts {
|
||||
var jsonValue []byte
|
||||
|
||||
if i == 0 {
|
||||
if jsonValue, err = json.Marshal(t.Header); err != nil {
|
||||
return "", err
|
||||
}
|
||||
header := EncodeSegment(jsonValue)
|
||||
|
||||
} else {
|
||||
if jsonValue, err = json.Marshal(t.Claims); err != nil {
|
||||
return "", err
|
||||
}
|
||||
claim := EncodeSegment(jsonValue)
|
||||
}
|
||||
|
||||
return strings.Join([]string{header, claim}, "."), nil
|
||||
parts[i] = EncodeSegment(jsonValue)
|
||||
}
|
||||
return strings.Join(parts, "."), nil
|
||||
}
|
||||
|
||||
// Parse parses, validates, verifies the signature and returns the parsed token.
|
||||
|
@ -105,11 +103,6 @@ func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token
|
|||
return NewParser(options...).Parse(tokenString, keyFunc)
|
||||
}
|
||||
|
||||
// ParseWithClaims is a shortcut for NewParser().ParseWithClaims().
|
||||
//
|
||||
// Note: If you provide a custom claim implementation that embeds one of the standard claims (such as RegisteredClaims),
|
||||
// make sure that a) you either embed a non-pointer version of the claims or b) if you are using a pointer, allocate the
|
||||
// proper memory for it before passing in the overall claims, otherwise you might run into a panic.
|
||||
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error) {
|
||||
return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc)
|
||||
}
|
||||
|
@ -127,17 +120,12 @@ func EncodeSegment(seg []byte) string {
|
|||
// Deprecated: In a future release, we will demote this function to a non-exported function, since it
|
||||
// should only be used internally
|
||||
func DecodeSegment(seg string) ([]byte, error) {
|
||||
encoding := base64.RawURLEncoding
|
||||
|
||||
if DecodePaddingAllowed {
|
||||
if l := len(seg) % 4; l > 0 {
|
||||
seg += strings.Repeat("=", 4-l)
|
||||
}
|
||||
encoding = base64.URLEncoding
|
||||
return base64.URLEncoding.DecodeString(seg)
|
||||
}
|
||||
|
||||
if DecodeStrict {
|
||||
encoding = encoding.Strict()
|
||||
}
|
||||
return encoding.DecodeString(seg)
|
||||
return base64.RawURLEncoding.DecodeString(seg)
|
||||
}
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
package jwt_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
)
|
||||
|
||||
func TestToken_SigningString(t1 *testing.T) {
|
||||
type fields struct {
|
||||
Raw string
|
||||
Method jwt.SigningMethod
|
||||
Header map[string]interface{}
|
||||
Claims jwt.Claims
|
||||
Signature string
|
||||
Valid bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "",
|
||||
fields: fields{
|
||||
Raw: "",
|
||||
Method: jwt.SigningMethodHS256,
|
||||
Header: map[string]interface{}{
|
||||
"typ": "JWT",
|
||||
"alg": jwt.SigningMethodHS256.Alg(),
|
||||
},
|
||||
Claims: jwt.StandardClaims{},
|
||||
Signature: "",
|
||||
Valid: false,
|
||||
},
|
||||
want: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t1.Run(tt.name, func(t1 *testing.T) {
|
||||
t := &jwt.Token{
|
||||
Raw: tt.fields.Raw,
|
||||
Method: tt.fields.Method,
|
||||
Header: tt.fields.Header,
|
||||
Claims: tt.fields.Claims,
|
||||
Signature: tt.fields.Signature,
|
||||
Valid: tt.fields.Valid,
|
||||
}
|
||||
got, err := t.SigningString()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t1.Errorf("SigningString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t1.Errorf("SigningString() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkToken_SigningString(b *testing.B) {
|
||||
t := &jwt.Token{
|
||||
Method: jwt.SigningMethodHS256,
|
||||
Header: map[string]interface{}{
|
||||
"typ": "JWT",
|
||||
"alg": jwt.SigningMethodHS256.Alg(),
|
||||
},
|
||||
Claims: jwt.StandardClaims{},
|
||||
}
|
||||
b.Run("BenchmarkToken_SigningString", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
t.SigningString()
|
||||
}
|
||||
})
|
||||
}
|
22
types.go
22
types.go
|
@ -49,27 +49,9 @@ func newNumericDateFromSeconds(f float64) *NumericDate {
|
|||
// MarshalJSON is an implementation of the json.RawMessage interface and serializes the UNIX epoch
|
||||
// represented in NumericDate to a byte array, using the precision specified in TimePrecision.
|
||||
func (date NumericDate) MarshalJSON() (b []byte, err error) {
|
||||
var prec int
|
||||
if TimePrecision < time.Second {
|
||||
prec = int(math.Log10(float64(time.Second) / float64(TimePrecision)))
|
||||
}
|
||||
truncatedDate := date.Truncate(TimePrecision)
|
||||
f := float64(date.Truncate(TimePrecision).UnixNano()) / float64(time.Second)
|
||||
|
||||
// For very large timestamps, UnixNano would overflow an int64, but this
|
||||
// function requires nanosecond level precision, so we have to use the
|
||||
// following technique to get round the issue:
|
||||
// 1. Take the normal unix timestamp to form the whole number part of the
|
||||
// output,
|
||||
// 2. Take the result of the Nanosecond function, which retuns the offset
|
||||
// within the second of the particular unix time instance, to form the
|
||||
// decimal part of the output
|
||||
// 3. Concatenate them to produce the final result
|
||||
seconds := strconv.FormatInt(truncatedDate.Unix(), 10)
|
||||
nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64)
|
||||
|
||||
output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...)
|
||||
|
||||
return output, nil
|
||||
return []byte(strconv.FormatFloat(f, 'f', -1, 64)), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a
|
||||
|
|
|
@ -2,11 +2,10 @@ package jwt_test
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.internal/re/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
func TestNumericDate(t *testing.T) {
|
||||
|
@ -19,10 +18,12 @@ func TestNumericDate(t *testing.T) {
|
|||
|
||||
jwt.TimePrecision = time.Microsecond
|
||||
|
||||
raw := `{"iat":1516239022.000000,"exp":1516239022.123450}`
|
||||
raw := `{"iat":1516239022,"exp":1516239022.12345}`
|
||||
|
||||
if err := json.Unmarshal([]byte(raw), &s); err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
err := json.Unmarshal([]byte(raw), &s)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(s)
|
||||
|
@ -41,6 +42,7 @@ func TestSingleArrayMarshal(t *testing.T) {
|
|||
expected := `"test"`
|
||||
|
||||
b, err := json.Marshal(s)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
|
@ -63,64 +65,3 @@ func TestSingleArrayMarshal(t *testing.T) {
|
|||
t.Errorf("Serialized format of string array mismatch. Expecting: %s Got: %s", string(expected), string(b))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumericDate_MarshalJSON(t *testing.T) {
|
||||
// Do not run this test in parallel because it's changing
|
||||
// global state.
|
||||
oldPrecision := jwt.TimePrecision
|
||||
t.Cleanup(func() {
|
||||
jwt.TimePrecision = oldPrecision
|
||||
})
|
||||
|
||||
tt := []struct {
|
||||
in time.Time
|
||||
want string
|
||||
precision time.Duration
|
||||
}{
|
||||
{time.Unix(5243700879, 0), "5243700879", time.Second},
|
||||
{time.Unix(5243700879, 0), "5243700879.000", time.Millisecond},
|
||||
{time.Unix(5243700879, 0), "5243700879.000000", time.Microsecond},
|
||||
{time.Unix(5243700879, 0), "5243700879.000000000", time.Nanosecond},
|
||||
//
|
||||
{time.Unix(4239425898, 0), "4239425898", time.Second},
|
||||
{time.Unix(4239425898, 0), "4239425898.000", time.Millisecond},
|
||||
{time.Unix(4239425898, 0), "4239425898.000000", time.Microsecond},
|
||||
{time.Unix(4239425898, 0), "4239425898.000000000", time.Nanosecond},
|
||||
//
|
||||
{time.Unix(253402271999, 0), "253402271999", time.Second},
|
||||
{time.Unix(253402271999, 0), "253402271999.000", time.Millisecond},
|
||||
{time.Unix(253402271999, 0), "253402271999.000000", time.Microsecond},
|
||||
{time.Unix(253402271999, 0), "253402271999.000000000", time.Nanosecond},
|
||||
//
|
||||
{time.Unix(0, 1644285000210402000), "1644285000", time.Second},
|
||||
{time.Unix(0, 1644285000210402000), "1644285000.210", time.Millisecond},
|
||||
{time.Unix(0, 1644285000210402000), "1644285000.210402", time.Microsecond},
|
||||
{time.Unix(0, 1644285000210402000), "1644285000.210402000", time.Nanosecond},
|
||||
//
|
||||
{time.Unix(0, 1644285315063096000), "1644285315", time.Second},
|
||||
{time.Unix(0, 1644285315063096000), "1644285315.063", time.Millisecond},
|
||||
{time.Unix(0, 1644285315063096000), "1644285315.063096", time.Microsecond},
|
||||
{time.Unix(0, 1644285315063096000), "1644285315.063096000", time.Nanosecond},
|
||||
// Maximum time that a go time.Time can represent
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807", time.Second},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999", time.Millisecond},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999999", time.Microsecond},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807.999999999", time.Nanosecond},
|
||||
// Strange precisions
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775807", time.Second},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854775756", time.Minute},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854774016", time.Hour},
|
||||
{time.Unix(math.MaxInt64, 999999999), "9223372036854745216", 24 * time.Hour},
|
||||
}
|
||||
|
||||
for i, tc := range tt {
|
||||
jwt.TimePrecision = tc.precision
|
||||
by, err := jwt.NewNumericDate(tc.in).MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := string(by); got != tc.want {
|
||||
t.Errorf("[%d]: failed encoding: got %q want %q", i, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue