Compare commits

..

3 Commits

Author SHA1 Message Date
Mike Fridman de7268f1f8 Format Go code
go fmt ./...
2022-05-27 19:14:55 -04:00
Mike Fridman 1697222a72 Add Go code formatting 2022-05-27 19:14:46 -04:00
jay-dee7 d272f4dadc
Chore: Added Github Action to auto format code on push to main
Signed-off-by: jay-dee7 <jasdeepsingh.uppal@gmail.com>
2022-03-28 03:05:52 +05:30
28 changed files with 125 additions and 321 deletions

View File

@ -25,7 +25,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
go: [1.17, 1.18, 1.19] go: [1.16, 1.17, 1.18]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -33,8 +33,6 @@ jobs:
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: "${{ matrix.go }}" go-version: "${{ matrix.go }}"
check-latest: true
cache: true
- name: Check Go code formatting - name: Check Go code formatting
run: | run: |
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
@ -44,7 +42,6 @@ jobs:
fi fi
- name: Build - name: Build
run: | run: |
go install github.com/mfridman/tparse@latest
go vet ./... go vet ./...
go test -v -race -count=1 -json -coverpkg=$(go list ./...) ./... | tparse -follow -notests go test -v ./...
go build ./... go build ./...

View File

@ -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: 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 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 `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. 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: And then you'd typically run:
``` ```
go get git.internal/re/jwt/v4 go get github.com/golang-jwt/jwt/v4
go mod tidy go mod tidy
``` ```

View File

@ -1,7 +1,7 @@
# jwt-go # 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) [![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). 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,39 +36,24 @@ 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. 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 ## 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 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/git.internal/re/jwt/v4#example-New-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/git.internal/re/jwt/v4#pkg-examples) * [Directory of Examples](https://pkg.go.dev/github.com/golang-jwt/jwt#pkg-examples)
## Extensions ## 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. 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).
| Extension | Purpose | Repo | | Extension | Purpose | Repo |
| --------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ | |-----------|----------------------------------------------------------------------------------------------|--------------------------------------------|
| GCP | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS) | https://github.com/someone1/gcp-jwt-go | | 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 | | 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 *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
@ -96,7 +81,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 author of the token was in the possession of the signing secret
* The data has not been modified since it was signed * 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 ### Choosing a Signing Method
@ -110,10 +95,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: 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 [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/git.internal/re/jwt/v4#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for 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/git.internal/re/jwt/v4#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.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/git.internal/re/jwt/v4#SigningMethodEd25519) (`Ed25519`) expect `ed25519.PrivateKey` for signing and `ed25519.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 ### JWT and OAuth
@ -131,7 +116,7 @@ This library uses descriptive error messages whenever possible. If you are not g
## More ## 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. 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.

View File

@ -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!

View File

@ -265,5 +265,9 @@ func verifyIss(iss string, cmp string, required bool) bool {
if iss == "" { if iss == "" {
return !required return !required
} }
return subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 {
return true
} else {
return false
}
} }

View File

@ -16,4 +16,4 @@ To simply display a token, use:
You can install this tool with the following command: 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

View File

@ -3,7 +3,6 @@
// //
// Example usage: // Example usage:
// The following will create and sign a token, then verify it and output the original claims. // 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 - // echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify -
package main package main
@ -12,12 +11,13 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
var ( var (
@ -91,7 +91,7 @@ func loadData(p string) ([]byte, error) {
return nil, err 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) // Print a json object in accordance with the prophecy (or the command line options)

View File

@ -2,11 +2,11 @@ package jwt_test
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"os" "io/ioutil"
"strings" "strings"
"testing" "testing"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
var ecdsaTestData = []struct { var ecdsaTestData = []struct {
@ -55,7 +55,7 @@ func TestECDSAVerify(t *testing.T) {
for _, data := range ecdsaTestData { for _, data := range ecdsaTestData {
var err error var err error
key, _ := os.ReadFile(data.keys["public"]) key, _ := ioutil.ReadFile(data.keys["public"])
var ecdsaKey *ecdsa.PublicKey var ecdsaKey *ecdsa.PublicKey
if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil { if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil {
@ -78,7 +78,7 @@ func TestECDSAVerify(t *testing.T) {
func TestECDSASign(t *testing.T) { func TestECDSASign(t *testing.T) {
for _, data := range ecdsaTestData { for _, data := range ecdsaTestData {
var err error var err error
key, _ := os.ReadFile(data.keys["private"]) key, _ := ioutil.ReadFile(data.keys["private"])
var ecdsaKey *ecdsa.PrivateKey var ecdsaKey *ecdsa.PrivateKey
if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil { if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil {
@ -90,6 +90,7 @@ func TestECDSASign(t *testing.T) {
toSign := strings.Join(parts[0:2], ".") toSign := strings.Join(parts[0:2], ".")
method := jwt.GetSigningMethod(data.alg) method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(toSign, ecdsaKey) sig, err := method.Sign(toSign, ecdsaKey)
if err != nil { if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err) t.Errorf("[%v] Error signing token: %v", data.name, err)
} }
@ -107,7 +108,7 @@ func TestECDSASign(t *testing.T) {
func BenchmarkECDSAParsing(b *testing.B) { func BenchmarkECDSAParsing(b *testing.B) {
for _, data := range ecdsaTestData { 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.Run(data.name, func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
@ -125,7 +126,7 @@ func BenchmarkECDSAParsing(b *testing.B) {
func BenchmarkECDSASigning(b *testing.B) { func BenchmarkECDSASigning(b *testing.B) {
for _, data := range ecdsaTestData { for _, data := range ecdsaTestData {
key, _ := os.ReadFile(data.keys["private"]) key, _ := ioutil.ReadFile(data.keys["private"])
ecdsaKey, err := jwt.ParseECPrivateKeyFromPEM(key) ecdsaKey, err := jwt.ParseECPrivateKeyFromPEM(key)
if err != nil { if err != nil {

View File

@ -1,11 +1,11 @@
package jwt_test package jwt_test
import ( import (
"os" "io/ioutil"
"strings" "strings"
"testing" "testing"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
var ed25519TestData = []struct { var ed25519TestData = []struct {
@ -38,7 +38,7 @@ func TestEd25519Verify(t *testing.T) {
for _, data := range ed25519TestData { for _, data := range ed25519TestData {
var err error var err error
key, _ := os.ReadFile(data.keys["public"]) key, _ := ioutil.ReadFile(data.keys["public"])
ed25519Key, err := jwt.ParseEdPublicKeyFromPEM(key) ed25519Key, err := jwt.ParseEdPublicKeyFromPEM(key)
if err != nil { if err != nil {
@ -62,7 +62,7 @@ func TestEd25519Verify(t *testing.T) {
func TestEd25519Sign(t *testing.T) { func TestEd25519Sign(t *testing.T) {
for _, data := range ed25519TestData { for _, data := range ed25519TestData {
var err error var err error
key, _ := os.ReadFile(data.keys["private"]) key, _ := ioutil.ReadFile(data.keys["private"])
ed25519Key, err := jwt.ParseEdPrivateKeyFromPEM(key) ed25519Key, err := jwt.ParseEdPrivateKeyFromPEM(key)
if err != nil { if err != nil {

View File

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"time" "time"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
// Example (atypical) using the RegisteredClaims type by itself to parse a token. // Example (atypical) using the RegisteredClaims type by itself to parse a token.
@ -25,7 +25,7 @@ func ExampleNewWithClaims_registeredClaims() {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey) ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err) fmt.Printf("%v %v", ss, err)
// Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.0XN_1Tpp9FszFOonIBpwha0c_SfnNI22DhTnjMshPg8 <nil> //Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.0XN_1Tpp9FszFOonIBpwha0c_SfnNI22DhTnjMshPg8 <nil>
} }
// Example creating a token using a custom claims type. The RegisteredClaims is embedded // Example creating a token using a custom claims type. The RegisteredClaims is embedded
@ -67,7 +67,7 @@ func ExampleNewWithClaims_customClaimsType() {
ss, err := token.SignedString(mySigningKey) ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err) fmt.Printf("%v %v", ss, err)
// Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.xVuY2FZ_MRXMIEgVQ7J-TFtaucVFRXUzHm9LmV41goM <nil> //Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.xVuY2FZ_MRXMIEgVQ7J-TFtaucVFRXUzHm9LmV41goM <nil>
} }
// Example creating a token using a custom claims type. The StandardClaim is embedded // Example creating a token using a custom claims type. The StandardClaim is embedded
@ -96,7 +96,7 @@ func ExampleParseWithClaims_customClaimsType() {
// An example of parsing the error types using bitfield checks // An example of parsing the error types using bitfield checks
func ExampleParse_errorChecking() { func ExampleParse_errorChecking() {
// Token from another example. This token is expired // Token from another example. This token is expired
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c" var 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 return []byte("AllYourBase"), nil

2
go.mod
View File

@ -1,4 +1,4 @@
module git.internal/re/jwt/v4 module github.com/golang-jwt/jwt/v4
go 1.16 go 1.16

View File

@ -2,10 +2,10 @@ package jwt_test
import ( import (
"fmt" "fmt"
"os" "io/ioutil"
"time" "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 // For HMAC signing method, the key can be any []byte. It is recommended to generate
@ -15,7 +15,7 @@ var hmacSampleSecret []byte
func init() { func init() {
// Load sample key data // Load sample key data
if keyData, e := os.ReadFile("test/hmacTestKey"); e == nil { if keyData, e := ioutil.ReadFile("test/hmacTestKey"); e == nil {
hmacSampleSecret = keyData hmacSampleSecret = keyData
} else { } else {
panic(e) panic(e)

View File

@ -1,11 +1,11 @@
package jwt_test package jwt_test
import ( import (
"os" "io/ioutil"
"strings" "strings"
"testing" "testing"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
var hmacTestData = []struct { 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 // 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) { func TestHMACVerify(t *testing.T) {
for _, data := range hmacTestData { for _, data := range hmacTestData {

View File

@ -8,16 +8,16 @@ import (
"crypto/rsa" "crypto/rsa"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strings" "strings"
"time" "time"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
"git.internal/re/jwt/v4/request" "github.com/golang-jwt/jwt/v4/request"
) )
// location of the files used for signing and verification // location of the files used for signing and verification
@ -34,13 +34,13 @@ var (
// read the key files before starting http handlers // read the key files before starting http handlers
func init() { func init() {
signBytes, err := os.ReadFile(privKeyPath) signBytes, err := ioutil.ReadFile(privKeyPath)
fatal(err) fatal(err)
signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes) signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
fatal(err) fatal(err)
verifyBytes, err := os.ReadFile(pubKeyPath) verifyBytes, err := ioutil.ReadFile(pubKeyPath)
fatal(err) fatal(err)
verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes) verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
@ -73,7 +73,7 @@ type CustomerInfo struct {
} }
type CustomClaimsExample struct { type CustomClaimsExample struct {
jwt.RegisteredClaims *jwt.RegisteredClaims
TokenType string TokenType string
CustomerInfo CustomerInfo
} }
@ -109,10 +109,11 @@ func Example_getTokenViaHTTP() {
claims := token.Claims.(*CustomClaimsExample) claims := token.Claims.(*CustomClaimsExample)
fmt.Println(claims.CustomerInfo.Name) fmt.Println(claims.CustomerInfo.Name)
// Output: test //Output: test
} }
func Example_useTokenViaHTTP() { func Example_useTokenViaHTTP() {
// Make a sample token // Make a sample token
// In a real world situation, this token will have been acquired from // In a real world situation, this token will have been acquired from
// some other API call (see Example_getTokenViaHTTP) // some other API call (see Example_getTokenViaHTTP)
@ -141,7 +142,7 @@ func createToken(user string) (string, error) {
// set our claims // set our claims
t.Claims = &CustomClaimsExample{ t.Claims = &CustomClaimsExample{
jwt.RegisteredClaims{ &jwt.RegisteredClaims{
// set the expire time // set the expire time
// see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4 // see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 1)), 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 // we also only use its public counter part to verify
return verifyKey, nil return verifyKey, nil
}, request.WithClaims(&CustomClaimsExample{})) }, request.WithClaims(&CustomClaimsExample{}))
// If the token is missing or invalid, return error // If the token is missing or invalid, return error
if err != nil { if err != nil {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
var noneTestData = []struct { var noneTestData = []struct {

View File

@ -42,13 +42,6 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc) 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) { func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
token, parts, err := p.ParseUnverified(tokenString, claims) token, parts, err := p.ParseUnverified(tokenString, claims)
if err != nil { if err != nil {

View File

@ -10,8 +10,8 @@ import (
"testing" "testing"
"time" "time"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
"git.internal/re/jwt/v4/test" "github.com/golang-jwt/jwt/v4/test"
) )
var errKeyFuncError error = fmt.Errorf("error loading key") var errKeyFuncError error = fmt.Errorf("error loading key")
@ -42,6 +42,7 @@ func init() {
// Load private keys // Load private keys
jwtTestRSAPrivateKey = test.LoadRSAPrivateKeyFromDisk("test/sample_key") jwtTestRSAPrivateKey = test.LoadRSAPrivateKeyFromDisk("test/sample_key")
jwtTestEC256PrivateKey = test.LoadECPrivateKeyFromDisk("test/ec256-private.pem") jwtTestEC256PrivateKey = test.LoadECPrivateKeyFromDisk("test/ec256-private.pem")
} }
var jwtTestData = []struct { var jwtTestData = []struct {
@ -337,9 +338,11 @@ func signToken(claims jwt.Claims, signingMethod jwt.SigningMethod) string {
} }
func TestParser_Parse(t *testing.T) { func TestParser_Parse(t *testing.T) {
// Iterate over test data set and run tests // Iterate over test data set and run tests
for _, data := range jwtTestData { for _, data := range jwtTestData {
t.Run(data.name, func(t *testing.T) { t.Run(data.name, func(t *testing.T) {
// If the token string is blank, use helper function to generate string // If the token string is blank, use helper function to generate string
if data.tokenString == "" { if data.tokenString == "" {
data.tokenString = signToken(data.claims, data.signingMethod) data.tokenString = signToken(data.claims, data.signingMethod)
@ -349,7 +352,7 @@ func TestParser_Parse(t *testing.T) {
var token *jwt.Token var token *jwt.Token
var ve *jwt.ValidationError var ve *jwt.ValidationError
var err error var err error
parser := data.parser var parser = data.parser
if parser == nil { if parser == nil {
parser = new(jwt.Parser) parser = new(jwt.Parser)
} }
@ -401,7 +404,7 @@ func TestParser_Parse(t *testing.T) {
if err == nil { if err == nil {
t.Errorf("[%v] Expecting error(s). Didn't get one.", data.name) t.Errorf("[%v] Expecting error(s). Didn't get one.", data.name)
} else { } else {
all := false var all = false
for _, e := range data.err { for _, e := range data.err {
all = errors.Is(err, e) all = errors.Is(err, e)
} }
@ -426,6 +429,7 @@ func TestParser_Parse(t *testing.T) {
} }
func TestParser_ParseUnverified(t *testing.T) { func TestParser_ParseUnverified(t *testing.T) {
// Iterate over test data set and run tests // Iterate over test data set and run tests
for _, data := range jwtTestData { for _, data := range jwtTestData {
// Skip test data, that intentionally contains malformed tokens, as they would lead to an error // Skip test data, that intentionally contains malformed tokens, as they would lead to an error
@ -442,7 +446,7 @@ func TestParser_ParseUnverified(t *testing.T) {
// Parse the token // Parse the token
var token *jwt.Token var token *jwt.Token
var err error var err error
parser := data.parser var parser = data.parser
if parser == nil { if parser == nil {
parser = new(jwt.Parser) parser = new(jwt.Parser)
} }
@ -485,7 +489,6 @@ var setPaddingTestData = []struct {
tokenString string tokenString string
claims jwt.Claims claims jwt.Claims
paddedDecode bool paddedDecode bool
strictDecode bool
signingMethod jwt.SigningMethod signingMethod jwt.SigningMethod
keyfunc jwt.Keyfunc keyfunc jwt.Keyfunc
valid bool valid bool
@ -544,108 +547,19 @@ var setPaddingTestData = []struct {
keyfunc: paddedKeyFunc, keyfunc: paddedKeyFunc,
valid: true, 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. // Extension of Parsing, this is to test out functionality specific to switching codecs with padding.
func TestSetPadding(t *testing.T) { func TestSetPadding(t *testing.T) {
for _, data := range setPaddingTestData { for _, data := range setPaddingTestData {
t.Run(data.name, func(t *testing.T) { 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 // If the token string is blank, use helper function to generate string
jwt.DecodePaddingAllowed = data.paddedDecode
if data.tokenString == "" { if data.tokenString == "" {
data.tokenString = signToken(data.claims, data.signingMethod) data.tokenString = signToken(data.claims, data.signingMethod)
} }
// Parse the token // Parse the token
@ -664,13 +578,15 @@ func TestSetPadding(t *testing.T) {
err, err,
) )
} }
}) })
jwt.DecodePaddingAllowed = false jwt.DecodePaddingAllowed = false
jwt.DecodeStrict = false
} }
} }
func BenchmarkParseUnverified(b *testing.B) { func BenchmarkParseUnverified(b *testing.B) {
// Iterate over test data set and run tests // Iterate over test data set and run tests
for _, data := range jwtTestData { for _, data := range jwtTestData {
// If the token string is blank, use helper function to generate string // If the token string is blank, use helper function to generate string
@ -679,7 +595,7 @@ func BenchmarkParseUnverified(b *testing.B) {
} }
// Parse the token // Parse the token
parser := data.parser var parser = data.parser
if parser == nil { if parser == nil {
parser = new(jwt.Parser) parser = new(jwt.Parser)
} }

View File

@ -3,7 +3,6 @@ package request
import ( import (
"errors" "errors"
"net/http" "net/http"
"strings"
) )
// Errors // Errors
@ -80,18 +79,3 @@ func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) {
return "", err 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
}

View File

@ -89,23 +89,3 @@ func makeExampleRequest(method, path string, headers map[string]string, urlArgs
} }
return r 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)
}
}

View File

@ -3,7 +3,7 @@ package request
import ( import (
"net/http" "net/http"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
// ParseFromRequest extracts and parses a JWT token from an HTTP request. // ParseFromRequest extracts and parses a JWT token from an HTTP request.

View File

@ -8,8 +8,8 @@ import (
"strings" "strings"
"testing" "testing"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
"git.internal/re/jwt/v4/test" "github.com/golang-jwt/jwt/v4/test"
) )
var requestTestData = []struct { var requestTestData = []struct {

View File

@ -5,13 +5,13 @@ package jwt_test
import ( import (
"crypto/rsa" "crypto/rsa"
"os" "io/ioutil"
"strings" "strings"
"testing" "testing"
"time" "time"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
"git.internal/re/jwt/v4/test" "github.com/golang-jwt/jwt/v4/test"
) )
var rsaPSSTestData = []struct { var rsaPSSTestData = []struct {
@ -54,7 +54,7 @@ var rsaPSSTestData = []struct {
func TestRSAPSSVerify(t *testing.T) { func TestRSAPSSVerify(t *testing.T) {
var err error var err error
key, _ := os.ReadFile("test/sample_key.pub") key, _ := ioutil.ReadFile("test/sample_key.pub")
var rsaPSSKey *rsa.PublicKey var rsaPSSKey *rsa.PublicKey
if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil { if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse RSA public key: %v", err) t.Errorf("Unable to parse RSA public key: %v", err)
@ -77,7 +77,7 @@ func TestRSAPSSVerify(t *testing.T) {
func TestRSAPSSSign(t *testing.T) { func TestRSAPSSSign(t *testing.T) {
var err error var err error
key, _ := os.ReadFile("test/sample_key") key, _ := ioutil.ReadFile("test/sample_key")
var rsaPSSKey *rsa.PrivateKey var rsaPSSKey *rsa.PrivateKey
if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil { if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse RSA private key: %v", err) t.Errorf("Unable to parse RSA private key: %v", err)

View File

@ -1,47 +1,52 @@
package jwt_test package jwt_test
import ( import (
"os" "io/ioutil"
"strings" "strings"
"testing" "testing"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
var rsaTestData = []struct { var rsaTestData = []struct {
name string name string
tokenString string tokenString string
alg string alg string
claims map[string]interface{}
valid bool valid bool
}{ }{
{ {
"Basic RS256", "Basic RS256",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
"RS256", "RS256",
map[string]interface{}{"foo": "bar"},
true, true,
}, },
{ {
"Basic RS384", "Basic RS384",
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw", "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
"RS384", "RS384",
map[string]interface{}{"foo": "bar"},
true, true,
}, },
{ {
"Basic RS512", "Basic RS512",
"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ", "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ",
"RS512", "RS512",
map[string]interface{}{"foo": "bar"},
true, true,
}, },
{ {
"basic invalid: foo => bar", "basic invalid: foo => bar",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
"RS256", "RS256",
map[string]interface{}{"foo": "bar"},
false, false,
}, },
} }
func TestRSAVerify(t *testing.T) { func TestRSAVerify(t *testing.T) {
keyData, _ := os.ReadFile("test/sample_key.pub") keyData, _ := ioutil.ReadFile("test/sample_key.pub")
key, _ := jwt.ParseRSAPublicKeyFromPEM(keyData) key, _ := jwt.ParseRSAPublicKeyFromPEM(keyData)
for _, data := range rsaTestData { for _, data := range rsaTestData {
@ -59,7 +64,7 @@ func TestRSAVerify(t *testing.T) {
} }
func TestRSASign(t *testing.T) { func TestRSASign(t *testing.T) {
keyData, _ := os.ReadFile("test/sample_key") keyData, _ := ioutil.ReadFile("test/sample_key")
key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData) key, _ := jwt.ParseRSAPrivateKeyFromPEM(keyData)
for _, data := range rsaTestData { for _, data := range rsaTestData {
@ -78,7 +83,7 @@ func TestRSASign(t *testing.T) {
} }
func TestRSAVerifyWithPreParsedPrivateKey(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) parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -92,7 +97,7 @@ func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) {
} }
func TestRSAWithPreParsedPrivateKey(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) parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -109,9 +114,9 @@ func TestRSAWithPreParsedPrivateKey(t *testing.T) {
} }
func TestRSAKeyParsing(t *testing.T) { func TestRSAKeyParsing(t *testing.T) {
key, _ := os.ReadFile("test/sample_key") key, _ := ioutil.ReadFile("test/sample_key")
secureKey, _ := os.ReadFile("test/privateSecure.pem") secureKey, _ := ioutil.ReadFile("test/privateSecure.pem")
pubKey, _ := os.ReadFile("test/sample_key.pub") pubKey, _ := ioutil.ReadFile("test/sample_key.pub")
badKey := []byte("All your base are belong to key") badKey := []byte("All your base are belong to key")
// Test parsePrivateKey // Test parsePrivateKey
@ -147,10 +152,11 @@ func TestRSAKeyParsing(t *testing.T) {
if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil { if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil {
t.Errorf("Parsed invalid key as valid private key: %v", k) t.Errorf("Parsed invalid key as valid private key: %v", k)
} }
} }
func BenchmarkRSAParsing(b *testing.B) { func BenchmarkRSAParsing(b *testing.B) {
key, _ := os.ReadFile("test/sample_key") key, _ := ioutil.ReadFile("test/sample_key")
b.ReportAllocs() b.ReportAllocs()
b.ResetTimer() b.ResetTimer()
@ -164,7 +170,7 @@ func BenchmarkRSAParsing(b *testing.B) {
} }
func BenchmarkRS256Signing(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) parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -174,7 +180,7 @@ func BenchmarkRS256Signing(b *testing.B) {
} }
func BenchmarkRS384Signing(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) parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -184,7 +190,7 @@ func BenchmarkRS384Signing(b *testing.B) {
} }
func BenchmarkRS512Signing(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) parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)

View File

@ -3,13 +3,13 @@ package test
import ( import (
"crypto" "crypto"
"crypto/rsa" "crypto/rsa"
"os" "io/ioutil"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
func LoadRSAPrivateKeyFromDisk(location string) *rsa.PrivateKey { func LoadRSAPrivateKeyFromDisk(location string) *rsa.PrivateKey {
keyData, e := os.ReadFile(location) keyData, e := ioutil.ReadFile(location)
if e != nil { if e != nil {
panic(e.Error()) panic(e.Error())
} }
@ -21,7 +21,7 @@ func LoadRSAPrivateKeyFromDisk(location string) *rsa.PrivateKey {
} }
func LoadRSAPublicKeyFromDisk(location string) *rsa.PublicKey { func LoadRSAPublicKeyFromDisk(location string) *rsa.PublicKey {
keyData, e := os.ReadFile(location) keyData, e := ioutil.ReadFile(location)
if e != nil { if e != nil {
panic(e.Error()) panic(e.Error())
} }
@ -45,7 +45,7 @@ func MakeSampleToken(c jwt.Claims, method jwt.SigningMethod, key interface{}) st
} }
func LoadECPrivateKeyFromDisk(location string) crypto.PrivateKey { func LoadECPrivateKeyFromDisk(location string) crypto.PrivateKey {
keyData, e := os.ReadFile(location) keyData, e := ioutil.ReadFile(location)
if e != nil { if e != nil {
panic(e.Error()) panic(e.Error())
} }
@ -57,7 +57,7 @@ func LoadECPrivateKeyFromDisk(location string) crypto.PrivateKey {
} }
func LoadECPublicKeyFromDisk(location string) crypto.PublicKey { func LoadECPublicKeyFromDisk(location string) crypto.PublicKey {
keyData, e := os.ReadFile(location) keyData, e := ioutil.ReadFile(location)
if e != nil { if e != nil {
panic(e.Error()) panic(e.Error())
} }

View File

@ -14,12 +14,6 @@ import (
// To use the non-recommended decoding, set this boolean to `true` prior to using this package. // To use the non-recommended decoding, set this boolean to `true` prior to using this package.
var DecodePaddingAllowed bool 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). // 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 // 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. // server uses a different time zone than your tokens.
@ -105,11 +99,6 @@ func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token
return NewParser(options...).Parse(tokenString, keyFunc) 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) { func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error) {
return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc) return NewParser(options...).ParseWithClaims(tokenString, claims, keyFunc)
} }
@ -127,17 +116,12 @@ func EncodeSegment(seg []byte) string {
// Deprecated: In a future release, we will demote this function to a non-exported function, since it // Deprecated: In a future release, we will demote this function to a non-exported function, since it
// should only be used internally // should only be used internally
func DecodeSegment(seg string) ([]byte, error) { func DecodeSegment(seg string) ([]byte, error) {
encoding := base64.RawURLEncoding
if DecodePaddingAllowed { if DecodePaddingAllowed {
if l := len(seg) % 4; l > 0 { if l := len(seg) % 4; l > 0 {
seg += strings.Repeat("=", 4-l) seg += strings.Repeat("=", 4-l)
} }
encoding = base64.URLEncoding return base64.URLEncoding.DecodeString(seg)
} }
if DecodeStrict { return base64.RawURLEncoding.DecodeString(seg)
encoding = encoding.Strict()
}
return encoding.DecodeString(seg)
} }

View File

@ -3,7 +3,7 @@ package jwt_test
import ( import (
"testing" "testing"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
func TestToken_SigningString(t1 *testing.T) { func TestToken_SigningString(t1 *testing.T) {

View File

@ -53,23 +53,9 @@ func (date NumericDate) MarshalJSON() (b []byte, err error) {
if TimePrecision < time.Second { if TimePrecision < time.Second {
prec = int(math.Log10(float64(time.Second) / float64(TimePrecision))) 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 return []byte(strconv.FormatFloat(f, 'f', prec, 64)), nil
// 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
} }
// UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a // UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a

View File

@ -2,11 +2,10 @@ package jwt_test
import ( import (
"encoding/json" "encoding/json"
"math"
"testing" "testing"
"time" "time"
"git.internal/re/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
func TestNumericDate(t *testing.T) { func TestNumericDate(t *testing.T) {
@ -41,6 +40,7 @@ func TestSingleArrayMarshal(t *testing.T) {
expected := `"test"` expected := `"test"`
b, err := json.Marshal(s) b, err := json.Marshal(s)
if err != nil { if err != nil {
t.Errorf("Unexpected error: %s", err) t.Errorf("Unexpected error: %s", err)
} }
@ -79,38 +79,23 @@ func TestNumericDate_MarshalJSON(t *testing.T) {
}{ }{
{time.Unix(5243700879, 0), "5243700879", time.Second}, {time.Unix(5243700879, 0), "5243700879", time.Second},
{time.Unix(5243700879, 0), "5243700879.000", time.Millisecond}, {time.Unix(5243700879, 0), "5243700879.000", time.Millisecond},
{time.Unix(5243700879, 0), "5243700879.000000", time.Microsecond}, {time.Unix(5243700879, 0), "5243700879.000001", time.Microsecond},
{time.Unix(5243700879, 0), "5243700879.000000000", time.Nanosecond}, {time.Unix(5243700879, 0), "5243700879.000000954", time.Nanosecond},
// //
{time.Unix(4239425898, 0), "4239425898", time.Second}, {time.Unix(4239425898, 0), "4239425898", time.Second},
{time.Unix(4239425898, 0), "4239425898.000", time.Millisecond}, {time.Unix(4239425898, 0), "4239425898.000", time.Millisecond},
{time.Unix(4239425898, 0), "4239425898.000000", time.Microsecond}, {time.Unix(4239425898, 0), "4239425898.000000", time.Microsecond},
{time.Unix(4239425898, 0), "4239425898.000000000", time.Nanosecond}, {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", time.Second},
{time.Unix(0, 1644285000210402000), "1644285000.210", time.Millisecond}, {time.Unix(0, 1644285000210402000), "1644285000.210", time.Millisecond},
{time.Unix(0, 1644285000210402000), "1644285000.210402", time.Microsecond}, {time.Unix(0, 1644285000210402000), "1644285000.210402", time.Microsecond},
{time.Unix(0, 1644285000210402000), "1644285000.210402000", time.Nanosecond}, {time.Unix(0, 1644285000210402000), "1644285000.210402012", time.Nanosecond},
// //
{time.Unix(0, 1644285315063096000), "1644285315", time.Second}, {time.Unix(0, 1644285315063096000), "1644285315", time.Second},
{time.Unix(0, 1644285315063096000), "1644285315.063", time.Millisecond}, {time.Unix(0, 1644285315063096000), "1644285315.063", time.Millisecond},
{time.Unix(0, 1644285315063096000), "1644285315.063096", time.Microsecond}, {time.Unix(0, 1644285315063096000), "1644285315.063096", time.Microsecond},
{time.Unix(0, 1644285315063096000), "1644285315.063096000", time.Nanosecond}, {time.Unix(0, 1644285315063096000), "1644285315.063096046", 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 { for i, tc := range tt {