⬆️ Upgraded JWX package

for secure and fast decoding access tokens
This commit is contained in:
Maxim Lebedev 2022-06-09 23:53:45 +05:00
parent dcf9e3c2ca
commit 531b14524c
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
195 changed files with 10853 additions and 8787 deletions

13
go.mod
View File

@ -4,11 +4,11 @@ go 1.18
require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/brianvoe/gofakeit/v6 v6.15.0
github.com/brianvoe/gofakeit/v6 v6.16.0
github.com/fasthttp/router v1.4.10
github.com/goccy/go-json v0.9.7
github.com/jmoiron/sqlx v1.3.5
github.com/lestrrat-go/jwx v1.2.25
github.com/lestrrat-go/jwx/v2 v2.0.2
github.com/spf13/viper v1.12.0
github.com/stretchr/testify v1.7.2
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
@ -21,7 +21,7 @@ require (
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
modernc.org/sqlite v1.17.3
source.toby3d.me/toby3d/form v0.3.0
source.toby3d.me/toby3d/middleware v0.9.1
source.toby3d.me/toby3d/middleware v0.9.2
willnorris.com/go/microformats v1.1.1
)
@ -36,9 +36,9 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/compress v1.15.6 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
@ -47,7 +47,6 @@ require (
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
github.com/philhofer/fwd v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d // indirect
@ -62,10 +61,10 @@ require (
go4.org/intern v0.0.0-20220301175310-a089fc204883 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/tools v0.1.11 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

28
go.sum
View File

@ -47,8 +47,8 @@ github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/brianvoe/gofakeit/v6 v6.15.0 h1:lJPGJZ2/07TRGDazyTzD5b18N3y4tmmJpdhCUw18FlI=
github.com/brianvoe/gofakeit/v6 v6.15.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/brianvoe/gofakeit/v6 v6.16.0 h1:EelCqtfArd8ppJ0z+TpOxXH8sVWNPBadPNdCDSMMw7k=
github.com/brianvoe/gofakeit/v6 v6.16.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@ -62,7 +62,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
@ -185,18 +184,16 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/httprc v1.0.1 h1:Cnc4NxIySph38pQPzKbjg5OkKsGR/Cf5xcWt5OlSUDI=
github.com/lestrrat-go/httprc v1.0.1/go.mod h1:5Ml+nB++j6IC0e6LzefJnrpMQDKgDwDCaIQQzhbqhJM=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA=
github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY=
github.com/lestrrat-go/jwx/v2 v2.0.2 h1:wkq9jwCkF3xrykISzn0Eksd7NEMOZ9yvCdnEpovIJX8=
github.com/lestrrat-go/jwx/v2 v2.0.2/go.mod h1:xV8+xRcrKbmnScV8adOzUuuTrL8aAZJoY4q2JAqIYU8=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -230,7 +227,6 @@ github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -342,8 +338,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -518,8 +514,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -685,7 +681,7 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
source.toby3d.me/toby3d/form v0.3.0 h1:kI8apdFeVr+koqTTGVoIRiR5NMqjrhCJlajYlu+1bVw=
source.toby3d.me/toby3d/form v0.3.0/go.mod h1:drlHMC+j/gb5zsttCSwx8qcYsbaRW+wFfE8bK6y+oeY=
source.toby3d.me/toby3d/middleware v0.9.1 h1:MjXDs57SbrIndEVFJHN36ZiO2OgnXNSajrLqa87vzZQ=
source.toby3d.me/toby3d/middleware v0.9.1/go.mod h1:Z67Z26wyW5USWnAYBYiA6mLubyzsHkbY6sWuIzs4304=
source.toby3d.me/toby3d/middleware v0.9.2 h1:HInjjZaN7GTqlWq32XscJs4Wf0taFG6OhyTAJrED1vA=
source.toby3d.me/toby3d/middleware v0.9.2/go.mod h1:MWedNnEpLCOk2rgjlfjpkn38t+1j53htSrp3lf6pC34=
willnorris.com/go/microformats v1.1.1 h1:h5tk2luq6KBIRcwMGdksxdeea4GGuWrRFie5460OAbo=
willnorris.com/go/microformats v1.1.1/go.mod h1:kvVnWrkkEscVAIITCEoiTX66Hcyg59C7q0E49mb9TJ0=

View File

@ -95,6 +95,7 @@ func initLookup() {
addWeightedLookup()
addMinecraftLookup()
addCelebrityLookup()
addDatabaseSQLLookup()
}
// NewMapParams will create a new MapParams

157
vendor/github.com/brianvoe/gofakeit/v6/sql.go generated vendored Normal file
View File

@ -0,0 +1,157 @@
package gofakeit
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"strings"
)
type SQLOptions struct {
Table string `json:"table" xml:"table"` // Table name we are inserting into
Count int `json:"count" xml:"count"` // How many entries (tuples) we're generating
Fields []Field `json:"fields" xml:"fields"` // The fields to be generated
}
func SQL(so *SQLOptions) (string, error) { return sqlFunc(globalFaker.Rand, so) }
func (f *Faker) SQL(so *SQLOptions) (string, error) { return sqlFunc(f.Rand, so) }
func sqlFunc(r *rand.Rand, so *SQLOptions) (string, error) {
if so.Table == "" {
return "", errors.New("must provide table name to generate SQL")
}
if so.Fields == nil || len(so.Fields) <= 0 {
return "", errors.New(("must pass fields in order to generate SQL queries"))
}
if so.Count <= 0 {
return "", errors.New("must have entry count")
}
var sb strings.Builder
sb.WriteString("INSERT INTO " + so.Table + " ")
// Loop through each field and put together column names
var cols []string
for _, f := range so.Fields {
cols = append(cols, f.Name)
}
sb.WriteString("(" + strings.Join(cols, ", ") + ")")
sb.WriteString(" VALUES ")
for i := 0; i < so.Count; i++ {
// Start opening value
sb.WriteString("(")
// Now, we need to add all of our fields
var endStr string
for ii, field := range so.Fields {
// Set end of value string
endStr = ", "
if ii == len(so.Fields)-1 {
endStr = ""
}
// If autoincrement, add based upon loop
if field.Function == "autoincrement" {
sb.WriteString(fmt.Sprintf("%d%s", i+1, endStr))
continue
}
// Get the function info for the field
funcInfo := GetFuncLookup(field.Function)
if funcInfo == nil {
return "", errors.New("invalid function, " + field.Function + " does not exist")
}
// Generate the value
val, err := funcInfo.Generate(r, &field.Params, funcInfo)
if err != nil {
return "", err
}
// Convert the output value to the proper SQL type
convertType := sqlConvertType(funcInfo.Output, val)
// If its the last field, we need to close the value
sb.WriteString(convertType + endStr)
}
// If its the last value, we need to close the value
if i == so.Count-1 {
sb.WriteString(");")
} else {
sb.WriteString("),")
}
}
return sb.String(), nil
}
// sqlConvertType will take in a type and value and convert it to the proper SQL type
func sqlConvertType(t string, val interface{}) string {
switch t {
case "string":
return `'` + fmt.Sprintf("%v", val) + `'`
case "[]byte":
return `'` + fmt.Sprintf("%s", val) + `'`
default:
return fmt.Sprintf("%v", val)
}
}
func addDatabaseSQLLookup() {
AddFuncLookup("sql", Info{
Display: "SQL",
Category: "database",
Description: "Generates an object or an array of objects in json format",
Example: `INSERT INTO people
(id, first_name, price, age, created_at)
VALUES
(1, 'Markus', 804.92, 21, '1937-01-30 07:58:01'),
(2, 'Santino', 235.13, 40, '1964-07-07 22:25:40');`,
Output: "string",
ContentType: "application/sql",
Params: []Param{
{Field: "table", Display: "Table", Type: "string", Description: "Name of the table to insert into"},
{Field: "count", Display: "Count", Type: "int", Default: "100", Description: "Number of inserts to generate"},
{Field: "fields", Display: "Fields", Type: "[]Field", Description: "Fields containing key name and function to run in json format"},
},
Generate: func(r *rand.Rand, m *MapParams, info *Info) (interface{}, error) {
so := SQLOptions{}
table, err := info.GetString(m, "table")
if err != nil {
return nil, err
}
so.Table = table
count, err := info.GetInt(m, "count")
if err != nil {
return nil, err
}
so.Count = count
fieldsStr, err := info.GetStringArray(m, "fields")
if err != nil {
return nil, err
}
// Check to make sure fields has length
if len(fieldsStr) > 0 {
so.Fields = make([]Field, len(fieldsStr))
for i, f := range fieldsStr {
// Unmarshal fields string into fields array
err = json.Unmarshal([]byte(f), &so.Fields[i])
if err != nil {
return nil, err
}
}
}
return sqlFunc(r, &so)
},
})
}

View File

@ -1,7 +0,0 @@
issues:
exclude-rules:
- path: /*_example_test.go
linters:
- errcheck
- forbidigo

View File

@ -1,8 +0,0 @@
v2.0.8 - 28 Feb 2021
* Fix possible goroutine leak (#30)
v2.0.7 - 26 Jan 2021
* Cosmetic go.mod / go.sum changes
v2.0.6 - 25 Jan 2021
* Add jitter to constant backoff

View File

@ -1,183 +0,0 @@
# backoff ![](https://github.com/lestrrat-go/backoff/workflows/CI/badge.svg) [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/backoff/v2.svg)](https://pkg.go.dev/github.com/lestrrat-go/backoff/v2)
Idiomatic backoff for Go
This library is an implementation of backoff algorithm for retrying operations
in an idiomatic Go way. It respects `context.Context` natively, and the critical
notifications are done through *channel operations*, allowing you to write code
that is both more explicit and flexibile.
For a longer discussion, [please read this article](https://medium.com/@lestrrat/yak-shaving-with-backoff-libraries-in-go-80240f0aa30c)
# IMPORT
```go
import "github.com/lestrrat-go/backoff/v2"
```
# SYNOPSIS
```go
func ExampleExponential() {
p := backoff.Exponential(
backoff.WithMinInterval(time.Second),
backoff.WithMaxInterval(time.Minute),
backoff.WithJitterFactor(0.05),
)
flakyFunc := func(a int) (int, error) {
// silly function that only succeeds if the current call count is
// divisible by either 3 or 5 but not both
switch {
case a%15 == 0:
return 0, errors.New(`invalid`)
case a%3 == 0 || a%5 == 0:
return a, nil
}
return 0, errors.New(`invalid`)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
retryFunc := func(v int) (int, error) {
b := p.Start(ctx)
for backoff.Continue(b) {
x, err := flakyFunc(v)
if err == nil {
return x, nil
}
}
return 0, errors.New(`failed to get value`)
}
retryFunc(15)
}
```
# POLICIES
Policy objects describe a backoff policy, and are factories to create backoff Controller objects.
Controller objects does the actual coordination.
Create a new controller for each invocation of a backoff enabled operation.
This way the controller object is protected from concurrent access (if you have one) and can easily be discarded
## Null
A null policy means there's no backoff.
For example, if you were to support both using and not using a backoff in your code you can say
```go
var p backoff.Policy
if useBackoff {
p = backoff.Exponential(...)
} else {
p = backoff.Null()
}
c := p.Start(ctx)
for backoff.Continue(c) {
if err := doSomething(); err != nil {
continue
}
return
}
```
Instead of
```go
if useBackoff {
p := backoff.Exponential(...)
c := p.Start(ctx)
for backoff.Continue(c) {
if err := doSomething(); err != nil {
continue
}
return
}
} else {
if err := doSomething(); err != nil {
continue
}
}
```
## Constant
A constant policy implements are backoff where the intervals are always the same
## Exponential
This is the most "common" of the backoffs. Intervals between calls are spaced out such that as you keep retrying, the intervals keep increasing.
# FAQ
## I'm getting "package github.com/lestrrat-go/backoff/v2: no Go files in /go/src/github.com/lestrrat-go/backoff/v2"
You are using Go in GOPATH mode, which was the way before [Go Modules](https://blog.golang.org/using-go-modules) were introduced in Go 1.11 (Aug 2018).
GOPATH has slowly been phased out, and in Go 1.14 onwards, Go Modules pretty much Just Work.
Go 1.16 introduced more visible changes that forces users to be aware of the existance of go.mod files.
The short answer when you you get the above error is: **Migrate to using Go Modules**.
This is simple: All you need to do is to include a go.mod (and go.sum) file to your library or app.
For example, if you have previously been doing this:
```
git clone git@github.com:myusername/myawesomeproject.git
cd myawesomeproject
go get ./...
```
First include go.mod and go.sum in your repository:
```
git clone git@github.com:myusername/myawesomeproject.git
cd myawesomeproject
go mod init
go mod tidy
git add go.mod go.sum
git commit -m "Add go.mod and go.sum" -a
git push
```
Then from subsequent calls:
```
git clone git@github.com:myusername/myawesomeproject.git
cd myawesomeproject
go build # or go test, or go run, or whatever.
```
This will tell go to respect tags, and will automatically pick up the latest version of github.com/lestrrat-go/backoff
If you really can't do this, then the quick and dirty workaround is to just copy the files over to /v2 directory of this library
```
BACKOFF=github.com/lestrrat-go/backoff
go get github.com/lestrrat-go/backoff
if [[ if ! -d "$GOPATH/src/$BACKOFF/v2" ]]; then
mkdir "$GOPATH/src/$BACKOFF/v2" # just to make sure it exists
fi
cp "$GOPATH/src/$BACKOFF/*.go" "$GOPATH/src/$BACKOFF/v2"
git clone git@github.com:myusername/myawesomeproject.git
cd myawesomeproject
go get ./...
```
## Why won't you just add the files in /v2?
Because it's hard to maintain multiple sources of truth. Sorry, I don't get paid to do this.
I will not hold anything against you if you decided to fork this to your repository, and move files to your own /v2 directory.
Then, if you have a go.mod in your app, you can just do
```
go mod edit -replace github.com/lestrrat-go/backoff/v2=github.com/myusername/myawesomemfork/v2
```
Oh, wait, then you already have go.mod, so this is a non-issue.
...Yeah, just migrate to using go modules, please?

View File

@ -1,31 +0,0 @@
package backoff
// Null creates a new NullPolicy object
func Null() Policy {
return NewNull()
}
// Constant creates a new ConstantPolicy object
func Constant(options ...Option) Policy {
return NewConstantPolicy(options...)
}
// Constant creates a new ExponentialPolicy object
func Exponential(options ...ExponentialOption) Policy {
return NewExponentialPolicy(options...)
}
// Continue is a convenience function to check when we can fire
// the next invocation of the desired backoff code
//
// for backoff.Continue(c) {
// ... your code ...
// }
func Continue(c Controller) bool {
select {
case <-c.Done():
return false
case _, ok := <-c.Next():
return ok
}
}

View File

@ -1,66 +0,0 @@
package backoff
import (
"context"
"time"
)
type ConstantInterval struct {
interval time.Duration
jitter jitter
}
func NewConstantInterval(options ...ConstantOption) *ConstantInterval {
jitterFactor := 0.0
interval := time.Minute
var rng Random
for _, option := range options {
switch option.Ident() {
case identInterval{}:
interval = option.Value().(time.Duration)
case identJitterFactor{}:
jitterFactor = option.Value().(float64)
case identRNG{}:
rng = option.Value().(Random)
}
}
return &ConstantInterval{
interval: interval,
jitter: newJitter(jitterFactor, rng),
}
}
func (g *ConstantInterval) Next() time.Duration {
return time.Duration(g.jitter.apply(float64(g.interval)))
}
type ConstantPolicy struct {
cOptions []ControllerOption
igOptions []ConstantOption
}
func NewConstantPolicy(options ...Option) *ConstantPolicy {
var cOptions []ControllerOption
var igOptions []ConstantOption
for _, option := range options {
switch opt := option.(type) {
case ControllerOption:
cOptions = append(cOptions, opt)
default:
igOptions = append(igOptions, opt.(ConstantOption))
}
}
return &ConstantPolicy{
cOptions: cOptions,
igOptions: igOptions,
}
}
func (p *ConstantPolicy) Start(ctx context.Context) Controller {
ig := NewConstantInterval(p.igOptions...)
return newController(ctx, ig, p.cOptions...)
}

View File

@ -1,99 +0,0 @@
package backoff
import (
"context"
"sync"
"time"
)
type controller struct {
ctx context.Context
cancel func()
ig IntervalGenerator
maxRetries int
mu *sync.RWMutex
next chan struct{} // user-facing channel
resetTimer chan time.Duration
retries int
timer *time.Timer
}
func newController(ctx context.Context, ig IntervalGenerator, options ...ControllerOption) *controller {
cctx, cancel := context.WithCancel(ctx) // DO NOT fire this cancel here
maxRetries := 10
for _, option := range options {
switch option.Ident() {
case identMaxRetries{}:
maxRetries = option.Value().(int)
}
}
c := &controller{
cancel: cancel,
ctx: cctx,
ig: ig,
maxRetries: maxRetries,
mu: &sync.RWMutex{},
next: make(chan struct{}, 1),
resetTimer: make(chan time.Duration, 1),
timer: time.NewTimer(ig.Next()),
}
// enqueue a single fake event so the user gets to retry once
c.next <- struct{}{}
go c.loop()
return c
}
func (c *controller) loop() {
for {
select {
case <-c.ctx.Done():
return
case d := <-c.resetTimer:
if !c.timer.Stop() {
select {
case <-c.timer.C:
default:
}
}
c.timer.Reset(d)
case <-c.timer.C:
select {
case <-c.ctx.Done():
return
case c.next <- struct{}{}:
}
if c.maxRetries > 0 {
c.retries++
}
if !c.check() {
c.cancel()
return
}
c.resetTimer <- c.ig.Next()
}
}
}
func (c *controller) check() bool {
if c.maxRetries > 0 && c.retries >= c.maxRetries {
return false
}
return true
}
func (c *controller) Done() <-chan struct{} {
c.mu.RLock()
defer c.mu.RUnlock()
return c.ctx.Done()
}
func (c *controller) Next() <-chan struct{} {
c.mu.RLock()
defer c.mu.RUnlock()
return c.next
}

View File

@ -1,6 +0,0 @@
// Package backoff implments backoff algorithms for retrying operations.
//
// Users first create an appropriate `Policy` object, and when the operation
// that needs retrying is about to start, they kick the actual backoff
//
package backoff

View File

@ -1,107 +0,0 @@
package backoff
import (
"context"
"time"
)
type ExponentialInterval struct {
current float64
maxInterval float64
minInterval float64
multiplier float64
jitter jitter
}
const (
defaultMaxInterval = float64(time.Minute)
defaultMinInterval = float64(500 * time.Millisecond)
defaultMultiplier = 1.5
)
func NewExponentialInterval(options ...ExponentialOption) *ExponentialInterval {
jitterFactor := 0.0
maxInterval := defaultMaxInterval
minInterval := defaultMinInterval
multiplier := defaultMultiplier
var rng Random
for _, option := range options {
switch option.Ident() {
case identJitterFactor{}:
jitterFactor = option.Value().(float64)
case identMaxInterval{}:
maxInterval = float64(option.Value().(time.Duration))
case identMinInterval{}:
minInterval = float64(option.Value().(time.Duration))
case identMultiplier{}:
multiplier = option.Value().(float64)
case identRNG{}:
rng = option.Value().(Random)
}
}
if minInterval > maxInterval {
minInterval = maxInterval
}
if multiplier <= 1 {
multiplier = defaultMultiplier
}
return &ExponentialInterval{
maxInterval: maxInterval,
minInterval: minInterval,
multiplier: multiplier,
jitter: newJitter(jitterFactor, rng),
}
}
func (g *ExponentialInterval) Next() time.Duration {
var next float64
if g.current == 0 {
next = g.minInterval
} else {
next = g.current * g.multiplier
}
if next > g.maxInterval {
next = g.maxInterval
}
if next < g.minInterval {
next = g.minInterval
}
// Apply jitter *AFTER* we calculate the base interval
next = g.jitter.apply(next)
g.current = next
return time.Duration(next)
}
type ExponentialPolicy struct {
cOptions []ControllerOption
igOptions []ExponentialOption
}
func NewExponentialPolicy(options ...ExponentialOption) *ExponentialPolicy {
var cOptions []ControllerOption
var igOptions []ExponentialOption
for _, option := range options {
switch opt := option.(type) {
case ControllerOption:
cOptions = append(cOptions, opt)
default:
igOptions = append(igOptions, opt)
}
}
return &ExponentialPolicy{
cOptions: cOptions,
igOptions: igOptions,
}
}
func (p *ExponentialPolicy) Start(ctx context.Context) Controller {
ig := NewExponentialInterval(p.igOptions...)
return newController(ctx, ig, p.cOptions...)
}

View File

@ -1,30 +0,0 @@
package backoff
import (
"context"
"time"
"github.com/lestrrat-go/option"
)
type Option = option.Interface
type Controller interface {
Done() <-chan struct{}
Next() <-chan struct{}
}
type IntervalGenerator interface {
Next() time.Duration
}
// Policy is an interface for the backoff policies that this package
// implements. Users must create a controller object from this
// policy to actually do anything with it
type Policy interface {
Start(context.Context) Controller
}
type Random interface {
Float64() float64
}

View File

@ -1,59 +0,0 @@
package backoff
import (
"math/rand"
"time"
)
type jitter interface {
apply(interval float64) float64
}
func newJitter(jitterFactor float64, rng Random) jitter {
if jitterFactor <= 0 || jitterFactor >= 1 {
return newNopJitter()
}
return newRandomJitter(jitterFactor, rng)
}
type nopJitter struct{}
func newNopJitter() *nopJitter {
return &nopJitter{}
}
func (j *nopJitter) apply(interval float64) float64 {
return interval
}
type randomJitter struct {
jitterFactor float64
rng Random
}
func newRandomJitter(jitterFactor float64, rng Random) *randomJitter {
if rng == nil {
// if we have a jitter factor, and no RNG is provided, create one.
// This is definitely not "secure", but well, if you care enough,
// you would provide one
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
}
return &randomJitter{
jitterFactor: jitterFactor,
rng: rng,
}
}
func (j *randomJitter) apply(interval float64) float64 {
jitterDelta := interval * j.jitterFactor
jitterMin := interval - jitterDelta
jitterMax := interval + jitterDelta
// Get a random value from the range [minInterval, maxInterval].
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
// we want a 33% chance for selecting either 1, 2 or 3.
//
// see also: https://github.com/cenkalti/backoff/blob/c2975ffa541a1caeca5f76c396cb8c3e7b3bb5f8/exponential.go#L154-L157
return jitterMin + j.rng.Float64()*(jitterMax-jitterMin+1)
}

View File

@ -1,51 +0,0 @@
package backoff
import (
"context"
"sync"
)
// NullPolicy does not do any backoff. It allows the caller
// to execute the desired code once, and no more
type NullPolicy struct{}
func NewNull() *NullPolicy {
return &NullPolicy{}
}
func (p *NullPolicy) Start(ctx context.Context) Controller {
return newNullController(ctx)
}
type nullController struct {
mu *sync.RWMutex
ctx context.Context
next chan struct{}
}
func newNullController(ctx context.Context) *nullController {
cctx, cancel := context.WithCancel(ctx)
c := &nullController{
mu: &sync.RWMutex{},
ctx: cctx,
next: make(chan struct{}), // NO BUFFER
}
go func(ch chan struct{}, cancel func()) {
ch <- struct{}{}
close(ch)
cancel()
}(c.next, cancel)
return c
}
func (c *nullController) Done() <-chan struct{} {
c.mu.RLock()
defer c.mu.RUnlock()
return c.ctx.Done()
}
func (c *nullController) Next() <-chan struct{} {
c.mu.RLock()
defer c.mu.RUnlock()
return c.next
}

View File

@ -1,127 +0,0 @@
package backoff
import (
"time"
"github.com/lestrrat-go/option"
)
type identInterval struct{}
type identJitterFactor struct{}
type identMaxInterval struct{}
type identMaxRetries struct{}
type identMinInterval struct{}
type identMultiplier struct{}
type identRNG struct{}
// ControllerOption is an option that may be passed to Policy objects,
// but are ultimately passed down to the Controller objects.
// (Normally you do not have to care about the distinction)
type ControllerOption interface {
ConstantOption
ExponentialOption
CommonOption
controllerOption()
}
type controllerOption struct {
Option
}
func (*controllerOption) exponentialOption() {}
func (*controllerOption) controllerOption() {}
func (*controllerOption) constantOption() {}
// ConstantOption is an option that is used by the Constant policy.
type ConstantOption interface {
Option
constantOption()
}
type constantOption struct {
Option
}
func (*constantOption) constantOption() {}
// ExponentialOption is an option that is used by the Exponential policy.
type ExponentialOption interface {
Option
exponentialOption()
}
type exponentialOption struct {
Option
}
func (*exponentialOption) exponentialOption() {}
// CommonOption is an option that can be passed to any of the backoff policies.
type CommonOption interface {
ExponentialOption
ConstantOption
}
type commonOption struct {
Option
}
func (*commonOption) constantOption() {}
func (*commonOption) exponentialOption() {}
// WithMaxRetries specifies the maximum number of attempts that can be made
// by the backoff policies. By default each policy tries up to 10 times.
//
// If you would like to retry forever, specify "0" and pass to the constructor
// of each policy.
//
// This option can be passed to all policy constructors except for NullPolicy
func WithMaxRetries(v int) ControllerOption {
return &controllerOption{option.New(identMaxRetries{}, v)}
}
// WithInterval specifies the constant interval used in ConstantPolicy and
// ConstantInterval.
// The default value is 1 minute.
func WithInterval(v time.Duration) ConstantOption {
return &constantOption{option.New(identInterval{}, v)}
}
// WithMaxInterval specifies the maximum duration used in exponential backoff
// The default value is 1 minute.
func WithMaxInterval(v time.Duration) ExponentialOption {
return &exponentialOption{option.New(identMaxInterval{}, v)}
}
// WithMinInterval specifies the minimum duration used in exponential backoff.
// The default value is 500ms.
func WithMinInterval(v time.Duration) ExponentialOption {
return &exponentialOption{option.New(identMinInterval{}, v)}
}
// WithMultiplier specifies the factor in which the backoff intervals are
// increased. By default this value is set to 1.5, which means that for
// every iteration a 50% increase in the interval for every iteration
// (up to the value specified by WithMaxInterval). this value must be greater
// than 1.0. If the value is less than equal to 1.0, the default value
// of 1.5 is used.
func WithMultiplier(v float64) ExponentialOption {
return &exponentialOption{option.New(identMultiplier{}, v)}
}
// WithJitterFactor enables some randomness (jittering) in the computation of
// the backoff intervals. This value must be between 0.0 < v < 1.0. If a
// value outside of this range is specified, the value will be silently
// ignored and jittering is disabled.
//
// This option can be passed to ExponentialPolicy or ConstantPolicy constructor
func WithJitterFactor(v float64) CommonOption {
return &commonOption{option.New(identJitterFactor{}, v)}
}
// WithRNG specifies the random number generator used for jittering.
// If not provided one will be created, but if you want a truly random
// jittering, make sure to provide one that you explicitly initialized
func WithRNG(v Random) CommonOption {
return &commonOption{option.New(identRNG{}, v)}
}

View File

@ -1,14 +1,15 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# Dependency directories (remove the comment below to include it)
# vendor/

82
vendor/github.com/lestrrat-go/httprc/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,82 @@
run:
linters-settings:
govet:
enable-all: true
disable:
- shadow
- fieldalignment
linters:
enable-all: true
disable:
- cyclop
- dupl
- exhaustive
- exhaustivestruct
- errorlint
- funlen
- gci
- gochecknoglobals
- gochecknoinits
- gocognit
- gocritic
- gocyclo
- godot
- godox
- goerr113
- gofumpt
- golint #deprecated
- gomnd
- gosec
- govet
- interfacer # deprecated
- ifshort
- lll
- maligned # deprecated
- makezero
- nakedret
- nestif
- nlreturn
- paralleltest
- scopelint # deprecated
- tagliatelle
- testpackage
- thelper
- wrapcheck
- wsl
issues:
exclude-rules:
# not needed
- path: /*.go
text: "ST1003: should not use underscores in package names"
linters:
- stylecheck
- path: /*.go
text: "don't use an underscore in package name"
linters:
- revive
- path: /main.go
linters:
- errcheck
- path: internal/codegen/codegen.go
linters:
- errcheck
- path: /*_test.go
linters:
- errcheck
- forcetypeassert
- path: /*_example_test.go
linters:
- forbidigo
- path: cmd/jwx/jwx.go
linters:
- forbidigo
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-issues-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0

8
vendor/github.com/lestrrat-go/httprc/Changes generated vendored Normal file
View File

@ -0,0 +1,8 @@
Changes
=======
v1.0.1 29 Mar 2022
* Bump dependency for github.com/lestrrat-go/httpcc to v1.0.1
v1.0.0 29 Mar 2022
* Initial release, refactored out of github.com/lestrrat-go/jwx

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018 lestrrat
Copyright (c) 2022 lestrrat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

130
vendor/github.com/lestrrat-go/httprc/README.md generated vendored Normal file
View File

@ -0,0 +1,130 @@
# httprc
`httprc` is a HTTP "Refresh" Cache. Its aim is to cache a remote resource that
can be fetched via HTTP, but keep the cached content up-to-date based on periodic
refreshing.
# SYNOPSIS
<!-- INCLUDE(httprc_example_test.go) -->
```go
package httprc_test
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"sync"
"time"
"github.com/lestrrat-go/httprc"
)
const (
helloWorld = `Hello World!`
goodbyeWorld = `Goodbye World!`
)
func ExampleCache() {
var mu sync.RWMutex
msg := helloWorld
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(`Cache-Control`, fmt.Sprintf(`max-age=%d`, 2))
w.WriteHeader(http.StatusOK)
mu.RLock()
fmt.Fprint(w, msg)
mu.RUnlock()
}))
defer srv.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
errSink := httprc.ErrSinkFunc(func(err error) {
fmt.Printf("%s\n", err)
})
c := httprc.NewCache(ctx,
httprc.WithErrSink(errSink),
httprc.WithRefreshWindow(time.Second), // force checks every second
)
c.Register(srv.URL,
httprc.WithHTTPClient(srv.Client()), // we need client with TLS settings
httprc.WithMinRefreshInterval(time.Second), // allow max-age=1 (smallest)
)
payload, err := c.Get(ctx, srv.URL)
if err != nil {
fmt.Printf("%s\n", err)
return
}
if string(payload.([]byte)) != helloWorld {
fmt.Printf("payload mismatch: %s\n", payload)
return
}
mu.Lock()
msg = goodbyeWorld
mu.Unlock()
time.Sleep(4 * time.Second)
payload, err = c.Get(ctx, srv.URL)
if err != nil {
fmt.Printf("%s\n", err)
return
}
if string(payload.([]byte)) != goodbyeWorld {
fmt.Printf("payload mismatch: %s\n", payload)
return
}
cancel()
// OUTPUT:
}
```
source: [httprc_example_test.go](https://github.com/lestrrat-go/jwx/blob/main/httprc_example_test.go)
<!-- END INCLUDE -->
# Sequence Diagram
```mermaid
sequenceDiagram
autonumber
actor User
participant httprc.Cache
participant httprc.Storage
User->>httprc.Cache: Fetch URL `u`
activate httprc.Storage
httprc.Cache->>httprc.Storage: Fetch local cache for `u`
alt Cache exists
httprc.Storage-->httprc.Cache: Return local cache
httprc.Cache-->>User: Return data
Note over httprc.Storage: If the cache exists, there's nothing more to do.<br />The cached content will be updated periodically in httprc.Refresher
deactivate httprc.Storage
else Cache does not exist
activate httprc.Fetcher
httprc.Cache->>httprc.Fetcher: Fetch remote resource `u`
httprc.Fetcher-->>httprc.Cache: Return fetched data
deactivate httprc.Fetcher
httprc.Cache-->>User: Return data
httprc.Cache-)httprc.Refresher: Enqueue into auto-refresh queue
activate httprc.Refresher
loop Refresh Loop
Note over httprc.Storage,httprc.Fetcher: Cached contents are updated synchronously
httprc.Refresher->>httprc.Refresher: Wait until next refresh
httprc.Refresher-->>httprc.Fetcher: Request fetch
httprc.Fetcher->>httprc.Refresher: Return fetched data
httprc.Refresher-->>httprc.Storage: Store new version in cache
httprc.Refresher->>httprc.Refresher: Enqueue into auto-refresh queue (again)
end
deactivate httprc.Refresher
end
```

171
vendor/github.com/lestrrat-go/httprc/cache.go generated vendored Normal file
View File

@ -0,0 +1,171 @@
package httprc
import (
"context"
"fmt"
"net/http"
"sync"
"time"
)
// ErrSink is an abstraction that allows users to consume errors
// produced while the cache queue is running.
type HTTPClient interface {
Get(string) (*http.Response, error)
}
// Cache represents a cache that stores resources locally, while
// periodically refreshing the contents based on HTTP header values
// and/or user-supplied hints.
//
// Refresh is performed _periodically_, and therefore the contents
// are not kept up-to-date in real time. The interval between checks
// for refreshes is called the refresh window.
//
// The default refresh window is 15 minutes. This means that if a
// resource is fetched is at time T, and it is supposed to be
// refreshed in 20 minutes, the next refresh for this resource will
// happen at T+30 minutes (15+15 minutes).
type Cache struct {
mu sync.RWMutex
queue *queue
wl Whitelist
}
const defaultRefreshWindow = 15 * time.Minute
// New creates a new Cache object.
//
// The context object in the argument controls the life-cycle of the
// auto-refresh worker. If you cancel the `ctx`, then the automatic
// refresh will stop working.
//
// Refresh will only be performed periodically where the interval between
// refreshes are controlled by the `refresh window` variable. For example,
// if the refresh window is every 5 minutes and the resource was queued
// to be refreshed at 7 minutes, the resource will be refreshed after 10
// minutes (in 2 refresh window time).
//
// The refresh window can be configured by using `httprc.WithRefreshWindow`
// option. If you want refreshes to be performed more often, provide a smaller
// refresh window. If you specify a refresh window that is smaller than 1
// second, it will automatically be set to the default value, which is 15
// minutes.
//
// Internally the HTTP fetching is done using a pool of HTTP fetch
// workers. The default number of workers is 3. You may change this
// number by specifying the `httprc.WithFetcherWorkerCount`
func NewCache(ctx context.Context, options ...CacheOption) *Cache {
var refreshWindow time.Duration
var errSink ErrSink
var wl Whitelist
var fetcherOptions []FetcherOption
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identRefreshWindow{}:
refreshWindow = option.Value().(time.Duration)
case identFetcherWorkerCount{}, identWhitelist{}:
fetcherOptions = append(fetcherOptions, option)
case identErrSink{}:
errSink = option.Value().(ErrSink)
}
}
if refreshWindow < time.Second {
refreshWindow = defaultRefreshWindow
}
fetch := NewFetcher(ctx, fetcherOptions...)
queue := newQueue(ctx, refreshWindow, fetch, errSink)
return &Cache{
queue: queue,
wl: wl,
}
}
// Register configures a URL to be stored in the cache.
//
// For any given URL, the URL must be registered _BEFORE_ it is
// accessed using `Get()` method.
func (c *Cache) Register(u string, options ...RegisterOption) error {
c.mu.Lock()
defer c.mu.Unlock()
if wl := c.wl; wl != nil {
if !wl.IsAllowed(u) {
return fmt.Errorf(`httprc.Cache: url %q has been rejected by whitelist`, u)
}
}
return c.queue.Register(u, options...)
}
// Unregister removes the given URL `u` from the cache.
//
// Subsequent calls to `Get()` will fail until `u` is registered again.
func (c *Cache) Unregister(u string) error {
c.mu.Lock()
defer c.mu.Unlock()
return c.queue.Unregister(u)
}
// IsRegistered returns true if the given URL `u` has already been
// registered in the cache.
func (c *Cache) IsRegistered(u string) bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.queue.IsRegistered(u)
}
// Refresh is identical to Get(), except it always fetches the
// specified resource anew, and updates the cached content
func (c *Cache) Refresh(ctx context.Context, u string) (interface{}, error) {
return c.getOrFetch(ctx, u, true)
}
// Get returns the cached object.
//
// The context.Context argument is used to control the timeout for
// synchronous fetches, when they need to happen. Synchronous fetches
// will be performed when the cache does not contain the specified
// resource.
func (c *Cache) Get(ctx context.Context, u string) (interface{}, error) {
return c.getOrFetch(ctx, u, false)
}
func (c *Cache) getOrFetch(ctx context.Context, u string, forceRefresh bool) (interface{}, error) {
c.mu.RLock()
e, ok := c.queue.getRegistered(u)
if !ok {
c.mu.RUnlock()
return nil, fmt.Errorf(`url %q is not registered (did you make sure to call Register() first?)`, u)
}
c.mu.RUnlock()
// Only one goroutine may enter this section.
e.acquireSem()
// has this entry been fetched? (but ignore and do a fetch
// if forceRefresh is true)
if forceRefresh || !e.hasBeenFetched() {
if err := c.queue.fetchAndStore(ctx, e); err != nil {
return nil, fmt.Errorf(`failed to fetch %q: %w`, u, err)
}
}
e.releaseSem()
e.mu.RLock()
data := e.data
e.mu.RUnlock()
return data, nil
}
func (c *Cache) Snapshot() *Snapshot {
c.mu.RLock()
defer c.mu.RUnlock()
return c.queue.snapshot()
}

182
vendor/github.com/lestrrat-go/httprc/fetcher.go generated vendored Normal file
View File

@ -0,0 +1,182 @@
package httprc
import (
"context"
"fmt"
"net/http"
"sync"
)
type fetchRequest struct {
mu sync.RWMutex
// client contains the HTTP Client that can be used to make a
// request. By setting a custom *http.Client, you can for example
// provide a custom http.Transport
//
// If not specified, http.DefaultClient will be used.
client HTTPClient
wl Whitelist
// u contains the URL to be fetched
url string
// reply is a field that is only used by the internals of the fetcher
// it is used to return the result of fetching
reply chan *fetchResult
}
type fetchResult struct {
mu sync.RWMutex
res *http.Response
err error
}
func (fr *fetchResult) reply(ctx context.Context, reply chan *fetchResult) error {
select {
case <-ctx.Done():
return ctx.Err()
case reply <- fr:
}
close(reply)
return nil
}
type fetcher struct {
requests chan *fetchRequest
}
type Fetcher interface {
Fetch(context.Context, string, ...FetchOption) (*http.Response, error)
fetch(context.Context, *fetchRequest) (*http.Response, error)
}
func NewFetcher(ctx context.Context, options ...FetcherOption) Fetcher {
var nworkers int
var wl Whitelist
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identFetcherWorkerCount{}:
nworkers = option.Value().(int)
case identWhitelist{}:
wl = option.Value().(Whitelist)
}
}
if nworkers < 1 {
nworkers = 3
}
incoming := make(chan *fetchRequest)
for i := 0; i < nworkers; i++ {
go runFetchWorker(ctx, incoming, wl)
}
return &fetcher{
requests: incoming,
}
}
func (f *fetcher) Fetch(ctx context.Context, u string, options ...FetchOption) (*http.Response, error) {
var client HTTPClient
var wl Whitelist
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identHTTPClient{}:
client = option.Value().(HTTPClient)
case identWhitelist{}:
wl = option.Value().(Whitelist)
}
}
req := fetchRequest{
client: client,
url: u,
wl: wl,
}
return f.fetch(ctx, &req)
}
// fetch (unexported) is the main fetching implemntation.
// it allows the caller to reuse the same *fetchRequest object
func (f *fetcher) fetch(ctx context.Context, req *fetchRequest) (*http.Response, error) {
reply := make(chan *fetchResult, 1)
req.mu.Lock()
req.reply = reply
req.mu.Unlock()
// Send a request to the backend
select {
case <-ctx.Done():
return nil, ctx.Err()
case f.requests <- req:
}
// wait until we get a reply
select {
case <-ctx.Done():
return nil, ctx.Err()
case fr := <-reply:
fr.mu.RLock()
res := fr.res
err := fr.err
fr.mu.RUnlock()
return res, err
}
}
func runFetchWorker(ctx context.Context, incoming chan *fetchRequest, wl Whitelist) {
LOOP:
for {
select {
case <-ctx.Done():
break LOOP
case req := <-incoming:
req.mu.RLock()
reply := req.reply
client := req.client
if client == nil {
client = http.DefaultClient
}
url := req.url
reqwl := req.wl
req.mu.RUnlock()
var wls []Whitelist
for _, v := range []Whitelist{wl, reqwl} {
if v != nil {
wls = append(wls, v)
}
}
if len(wls) > 0 {
for _, wl := range wls {
if !wl.IsAllowed(url) {
r := &fetchResult{
err: fmt.Errorf(`fetching url %q rejected by whitelist`, url),
}
if err := r.reply(ctx, reply); err != nil {
break LOOP
}
continue LOOP
}
}
}
// The body is handled by the consumer of the fetcher
//nolint:bodyclose
res, err := client.Get(url)
r := &fetchResult{
res: res,
err: err,
}
if err := r.reply(ctx, reply); err != nil {
break LOOP
}
}
}
}

22
vendor/github.com/lestrrat-go/httprc/httprc.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
//go:generate tools/genoptions.sh
// Package httprc implements a cache for resources available
// over http(s). Its aim is not only to cache these resources so
// that it saves on HTTP roundtrips, but it also periodically
// attempts to auto-refresh these resources once they are cached
// based on the user-specified intervals and HTTP `Expires` and
// `Cache-Control` headers, thus keeping the entries _relatively_ fresh.
package httprc
import "fmt"
// RefreshError is the underlying error type that is sent to
// the `httprc.ErrSink` objects
type RefreshError struct {
URL string
Err error
}
func (re *RefreshError) Error() string {
return fmt.Sprintf(`refresh error (%q): %s`, re.URL, re.Err)
}

119
vendor/github.com/lestrrat-go/httprc/options.yaml generated vendored Normal file
View File

@ -0,0 +1,119 @@
package_name: httprc
output: options_gen.go
interfaces:
- name: RegisterOption
comment: |
RegisterOption desribes options that can be passed to `(httprc.Cache).Register()`
- name: CacheOption
comment: |
CacheOption desribes options that can be passed to `New()`
- name: FetcherOption
methods:
- cacheOption
comment: |
FetcherOption describes options that can be passed to `(httprc.Fetcher).NewFetcher()`
- name: FetchOption
comment: |
FetchOption describes options that can be passed to `(httprc.Fetcher).Fetch()`
- name: FetchRegisterOption
methods:
- fetchOption
- registerOption
- name: FetchFetcherRegisterOption
methods:
- fetchOption
- fetcherOption
- registerOption
options:
- ident: FetcherWorkerCount
interface: FetcherOption
argument_type: int
comment: |
WithFetchWorkerCount specifies the number of HTTP fetch workers that are spawned
in the backend. By default 3 workers are spawned.
- ident: Whitelist
interface: FetchFetcherRegisterOption
argument_type: Whitelist
comment: |
WithWhitelist specifies the Whitelist object that can control which URLs are
allowed to be processed.
It can be passed to `httprc.NewCache` as a whitelist applied to all
URLs that are fetched by the cache, or it can be passed on a per-URL
basis using `(httprc.Cache).Register()`. If both are specified,
the url must fulfill _both_ the cache-wide whitelist and the per-URL
whitelist.
- ident: Transformer
interface: RegisterOption
argument_type: Transformer
comment: |
WithTransformer specifies the `httprc.Transformer` object that should be applied
to the fetched resource. The `Transform()` method is only called if the HTTP request
returns a `200 OK` status.
- ident: HTTPClient
interface: FetchRegisterOption
argument_type: HTTPClient
comment: |
WithHTTPClient specififes the HTTP Client object that should be used to fetch
the resource. For example, if you need an `*http.Client` instance that requires
special TLS or Authorization setup, you might want to pass it using this option.
- ident: MinRefreshInterval
interface: RegisterOption
argument_type: time.Duration
comment: |
WithMinRefreshInterval specifies the minimum refresh interval to be used.
When we fetch the key from a remote URL, we first look at the `max-age`
directive from `Cache-Control` response header. If this value is present,
we compare the `max-age` value and the value specified by this option
and take the larger one (e.g. if `max-age` = 5 minutes and `min refresh` = 10
minutes, then next fetch will happen in 10 minutes)
Next we check for the `Expires` header, and similarly if the header is
present, we compare it against the value specified by this option,
and take the larger one.
Finally, if neither of the above headers are present, we use the
value specified by this option as the interval until the next refresh.
If unspecified, the minimum refresh interval is 1 hour.
This value and the header values are ignored if `WithRefreshInterval` is specified.
- ident: RefreshInterval
interface: RegisterOption
argument_type: time.Duration
comment: |
WithRefreshInterval specifies the static interval between refreshes
of resources controlled by `httprc.Cache`.
Providing this option overrides the adaptive token refreshing based
on Cache-Control/Expires header (and `httprc.WithMinRefreshInterval`),
and refreshes will *always* happen in this interval.
You generally do not want to make this value too small, as it can easily
be considered a DoS attack, and there is no backoff mechanism for failed
attempts.
- ident: RefreshWindow
interface: CacheOption
argument_type: time.Duration
comment: |
WithRefreshWindow specifies the interval between checks for refreshes.
`httprc.Cache` does not check for refreshes in exact intervals. Instead,
it wakes up at every tick that occurs in the interval specified by
`WithRefreshWindow` option, and refreshes all entries that need to be
refreshed within this window.
The default value is 15 minutes.
You generally do not want to make this value too small, as it can easily
be considered a DoS attack, and there is no backoff mechanism for failed
attempts.
- ident: ErrSink
interface: CacheOption
argument_type: ErrSink
comment: |
WithErrSink specifies the `httprc.ErrSink` object that handles errors
that occurred during the cache's execution. For example, you will be
able to intercept errors that occurred during the execution of Transformers.

221
vendor/github.com/lestrrat-go/httprc/options_gen.go generated vendored Normal file
View File

@ -0,0 +1,221 @@
// This file is auto-generated by github.com/lestrrat-go/option/cmd/genoptions. DO NOT EDIT
package httprc
import (
"time"
"github.com/lestrrat-go/option"
)
type Option = option.Interface
// CacheOption desribes options that can be passed to `New()`
type CacheOption interface {
Option
cacheOption()
}
type cacheOption struct {
Option
}
func (*cacheOption) cacheOption() {}
type FetchFetcherRegisterOption interface {
Option
fetchOption()
fetcherOption()
registerOption()
}
type fetchFetcherRegisterOption struct {
Option
}
func (*fetchFetcherRegisterOption) fetchOption() {}
func (*fetchFetcherRegisterOption) fetcherOption() {}
func (*fetchFetcherRegisterOption) registerOption() {}
// FetchOption describes options that can be passed to `(httprc.Fetcher).Fetch()`
type FetchOption interface {
Option
fetchOption()
}
type fetchOption struct {
Option
}
func (*fetchOption) fetchOption() {}
type FetchRegisterOption interface {
Option
fetchOption()
registerOption()
}
type fetchRegisterOption struct {
Option
}
func (*fetchRegisterOption) fetchOption() {}
func (*fetchRegisterOption) registerOption() {}
// FetcherOption describes options that can be passed to `(httprc.Fetcher).NewFetcher()`
type FetcherOption interface {
Option
cacheOption()
}
type fetcherOption struct {
Option
}
func (*fetcherOption) cacheOption() {}
// RegisterOption desribes options that can be passed to `(httprc.Cache).Register()`
type RegisterOption interface {
Option
registerOption()
}
type registerOption struct {
Option
}
func (*registerOption) registerOption() {}
type identErrSink struct{}
type identFetcherWorkerCount struct{}
type identHTTPClient struct{}
type identMinRefreshInterval struct{}
type identRefreshInterval struct{}
type identRefreshWindow struct{}
type identTransformer struct{}
type identWhitelist struct{}
func (identErrSink) String() string {
return "WithErrSink"
}
func (identFetcherWorkerCount) String() string {
return "WithFetcherWorkerCount"
}
func (identHTTPClient) String() string {
return "WithHTTPClient"
}
func (identMinRefreshInterval) String() string {
return "WithMinRefreshInterval"
}
func (identRefreshInterval) String() string {
return "WithRefreshInterval"
}
func (identRefreshWindow) String() string {
return "WithRefreshWindow"
}
func (identTransformer) String() string {
return "WithTransformer"
}
func (identWhitelist) String() string {
return "WithWhitelist"
}
// WithErrSink specifies the `httprc.ErrSink` object that handles errors
// that occurred during the cache's execution. For example, you will be
// able to intercept errors that occurred during the execution of Transformers.
func WithErrSink(v ErrSink) CacheOption {
return &cacheOption{option.New(identErrSink{}, v)}
}
// WithFetchWorkerCount specifies the number of HTTP fetch workers that are spawned
// in the backend. By default 3 workers are spawned.
func WithFetcherWorkerCount(v int) FetcherOption {
return &fetcherOption{option.New(identFetcherWorkerCount{}, v)}
}
// WithHTTPClient specififes the HTTP Client object that should be used to fetch
// the resource. For example, if you need an `*http.Client` instance that requires
// special TLS or Authorization setup, you might want to pass it using this option.
func WithHTTPClient(v HTTPClient) FetchRegisterOption {
return &fetchRegisterOption{option.New(identHTTPClient{}, v)}
}
// WithMinRefreshInterval specifies the minimum refresh interval to be used.
//
// When we fetch the key from a remote URL, we first look at the `max-age`
// directive from `Cache-Control` response header. If this value is present,
// we compare the `max-age` value and the value specified by this option
// and take the larger one (e.g. if `max-age` = 5 minutes and `min refresh` = 10
// minutes, then next fetch will happen in 10 minutes)
//
// Next we check for the `Expires` header, and similarly if the header is
// present, we compare it against the value specified by this option,
// and take the larger one.
//
// Finally, if neither of the above headers are present, we use the
// value specified by this option as the interval until the next refresh.
//
// If unspecified, the minimum refresh interval is 1 hour.
//
// This value and the header values are ignored if `WithRefreshInterval` is specified.
func WithMinRefreshInterval(v time.Duration) RegisterOption {
return &registerOption{option.New(identMinRefreshInterval{}, v)}
}
// WithRefreshInterval specifies the static interval between refreshes
// of resources controlled by `httprc.Cache`.
//
// Providing this option overrides the adaptive token refreshing based
// on Cache-Control/Expires header (and `httprc.WithMinRefreshInterval`),
// and refreshes will *always* happen in this interval.
//
// You generally do not want to make this value too small, as it can easily
// be considered a DoS attack, and there is no backoff mechanism for failed
// attempts.
func WithRefreshInterval(v time.Duration) RegisterOption {
return &registerOption{option.New(identRefreshInterval{}, v)}
}
// WithRefreshWindow specifies the interval between checks for refreshes.
// `httprc.Cache` does not check for refreshes in exact intervals. Instead,
// it wakes up at every tick that occurs in the interval specified by
// `WithRefreshWindow` option, and refreshes all entries that need to be
// refreshed within this window.
//
// The default value is 15 minutes.
//
// You generally do not want to make this value too small, as it can easily
// be considered a DoS attack, and there is no backoff mechanism for failed
// attempts.
func WithRefreshWindow(v time.Duration) CacheOption {
return &cacheOption{option.New(identRefreshWindow{}, v)}
}
// WithTransformer specifies the `httprc.Transformer` object that should be applied
// to the fetched resource. The `Transform()` method is only called if the HTTP request
// returns a `200 OK` status.
func WithTransformer(v Transformer) RegisterOption {
return &registerOption{option.New(identTransformer{}, v)}
}
// WithWhitelist specifies the Whitelist object that can control which URLs are
// allowed to be processed.
//
// It can be passed to `httprc.NewCache` as a whitelist applied to all
// URLs that are fetched by the cache, or it can be passed on a per-URL
// basis using `(httprc.Cache).Register()`. If both are specified,
// the url must fulfill _both_ the cache-wide whitelist and the per-URL
// whitelist.
func WithWhitelist(v Whitelist) FetchFetcherRegisterOption {
return &fetchFetcherRegisterOption{option.New(identWhitelist{}, v)}
}

446
vendor/github.com/lestrrat-go/httprc/queue.go generated vendored Normal file
View File

@ -0,0 +1,446 @@
package httprc
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
"github.com/lestrrat-go/httpcc"
)
// ErrSink is an abstraction that allows users to consume errors
// produced while the cache queue is running.
type ErrSink interface {
// Error accepts errors produced during the cache queue's execution.
// The method should never block, otherwise the fetch loop may be
// paused for a prolonged amount of time.
Error(error)
}
type ErrSinkFunc func(err error)
func (f ErrSinkFunc) Error(err error) {
f(err)
}
// Transformer is responsible for converting an HTTP response
// into an appropriate form of your choosing.
type Transformer interface {
// Transform receives an HTTP response object, and should
// return an appropriate object that suits your needs.
//
// If you happen to use the response body, you are responsible
// for closing the body
Transform(string, *http.Response) (interface{}, error)
}
type TransformFunc func(string, *http.Response) (interface{}, error)
func (f TransformFunc) Transform(u string, res *http.Response) (interface{}, error) {
return f(u, res)
}
// BodyBytes is the default Transformer applied to all resources.
// It takes an *http.Response object and extracts the body
// of the response as `[]byte`
type BodyBytes struct{}
func (BodyBytes) Transform(_ string, res *http.Response) (interface{}, error) {
buf, err := ioutil.ReadAll(res.Body)
defer res.Body.Close()
if err != nil {
return nil, fmt.Errorf(`failed to read response body: %w`, err)
}
return buf, nil
}
type rqentry struct {
fireAt time.Time
url string
}
// entry represents a resource to be fetched over HTTP,
// long with optional specifications such as the *http.Client
// object to use.
type entry struct {
mu sync.RWMutex
sem chan struct{}
lastFetch time.Time
// Interval between refreshes are calculated two ways.
// 1) You can set an explicit refresh interval by using WithRefreshInterval().
// In this mode, it doesn't matter what the HTTP response says in its
// Cache-Control or Expires headers
// 2) You can let us calculate the time-to-refresh based on the key's
// Cache-Control or Expires headers.
// First, the user provides us the absolute minimum interval before
// refreshes. We will never check for refreshes before this specified
// amount of time.
//
// Next, max-age directive in the Cache-Control header is consulted.
// If `max-age` is not present, we skip the following section, and
// proceed to the next option.
// If `max-age > user-supplied minimum interval`, then we use the max-age,
// otherwise the user-supplied minimum interval is used.
//
// Next, the value specified in Expires header is consulted.
// If the header is not present, we skip the following seciont and
// proceed to the next option.
// We take the time until expiration `expires - time.Now()`, and
// if `time-until-expiration > user-supplied minimum interval`, then
// we use the expires value, otherwise the user-supplied minimum interval is used.
//
// If all of the above fails, we used the user-supplied minimum interval
refreshInterval time.Duration
minRefreshInterval time.Duration
request *fetchRequest
transform Transformer
data interface{}
}
func (e *entry) acquireSem() {
e.sem <- struct{}{}
}
func (e *entry) releaseSem() {
<-e.sem
}
func (e *entry) hasBeenFetched() bool {
e.mu.RLock()
defer e.mu.RUnlock()
return !e.lastFetch.IsZero()
}
// queue is responsible for updating the contents of the storage
type queue struct {
mu sync.RWMutex
registry map[string]*entry
windowSize time.Duration
fetch Fetcher
fetchCond *sync.Cond
fetchQueue []*rqentry
// list is a sorted list of urls to their expected fire time
// when we get a new tick in the RQ loop, we process everything
// that can be fired up to the point the tick was called
list []*rqentry
}
func newQueue(ctx context.Context, window time.Duration, fetch Fetcher, errSink ErrSink) *queue {
fetchLocker := &sync.Mutex{}
rq := &queue{
windowSize: window,
fetch: fetch,
fetchCond: sync.NewCond(fetchLocker),
registry: make(map[string]*entry),
}
go rq.refreshLoop(ctx, errSink)
return rq
}
func (q *queue) Register(u string, options ...RegisterOption) error {
var refreshInterval time.Duration
var client HTTPClient
var wl Whitelist
var transform Transformer = BodyBytes{}
minRefreshInterval := 15 * time.Minute
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identHTTPClient{}:
client = option.Value().(HTTPClient)
case identRefreshInterval{}:
refreshInterval = option.Value().(time.Duration)
case identMinRefreshInterval{}:
minRefreshInterval = option.Value().(time.Duration)
case identTransformer{}:
transform = option.Value().(Transformer)
case identWhitelist{}:
wl = option.Value().(Whitelist)
}
}
q.mu.RLock()
rWindow := q.windowSize
q.mu.RUnlock()
if refreshInterval > 0 && refreshInterval < rWindow {
return fmt.Errorf(`refresh interval (%s) is smaller than refresh window (%s): this will not as expected`, refreshInterval, rWindow)
}
e := entry{
sem: make(chan struct{}, 1),
minRefreshInterval: minRefreshInterval,
transform: transform,
refreshInterval: refreshInterval,
request: &fetchRequest{
client: client,
url: u,
wl: wl,
},
}
q.mu.Lock()
q.registry[u] = &e
q.mu.Unlock()
return nil
}
func (q *queue) Unregister(u string) error {
q.mu.Lock()
defer q.mu.Unlock()
_, ok := q.registry[u]
if !ok {
return fmt.Errorf(`url %q has not been registered`, u)
}
delete(q.registry, u)
return nil
}
func (q *queue) getRegistered(u string) (*entry, bool) {
q.mu.RLock()
e, ok := q.registry[u]
q.mu.RUnlock()
return e, ok
}
func (q *queue) IsRegistered(u string) bool {
_, ok := q.getRegistered(u)
return ok
}
func (q *queue) fetchLoop(ctx context.Context, errSink ErrSink) {
for {
q.fetchCond.L.Lock()
for len(q.fetchQueue) <= 0 {
select {
case <-ctx.Done():
return
default:
q.fetchCond.Wait()
}
}
list := make([]*rqentry, len(q.fetchQueue))
copy(list, q.fetchQueue)
q.fetchQueue = q.fetchQueue[:0]
q.fetchCond.L.Unlock()
for _, rq := range list {
select {
case <-ctx.Done():
return
default:
}
e, ok := q.getRegistered(rq.url)
if !ok {
continue
}
if err := q.fetchAndStore(ctx, e); err != nil {
if errSink != nil {
errSink.Error(&RefreshError{
URL: rq.url,
Err: err,
})
}
}
}
}
}
// This loop is responsible for periodically updating the cached content
func (q *queue) refreshLoop(ctx context.Context, errSink ErrSink) {
// Tick every q.windowSize duration.
ticker := time.NewTicker(q.windowSize)
go q.fetchLoop(ctx, errSink)
defer q.fetchCond.Signal()
for {
select {
case <-ctx.Done():
return
case t := <-ticker.C:
t = t.Round(time.Second)
// To avoid getting stuck here, we just copy the relevant
// items, and release the lock within this critical section
var list []*rqentry
q.mu.Lock()
var max int
for i, r := range q.list {
if r.fireAt.Before(t) || r.fireAt.Equal(t) {
max = i
list = append(list, r)
continue
}
break
}
if len(list) > 0 {
q.list = q.list[max+1:]
}
q.mu.Unlock() // release lock
if len(list) > 0 {
// Now we need to fetch these, but do this elsewhere so
// that we don't block this main loop
q.fetchCond.L.Lock()
q.fetchQueue = append(q.fetchQueue, list...)
q.fetchCond.L.Unlock()
q.fetchCond.Signal()
}
}
}
}
func (q *queue) fetchAndStore(ctx context.Context, e *entry) error {
e.mu.Lock()
defer e.mu.Unlock()
// synchronously go fetch
e.lastFetch = time.Now()
res, err := q.fetch.fetch(ctx, e.request)
if err != nil {
// Even if the request failed, we need to queue the next fetch
q.enqueueNextFetch(nil, e)
return fmt.Errorf(`failed to fetch %q: %w`, e.request.url, err)
}
q.enqueueNextFetch(res, e)
data, err := e.transform.Transform(e.request.url, res)
if err != nil {
return fmt.Errorf(`failed to transform HTTP response for %q: %w`, e.request.url, err)
}
e.data = data
return nil
}
func (q *queue) Enqueue(u string, interval time.Duration) error {
fireAt := time.Now().Add(interval).Round(time.Second)
q.mu.Lock()
defer q.mu.Unlock()
list := q.list
ll := len(list)
if ll == 0 || list[ll-1].fireAt.Before(fireAt) {
list = append(list, &rqentry{
fireAt: fireAt,
url: u,
})
} else {
for i := 0; i < ll; i++ {
if i == ll-1 || list[i].fireAt.After(fireAt) {
// insert here
list = append(append(list[:i], &rqentry{fireAt: fireAt, url: u}), list[i:]...)
break
}
}
}
q.list = list
return nil
}
func (q *queue) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
buf.WriteString(`{"list":[`)
q.mu.RLock()
for i, e := range q.list {
if i > 0 {
buf.WriteByte(',')
}
fmt.Fprintf(&buf, `{"fire_at":%q,"url":%q}`, e.fireAt.Format(time.RFC3339), e.url)
}
q.mu.RUnlock()
buf.WriteString(`]}`)
return buf.Bytes(), nil
}
func (q *queue) enqueueNextFetch(res *http.Response, e *entry) {
dur := calculateRefreshDuration(res, e)
// TODO send to error sink
_ = q.Enqueue(e.request.url, dur)
}
func calculateRefreshDuration(res *http.Response, e *entry) time.Duration {
if e.refreshInterval > 0 {
return e.refreshInterval
}
if res != nil {
if v := res.Header.Get(`Cache-Control`); v != "" {
dir, err := httpcc.ParseResponse(v)
if err == nil {
maxAge, ok := dir.MaxAge()
if ok {
resDuration := time.Duration(maxAge) * time.Second
if resDuration > e.minRefreshInterval {
return resDuration
}
return e.minRefreshInterval
}
// fallthrough
}
// fallthrough
}
if v := res.Header.Get(`Expires`); v != "" {
expires, err := http.ParseTime(v)
if err == nil {
resDuration := time.Until(expires)
if resDuration > e.minRefreshInterval {
return resDuration
}
return e.minRefreshInterval
}
// fallthrough
}
}
// Previous fallthroughs are a little redandunt, but hey, it's all good.
return e.minRefreshInterval
}
type SnapshotEntry struct {
URL string `json:"url"`
Data interface{} `json:"data"`
LastFetched time.Time `json:"last_fetched"`
}
type Snapshot struct {
Entries []SnapshotEntry `json:"entries"`
}
// Snapshot returns the contents of the cache at the given moment.
func (q *queue) snapshot() *Snapshot {
q.mu.RLock()
list := make([]SnapshotEntry, 0, len(q.registry))
for url, e := range q.registry {
list = append(list, SnapshotEntry{
URL: url,
LastFetched: e.lastFetch,
Data: e.data,
})
}
q.mu.RUnlock()
return &Snapshot{
Entries: list,
}
}

73
vendor/github.com/lestrrat-go/httprc/whitelist.go generated vendored Normal file
View File

@ -0,0 +1,73 @@
package httprc
import "regexp"
// Whitelist is an interface for a set of URL whitelists. When provided
// to fetching operations, urls are checked against this object, and
// the object must return true for urls to be fetched.
type Whitelist interface {
IsAllowed(string) bool
}
// InsecureWhitelist allows any URLs to be fetched.
type InsecureWhitelist struct{}
func (InsecureWhitelist) IsAllowed(string) bool {
return true
}
// RegexpWhitelist is a httprc.Whitelist object comprised of a list of *regexp.Regexp
// objects. All entries in the list are tried until one matches. If none of the
// *regexp.Regexp objects match, then the URL is deemed unallowed.
type RegexpWhitelist struct {
patterns []*regexp.Regexp
}
func NewRegexpWhitelist() *RegexpWhitelist {
return &RegexpWhitelist{}
}
func (w *RegexpWhitelist) Add(pat *regexp.Regexp) *RegexpWhitelist {
w.patterns = append(w.patterns, pat)
return w
}
// IsAlloed returns true if any of the patterns in the whitelist
// returns true.
func (w *RegexpWhitelist) IsAllowed(u string) bool {
for _, pat := range w.patterns {
if pat.MatchString(u) {
return true
}
}
return false
}
// MapWhitelist is a httprc.Whitelist object comprised of a map of strings.
// If the URL exists in the map, then the URL is allowed to be fetched.
type MapWhitelist struct {
store map[string]struct{}
}
func NewMapWhitelist() *MapWhitelist {
return &MapWhitelist{store: make(map[string]struct{})}
}
func (w *MapWhitelist) Add(pat string) *MapWhitelist {
w.store[pat] = struct{}{}
return w
}
func (w *MapWhitelist) IsAllowed(u string) bool {
_, b := w.store[u]
return b
}
// WhitelistFunc is a httprc.Whitelist object based on a function.
// You can perform any sort of check against the given URL to determine
// if it can be fetched or not.
type WhitelistFunc func(string) bool
func (w WhitelistFunc) IsAllowed(u string) bool {
return w(u)
}

View File

@ -1,723 +0,0 @@
Changes
=======
v1.2.25 23 May 2022
[Bug Fixes][Security]
* [jwe] An old bug from at least 7 years ago existed in handling AES-CBC unpadding,
where the unpad operation might remove more bytes than necessary (#744)
This affects all jwx code that is available before v2.0.2 and v1.2.25.
v1.2.24 05 May 2022
[Security]
* Upgrade golang.org/x/crypto (#724)
v1.2.23 13 Apr 2022
[Bug fixes]
* [jwk] jwk.AutoRefresh had a race condition when `Configure()` was
called concurrently (#686)
(It has been patched correctly, but we may come back to revisit
the design choices in the near future)
v1.2.22 08 Apr 2022
[Bug fixes]
* [jws] jws.Verify was ignoring the `b64` header when it was present
in the protected headers (#681). Now the following should work:
jws.Sign(..., jws.WithDetachedPayload(payload))
// previously payload had to be base64 encoded
jws.Verify(..., jws.WithDetachedPayload(payload))
(note: v2 branch was not affected)
v1.2.21 30 Mar 2022
[Bug fixes]
* [jwk] RSA keys without p and q can now be parsed.
v1.2.20 03 Mar 2022
[Miscellaneous]
* Dependency on golang.org/x/crypto has been upgraded to
v0.0.0-20220214200702-86341886e292 to address
https://nvd.nist.gov/vuln/detail/CVE-2020-14040 (#598)
v1.2.19 22 Feb 2022
[New Feature]
* [jwk] jwk.Parse (and (jwk.AutoRefresh).Configure) can accept a new
option `jwk.WithIgnoreParseError(bool)`, which allows users to ignore
errors during parsing of each key contained in the JWKS, allowing
you to "skip" invalid keys.
This option should not be used lightly, as it hides the presence of
possibly faulty keys. However, this can be an escape hatch if you are
faced with a faulty JWKS that you do not control.
v1.2.18 23 Jan 2022
[Bug fixes]
* [jwe] When presented with jwk.Key with a key ID, the jwe encryption
code path did not assign this key ID to the resulting data structure.
This has been fixed, and now the key ID is properly applied to the
`kid` field.
* [jws] Use for `crypto.Signer`s were implemented for signing, but verification was
never properly implemented. This has been fixed.
[Miscellaneous]
* [jws] Because of fixes to code path that deals with `crypto.Signer`s, we are
now able to fully integrate with Cloud services, such as Google's Cloud KMS
and AWS KMS, that provide key management and signing payloads
An implementation for these are available at https://github.com/jwx-go/crypto-signer.
Suppot `crypto.Signer` in JWE encryption has not been implemented.
v1.2.17 12 Jan 2022
[Miscellaneous]
* Re-release v1.2.16 as v1.2.17 because of an error in the release process.
The code is exactly the same as what v1.2.16 intended to release.
v1.2.16 has been retracted in go.mod.
v1.2.16 12 Jan 2022
THIS VERSION HAS BEEN RETRACTED. PLEASE USE v1.2.17
[Bug Fixes]
* Peviously, `jws.Sign()` could not create a signed payload with
detached and unencoded payload, even when the documentation said it could.
Now you may use the `jws.Sign()` in the following way to create
a JWS message with detached, unencoded state:
hdrs := jws.NewHeaders()
hdrs.Set("b64", false)
hdrs.Set("crit", "b64")
jws.Sign(nil, alg, key, jws.WithDetachedPayload(payload), jws.WithHeaders(hdrs))
Notice the use of `nil` for the first parameter, and the use of
`jws.WithDetachedPayload()`.
We realize this is not exactly a clean API, but this is currently the
only way to implement this in a backward-compatible fashion. Most likely
this will change in a future major version.
[Miscellaneous]
* `jws.WithDetachedPayload()` is now of type `jws.SignVerifyOption`, which
satisfies both `jws.SignOption` and `jws.VerifyOption`
v1.2.15 07 Jan 2022
[New Features]
* `(jwk.AutoRefresh).Remove()` has been implemented.
[Bug Fixes]
* ES256K is now included in the list of JWS inferred algorithms, if it's
enabled via -tags jwx_es256k
[Miscellaneous]
* `jwt.Parse` has been improved for efficiency and has more tests to
cover corner cases.
* Documentation fixes
v1.2.14 22 Dec 2021
[New Features]
* `jwk.Fetch()` and `(*jwk.AutoRefresh).Configure()` can now take `jwk.Whitelist`
object to check for the validity of a url to be fetched
* `jws.VerifyAuto()` has been added to verify payloads that can be verified
using the JWK set provided in the "jku" field. This function is purposely
separated from the `jws.Verify()` function because 1) the required parameters
are different, and 2) Users MUST be aware that they are doing a totally
different operation than a regular `jws.Verify()`
* `(jwk.AutoRefresh).IsRegistered()` has been added.
[Bug fixes]
* `jws.SignMulti()` has been fixed to assign the "kid" field of the key used
for signing the payload
* `jws.SignMulti()` has been fixed to respect the "kid" field of the protected
header, not the public header
v1.2.13 07 Dec 2021
[New Features]
* `jwt` package now has a `Builder` that may make it easier to programmatically
create a JWT for some users.
* `jwt` errors now can be distinguished between validation errors and others.
Use `jwt.IsValidationError()` to check if it's a validation error, and then
use `errors.Is()` to check if it's one of the known (oft-used) errors
v1.2.12 01 Dec 2021
[New Features]
* `jwk.Set` can now parse private parameters. For example, after parsing
a JWKS serialized as `{"foo": "bar", "keys": [...]}`, users can get to
the value of `"foo"` by calling `set.Field("foo")`
* `jwk.Set` now has `Set()` method to set field values.
v1.2.11 14 Nov 2021
[Security Fix]
* It was reported that since v1.2.6, it was possible to craft
a special JSON object to bypass JWT verification via `jwt.Parse`.
If you relied on this module to perform all the verification,
upgrade is strongly recommended.
v1.2.10 09 Nov 2021
[Bug fixes]
* Parsing OpenID claims were not working for some fields.
This was caused by the same problem as the problem fixed in v1.2.9.
Proper tests have been added.
v1.2.9 26 Oct 2021
[Bug fixes]
* Parsing `key_ops` for JWKs which was broken in v1.2.8 has been fixed.
v1.2.8 21 Oct 2021
[Miscellaneous]
* `jws.Message`, `jws.Signature`, `jws.Headers` have been reworked
to allow JSON messages to be verified correctly. The problem can
be caused when protected headers are serialized one way (perhaps
`{"c":3","a":1,"b":2}` was used before being base64-encoded) but
the Go serialization differed from it (Go serializes in alphabetical
order: `{"a":1,"b":2,"c":3}`)
Messages serialized in compact form do NOT suffer from the
same problem.
This is close to fixes that went in v1.2.2. It boils down to the
fact that once deserialized, the JWS messages lose part of its
information (namely, the raw, original protected header value),
and neither users nor the developers of this library should
rely on it.
* Code generation has be refactored. The main go.mod should now
have slightly less dependencies.
v1.2.7 26 Sep 2021
[New features]
* `jwt.InferAlgorithmFromKey()` option is now available to "guess"
the algorithm used to verify the JWS signature on a JWT using
a JWKS (key set). This allows you to match JWKs that do not have
the `alg` field populated.
We understand that some providers do not provide the `alg` field,
which is a nuisance to users. But from a purely security minded PoV,
we don't think that this "try until something works" approach is a
good one, even if there are no known exploits. This is why the
default `jwt.Parse` mechanism is unchanged, and an explicit option
has been added.
* Types `jwt.KeySetProvider` and `jwk.KeySetProviderFunc` have been
added. Along with `jwt.WithKeySetProvider()` option, `jwt.Parse`
can now choose the `jwk.Set` to use for signature verification
dynamically using the UNVERFIEID token as a clue.
You should NOT trust the token information too much. For example,
DO NOT directly use values from the token as verificatin parameters
(such as the signature algorithm)
* `jwt.WithValidator()` has been added to allow users pass in aribtrary
validation code to the `jwt.Validate()` method.
It is also now possible to pass in a `context.Context` object to
`jwt.Validate()` using `jwt.WithContext()` option.
[Miscellaneous]
* Make the error messages when `jwt.ParseRequest` fails a bit better.
* Moved around documentation within the repository
* Validation logic for `jwt.Validate()` has been refactored to use the
new `jwt.Validator` mechanism
v1.2.6 24 Aug 2021
[New features]
* Support `crypto.Signer` keys for RSA, ECDSA, and EdDSA family
of signatures in `jws.Sign`
[Miscellaneous]
* `jwx.GuessFormat()` now requires the presense of both `payload` and
`signatures` keys for it to guess that a JSON object is a JWS message.
* Slightly enhance `jwt.Parse()` performance.
v1.2.5 04 Aug 2021
[New features]
* Implement RFC7797. The value of the header field `b64` changes
how the payload is treated in JWS
* Implement detached payloads for JWS
* Implement (jwk.AutoRefresh).ErrorSink() to register a channel
where you can receive errors from fetches and parses that occur during
JWK(s) retrieval.
v1.2.4 15 Jul 2021
[Bug fixes]
* We had the same off-by-one in another place and jumped the gun on
releasing a new version. At least we were making mistakes uniformally :/
`(jwk.Set).Remove` should finally be fixed.
[New features]
* `(jwk.Set).Clone()` has been added.
v1.2.3 15 Jul 2021
[Bug fixes]
* jwk.Set incorrectly removed 2 elements instead of one.
[Miscellaneous]
* github.com/goccy/go-json has been upgraded to v0.7.4
v1.2.2 13 Jul 2021
[Deprecation notice]
* `(jwe.Message).Decrypt()` will be removed from the API upon the next
major release.
[Bug Fixes]
* `jwe.Decrypt` and `(jwe.Message).Decrypt()` failed to decrypt even
with the correct message contents when used along with `jwe.RegisterCustomField`
[New features]
JWX
* Add GuessFormat() function to guess what the payload is.
JWT
* Options `jwt.WithMinDelta()`, `jwt.WithMaxDelta()` have been added.
These can be used to compare time-based fields in the JWT object.
* Option `jwt.WithRequiredClaim()` has been added. This can be used
to check that JWT contains the given claim.
* `jwt.Parse` now understands payloads that have been encrypted _and_ signed.
This is more in line with the RFC than the previous implementation, but
due to the fact that it requires a couple of extra unmarshaling, it may
add some amount of overhead.
* `jwt.Serializer` has been added as an easy wrapper to perform multiple
levels of serializations (e.g. apply JWS, then JWE)
JWE
* Option `jwe.WithMessage()` has been added. This allows the user to
obtain both the decrypted payload _and_ the raw `*jwe.Message` in one
go when `jwe.Decrypt()` is called
* Option `jwe.WithPostParser()`, along with `jwe.PostParser` and `jwe.PostParseFunc`
has been added. This allows advanced users to hook into the `jwe.Decrypt()`
process. The hook is called right after the JWE message has been parsed,
but before the actual decryption has taken place.
* `(jwe.Message).Decrypt()` has been marked for deprecation in a next major release.
JWS
* Option `jwe.WithMessage()` has been added. This allows the user to
obtain both the verified payload _and_ the raw `*jws.Message` in one
go when `jws.Verify()` is called
* Options to `jws.Sign()` are not of type `jws.SignOption`. There should be
no user-visible effects unless you were storing these somewhere.
v1.2.1 02 Jun 2021
[New features]
* Option `jwt.WithTypedClaim()` and `jwk.WithTypedField()` have been added.
They allow a per-object custom conversion from their JSON representation
to a Go object, much like `RegisterCustomField`.
The difference is that whereas `RegisterCustomField` has global effect,
these typed fields only take effect in the call where the option was
explicitly passed.
`jws` and `jwe` does not have these options because
(1) JWS and JWE messages don't generally carry much in terms of custom data
(2) This requires changes in function signatures.
Only use these options when you absolutely need to. While it is a powerful
tool, they do have many caveats, and abusing these features will have
negative effects. See the documentation for details
v1.2.0 30 Apr 2021
This is a security fix release with minor incompatibilities from earlier version
with regards to the behavior of `jwt.Verify()` function
[Security Fix]
* `jwt.Verify()` had improperly used the `"alg"` header from the JWS message
when `jwt.WithKeySet()` option was used (potentially allowing exploits
described in https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/.
This has been fixed by ONLY trusting the keys that you provide and
using the `"alg"` header from the keys themselves. (#375, #381)
As a side effect, `jwt.WithKeySet()` requires that all applicable keys
to contain a valid `"alg"` header. Without this we cannot safely choose a key to use,
and hence verification will fail.
The requirement for the `"alg"` header on keys is an INCOMPATIBLE behavior.
This may break existing code, if the key does not already have an `"alg"` header.
[New features]
* `jwt.Settings()` and `jwt.WithFlattenAudience(bool)` has been added
to control how the "aud" claim is serialized into JSON. When this
is enabled, all JWTs with a single "aud" claim will serialize
the field as a single string, instead of an array of strings with
a single element, i.e.:
// jwt.WithFlattenAudience(true)
{"aud": "foo"}
// jwt.WithFlattenAudience(false)
{"aud": ["foo"]}
This setting has a global effect.
[Buf fixes]
* jwt.Validate now returns true if the value in `nbf` field is exactly
the same as what the clock returns (e.g. token.nbf == time.Now())
v1.1.7 02 Apr 2021
[New features]
* `jwk.New` `jwk.Parse`, `jwk.ParseKey` can now take a Certificate in
ASN.1 DER format in PEM encoding to create a JWK.
[Bug fixes]
* Protect `jwk.New()` from invalid RSA/ECDSA keys (#360, #361)
[Miscellaneous]
* Removed "internal/blackmagic" and separated it to its own repository.
* Removed unused "marshal proxy" objects in jwt
* Added FAQ in `jwt` package
v1.1.6 28 Mar 2021
[Bug fixes]
* When an object (e.g. JWT) has a null value and `AsMap()` is called,
`github.com/lestrrat-go/iter` would panic.
This should be fixed in `github.com/lestrrat-go/iter@v1.0.1` and
the dependency has been updated accordingly
[Miscellaneous]
* Added How-to style docs under `docs/`
* github.com/goccy/go-json dependency has been updated to v0.4.8
v1.1.5 12 Mar 2021
This is a security fix release. The JWT validation could be skipped
for empty values. Upgrade recommended
[Security Fix]
* JWT validation could be skipped for empty fields (#352).
[Bug fixes]
* Allow setting JWT "typ" fields to any value (#351).
* Remove stray replace directive in cmd/jwx/go.mod (#349)
v1.1.4 02 Mar 2021
[New features]
* jwt.ParseRequest, jwt.ParseHeader, jwt.ParseForm have been added.
They are convenience functions to parse JWTs out of a HTTP request.
[Miscellaneous]
* Fix jwt.Equals() so that comparison between values containing time.Time
actually work
* ES256K has been made non-default. You must enable it using a build tag
go build -tags jwx_es256k ...
Your program will still compile without this tag, but it will return
an error during runtime, when ES256K is encountered.
This feature is still experimental.
v1.1.3 22 Feb 2021
[New features]
* Implemented ES256K signing (#337)
This feature should be considered experimental
[Miscellaneous]
* Bump minimum required version to go1.15
* Fix examples, bench, and cmd/jwx accidentally requiring go1.16
* Dependencies for "github.com/goccy/go-json" has been upgraded to
v0.4.7
v1.1.2 16 Feb 2021
[New features]
* `RegisterCustomField()` has been added, which allows users to
specify a private claim/field/header to decode into a particular
object of choice, instead of map[string]interface{} or []interface{} (#332, #333)
[Bug fixes]
* Failures for `jwk.Key.MarshalJSON()` were not properly reported (#330, #331)
[Miscellaneous]
* `jwe.Encrypt()` now takes options. This should not matter unless you
were somehow depending on its method signature.
* Dependencies for "github.com/goccy/go-json" has been upgraded to
v0.4.2
v1.1.1 05 Feb 2021
[New features]
* Command line tool `jwx` has ben completely reworked, and it is
now actually useful.
* JWKs can now be serialized into PEM files with ASN.1 DER format
data, which is useful when you need to work between JSON and PEM
data formats.
* Constants in jwa package now have can be listed via functions
in each category.
* jwe.Encrypt and jwe.Decrypt can now handle jwk.Key objects
v1.1.0 31 Jan 2021
v1.1.0 is a release that attempts to fix as many of the quirky APIs
that survived the API breaking change of v0.9.x -> v1.0.0. This is
hopefully the last releases that change backwards compatibility
in a major way, at least for some time to come.
It is unfortunate that we need to introduce API changes, but we
keep learning how the library is being used and the pain points
of using this library. Most of the times these pain points are
things that we initially did not think about, which in turn
requires us to rethink of the API.
If you do not wish to spend the time fixing your usage, make sure
you have your go.mod set up to not automatically track the latest
changes.
However, if you do decide to use the latest version, we believe
the API is more uniform across packages, and generally is easier
to understand. We hope this library helps some of you out there.
[BREAKING CHANGES]
* `jwk.Parse(io.Reader)`, `jws.Parse(io.Reader)`, `jwt.Parse(io.Reader)`,
have all been changed to `Parse([]byte)`. To use an `io.Reader`,
use `ParseReader(io.Reader)`. `jwe.Parse` already took `[]byte`, so
has not been changed.
With this change, all four package `jwe`, `jwk`, `jws`, and `jwt` follow
the same API design, which should make things easier to navigate:
Parse([]byte)
ParseString(string)
ParseReader(io.Reader)
* `jwk.Set` is now an interface, not a struct. `jwk.Set` now has a
well-defined API to access and modify the `jwk.Key` objects that it holds.
Add(jwk.Key) bool
Clear()
Get(int) (jwk.Key, bool)
Index(jwk.Key) int
Len() int
LookupKeyID() (jwk.Key, bool) // Read the section about it below
Remove(jwk.Key) bool
Iterate(context.Context) KeyIterator
* `(jwk.Set).LookupKeyID()` no longer returns an array of `jwk.Key`.
Instead, only the first key matching the given key ID will be returned.
If you need to work with multiple keys, use `(jwk.Set).Iterate()` or
`(jwk.Set).Get()` to look for matching keys.
* `jwk.PublicKeyOf()` has been renamed to `jwk.PublicRawKeyOf()`,
which converts raw keys (e.g. `rsa.PrivateKey`) to their public
counter part (e.g. `rsa.PublicKey`)
`jwk.PublicKeyOf()` is now used to get the public counter part of
`jwk.Key` objects (e.g. `jwk.RSAPrivateKey` to `jwk.RSAPublicKey`)
`jwk.PublicSetOf()` has been added to get a new `jwk.Set` but with
all keys transformed to public keys via `jwk.PublicKeyOf()`
* `jwk.FetchXXXX` functions have been removed. `jwk.Fetch()` remains, but
it now takes `context.Context`, and doesn't support retrieving files
from the local file system. See `ReadFile()` for that.
* `jws.VerifyWithJKU()`, `jws.VerifyWithJWK()`, `jwk.VerifyWithJWKSet()`
have all been removed, but `jwk.VerifySet(jwk.Set)` has been added.
* `jws.SplitCompact(io.Reader)` has been changd to `jws.SplitCompact([]byte)`
Similar to `Parse()`, `SplitCompactReader(io.Reader)` and `SplitCompactString(string)`
have been added
* `jws.SignLiteral` has been removed.
* `jws.PayloadSigner` has been removed (but should not matter, because
this as internal-use only anyways)
* `jwe.WithPrettyJSONFormat` has been renamed to `jwe.WithPrettyFormat`
* `jwt.Verify` has been removed. Use `jwt.Parse()` aloing with the `jwt.WithVerify()`
option to perform signature verification. Validation of verified data
can be performed via `(jwt.Token).Validate()` method, which has been available
since v1.0.6
* Package `buffer` has been removed. This package should have been an internal
package to start with, but it was left because it had been incorporated
in the public API in our initial versions.
* `(jwk.Key).Get(jwk.X509CertChainKey)` no longer returns a `jwk.CertificateChain`.
Instead it returns a raw []*x509.Certificate.
* `(jwt.Token).Size() has been removed.
* `jwt.WithOpenIDClaims()` has been removed. Use `jwt.WithToken(openid.New())` instead.
[New Features]
* `jwe.ReadFile(string)`, `jwk.ReadFile(string)`, `jws.ReadFile(string)`, and
`jwt.ReadFile(string)` have been added. In the future, we plan to introduce
a `WithFS` option so you can read from an arbitrary file system, but this cannot
be added while we keep go < 1.16 compatibility. If you want something like that,
you will need to put an adapter over the jwx for the time being.
* `(jwk.Key).PublicKey()` has been added. This method creates a corresponding
public key, with all fields (except those that shouldn't be) copied over.
This allows you to easily create a public key of a private key with the
same "kid" attribute.
* Both `jws.Verify` and `jws.Sign` methods can now handle `jwk.Key` objects, on
top of raw keys (e.g. rsa.PrivateKey). You no longer need to conver the
`jwk.Key` objects that you have in to raw keys before using these functions.
* `(jws.Header).Remove(string)`, `(jwk.Key).Remove(string)`, and
`(jwt.Token).Remove(string)` have been added. `jwe.Header` already had a `Remove()`
method, so it has not been changed.
* `(jwk.Key).Clone() has been added.
[Miscellaneous]
* Default branch for the repository is now `main`.
* Options have been reworked. In most instances, option types should now reflect
better the contexts in which they can be used. For example, `jwk` now has
`AutoRefreshOption` and `FetchOption` instead of a single `Option`.
* JSON marshaling should be 10~30% faster by default (though they may take
more allocations to achieve this).
However, if performance is really bogging you down, you can try to enable
the optional module github.com/goccy/go-json by enabling the "jwx_goccy" tag
go build -tags jwx_goccy ...
In some cases you get an extra 40~50% performance improvement in serailization
https://github.com/lestrrat-go/jwx/pull/314#issue-560594020
https://github.com/lestrrat-go/jwx/pull/314#issuecomment-766343888
* Location for examples and benchmarks have changed: Now examples/ and bench/
are their respective locations, and they are each a standalone module,
so that in case we need extra imports (such as the case in examples)
they do not interfere with users who just want to include jwx in their projects.
v1.0.8 15 Jan 2021
[New features]
* Fixed `jws.Message` and `jws.Signature` to be properly formatted when
marshaled into JSON. In the same manner, `json.Unmarshal` should also
work as expected.
* Added API to programatically manipulate `jws.Message` and `jws.Signature`
[Miscellaneous]
* The order of keys are now consistent as when used with `json.Marshal`.
Previously some objects used their own ordering, but now the code goes
through one extra roundtrip of `json.Unmarshal`/`json.Marshal` to preserve
compatible behavior. This *may* lead to slightly slower performance if
you are performing `json.Marshal` over and over in very quick succession.
Please file an issue if you have real world cases where the change
causes problems for you.
* Added more examples in various places.
* Tests runs have been sped up for the most oft used cases
v1.0.7 11 Jan 2021
[New features]
* Added jwk.AutoRefresh, which is a tool to periodically refresh JWKS. (#265)
* Added experimental ed25519 support (#252)
[Bug fixes]
* Fix `Set()` method for jwk Keys to properly accept either `jwk.KeyUsageType`
or a simple string.
[Miscellaneous]
* Updated dependencies
* Changed options to use github.com/lestrrat-go/option
* Various typos, unused annotations, etc, have been fixed by contributors
* Nobody except for the author really should care, but the underlying
`pdebug` utility, which is used for print debugging, has been
upgraded to v3, which should stop parallel test execution from throwing
an error when run with -race
v1.0.6 17 Dec 2020
* Fix ECDHES ciphers where padding in AAD et al was creating
incomptabile values with jose tool
* Also fix ECDH-ES cek handling (#248)
* Implement direct key encoding (#213, #249)
* Allow JWT tokens to use default JWK if only one key is given
and the JWT does not necessarily specifies a key (#214)
* Deprecate jwt.Verify and introduce jwt.Validate. JWS verification
used the term Verify, which was confusing when users wanted to
validate the JWT token itself. (#220)
* JWT library optins have been explicitly typed as ValidationOption
and ParseOption (#220, #223)
* Add jwx.DecoderSettings and jwx.WithUseNumber option to globally
change how jwx parses JSON objects (#222)
* Encode x5c field as base64 with padding (#244)
* Add more interoperability tests against jose tool.
* Special thanks to anatol and imirkin!
v1.0.5 - 28 Sep 2020
* Reinstate PrivateParams() method in jws and jwe packages.
These used to be available until v1.0.0, but somehow got lost during the
big change.
As a workaround for users of versions 1.0.0 to 1.0.4, you could have
achieved the same thing using AsMap() methods, albeit with a slight
performance penality (#205, #206)
v1.0.4 - 15 Aug 2020
* Fix jwt.WithOpenIDClaims(). Looks like something got lost along
the way, and it never really worked. (#201 #202)
v1.0.3 - 08 Jul 2020
* `jws.Sign`, and therefore `jwt.Sign` now accept `jwk.Key` as the
key to use for signature. (#199)
* `jwt.Sign` could sometimes return a nil error when setting bad
values to the protected header failed (#195)
* More golangci-lint cleanup (#193)
v1.0.2 - 07 May 2020
* Since 1.0.0, we took some time to play the test coverage game.
The coverage is around 30% better, and we _did_ uncover some
inconsistencies in the API, which got promptly fixed.
But I'm tired of the coverage game for the time being. PR's welcome!
* Add jwk.AssignKeyID to automatically assign a `kid` field to a JWK
* Fix jwe.Encrypt / jwe.Decrypt to properly look at the `zip` field
* Change jwe.Message accessors to return []byte, not buffer.Buffer
v1.0.1 - 04 May 2020
* Normalize all JWK serialization to use padding-less base64 encoding (#185)
* Fix edge case unmarshaling openid.AddressClaim within a openid.Token
* Fix edge case unmarshaling jwe.Message
* Export JWK key-specific constants, such as jwk.RSANKey, jwk.SymmetricOctetsKey, etc
* Remove some unused code
v1.0.0 - 03 May 2020
* All packages (`jws`, `jwe`, `jwk`, `jwt`) have all been reworked from
the ground-up.
* These packages now hide the actual implementation of the main structs behind an interface.
* Header/Token structs must now be instantiated using proper constructors
(most notably, json.Unmarshal will miserably fail if you just pass
and empty interface via `xxx.Token` or similar)
* Token/Header interfaces are now more or less standardized.
The following API should be consistent between all relevant packages:
* New()
* Get()
* Set()
* Remove()
* Iterate()
* Walk()
* AsMap()
* Oft-used fields are no longer directly accessible:
e.g. `token.KeyID = v` is no longer valid. You must set using `Set`
(and `Remove`, if you are removing it), and use either `Get` or
one of the utility methods such as `token.KeyID()`
* Many helper functions and structs have been unexported. They were never
meant to be anything useful for end-users, and hopefully it does not
cause any problems.
* Most errors type/instances have been removed from the public API
* `jwt` package can now work with different token types, such as OpenID tokens.
* `token.Sign` and `token.Verify` have been changed from methods to
package functions `jwt.Sign` and `jwt.Verify`, to allow different
types of tokens to be passed to the same logic.
* Added a custom token type in `openid` sub-package to make it easier to
work with OpenID claims
* `jwt.Parse` (and its siblings) now accept `jwt.WithOpenIDClaims()`
* `jwe` API has been reworked:
* `MultiEncrypt` has been removed.
* Serializer structs have been removed. Now you just need to call
`jwe.Compact` or `jwe.JSON`
* `jwk` API has been reworked:
* `jwk.ParseKey` has been added
* `jwk.Materialize` has been renamed to `Raw()`. A new corresponding
method to initialize the key from a raw key (RSA/ECDSA/byte keys)
called `FromRaw()` has also been added, which makes a nice pair.
* `jws` API has been reworked
* CI has been changed from Travis CI to Github Actions, and tests now
include linting via `golangci-lint`
v0.9.2 - 15 Apr 2020
* Maintenance release to protect users from upcoming breaking changes
v0.9.1 - 27 Feb 2020
* Fix error wrapping in certain cases
* Add Claims(), Walk(), and AsMap() to iterate claims, as well as
getting the entire data out as a single map
* Work with alternate base64 encodings when decoding
v0.9.0 - 22 May 2019
* Start tagging versions for good measure.

View File

@ -1,134 +0,0 @@
# github.com/lestrrat-go/jwx ![](https://github.com/lestrrat-go/jwx/workflows/CI/badge.svg) [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx) [![codecov.io](http://codecov.io/github/lestrrat-go/jwx/coverage.svg?branch=main)](http://codecov.io/github/lestrrat-go/jwx?branch=main)
Various libraries implementing various JWx technologies. Please click on the package names in the table below to find the synopsis/description for each package.
If you are using this module in your product or your company, please add your product and/or company name in the [Wiki](https://github.com/lestrrat-go/jwx/wiki/Users)! It really helps keeping up our motivation.
| Package name | Notes |
|-----------------------------------------------------------|-------------------------------------------------|
| [jwt](https://github.com/lestrrat-go/jwx/tree/main/jwt) | [RFC 7519](https://tools.ietf.org/html/rfc7519) |
| [jwk](https://github.com/lestrrat-go/jwx/tree/main/jwk) | [RFC 7517](https://tools.ietf.org/html/rfc7517) + [RFC 7638](https://tools.ietf.org/html/rfc7638) |
| [jwa](https://github.com/lestrrat-go/jwx/tree/main/jwa) | [RFC 7518](https://tools.ietf.org/html/rfc7518) |
| [jws](https://github.com/lestrrat-go/jwx/tree/main/jws) | [RFC 7515](https://tools.ietf.org/html/rfc7515) + [RFC 7797](https://tools.ietf.org/html/rfc7797) |
| [jwe](https://github.com/lestrrat-go/jwx/tree/main/jwe) | [RFC 7516](https://tools.ietf.org/html/rfc7516) |
# How to Use
* [API documentation](https://pkg.go.dev/github.com/lestrrat-go/jwx)
* [How-to style documentation](./docs)
* [Runnable Examples](./examples)
# Description
## History
My goal was to write a server that heavily uses JWK and JWT. At first glance
the libraries that already exist seemed sufficient, but soon I realized that
1. To completely implement the protocols, I needed the entire JWT, JWK, JWS, JWE (and JWA, by necessity).
2. Most of the libraries that existed only deal with a subset of the various JWx specifications that were necessary to implement their specific needs
For example, a certain library looks like it had most of JWS, JWE, JWK covered, but then it lacked the ability to include private claims in its JWT responses. Another library had support of all the private claims, but completely lacked in its flexibility to generate various different response formats.
Because I was writing the server side (and the client side for testing), I needed the *entire* JOSE toolset to properly implement my server, **and** they needed to be *flexible* enough to fulfill the entire spec that I was writing.
So here's `github.com/lestrrat-go/jwx`. This library is extensible, customizable, and hopefully well organized to the point that it is easy for you to slice and dice it.
## Why would I use this library?
There are several other major Go modules that handle JWT and related data formats,
so why should you use this library?
From a purely functional perspective, the only major difference is this:
Whereas most other projects only deal with what they seem necessary to handle
JWTs, this module handles the **_entire_** spectrum of JWS, JWE, JWK, and JWT.
That is, if you need to not only parse JWTs, but also to control JWKs, or
if you need to handle payloads that are NOT JWTs, you should probably consider
using this module. You should also note that JWT is built _on top_ of those
other technologies. You simply cannot have a complete JWT package without
implementing the entirety of JWS/JWS/JWK, which this library does.
Next, from an implementation perspective, this module differs significantly
from others in that it tries very hard to expose only the APIs, and not the
internal data. For example, individual JWT claims are not accessible through
struct field lookups. You need to use one of the getter methods.
This is because this library takes the stance that the end user is fully capable
and even willing to shoot themselves on the foot when presented with a lax
API. By making sure that users do not have access to open structs, we can protect
users from doing silly things like creating _incomplete_ structs, or access the
structs concurrently without any protection. This structure also allows
us to put extra smarts in the structs, such as doing the right thing when
you want to parse / write custom fields (this module does not require the user
to specify alternate structs to parse objects with custom fields)
In the end I think it comes down to your usage pattern, and priorities.
Some general guidelines that come to mind are:
* If you want a single library to handle everything JWx, such as using JWE, JWK, JWS, handling [auto-refreshing JWKs](https://github.com/lestrrat-go/jwx/blob/main/docs/04-jwk.md#auto-refreshing-remote-keys), use this module.
* If you want to honor all possible custom fields transparently, use this module.
* If you want a standardized clean API, use this module.
Otherwise, feel free to choose something else.
# Command Line Tool
Since v1.1.1 we have a command line tool `jwx` (*). With `jwx` you can create JWKs (from PEM files, even), sign and verify JWS message, encrypt and decrypt JWE messages, etc.
(*) Okay, it existed since a long time ago, but it was never useful.
## Installation
```
go install github.com/lestrrat-go/jwx/cmd/jwx
```
# Caveats
## Backwards Compatibility Notice
### Users of github.com/lestrrat/go-jwx
Uh, why are you using such an ancient version? You know that repository is archived for a reason, yeah? Please use the new version.
### Pre-1.0.0 users
The API has been reworked quite substantially between pre- and post 1.0.0 releases. Please check out the [Changes](./Changes) file (or the [diff](https://github.com/lestrrat-go/jwx/compare/v0.9.2...v1.0.0), if you are into that sort of thing)
### v1.0.x users
The API has gone under some changes for v1.1.0. If you are upgrading, you might want to read the relevant parts in the [Changes](./Changes) file.
# Contributions
## Issues
For bug reports and feature requests, please try to follow the issue templates as much as possible.
For either bug reports or feature requests, failing tests are even better.
## Pull Requests
Please make sure to include tests that excercise the changes you made.
If you are editing auto-generated files (those files with the `_gen.go` suffix, please make sure that you do the following:
1. Edit the generator, not the generated files (e.g. internal/cmd/genreadfile/main.go)
2. Run `make generate` (or `go generate`) to generate the new code
3. Commit _both_ the generator _and_ the generated files
## Discussions / Usage
Please try [discussions](https://github.com/lestrrat-go/jwx/discussions) first.
# Related Modules
* [github.com/jwx-go/crypto-signer/gcp](https://github.com/jwx-go/crypto-signer/tree/main/gcp) - GCP KMS wrapper that implements [`crypto.Signer`](https://pkg.go.dev/crypto#Signer)
* [github.com/jwx-go/crypto-signer/aws](https://github.com/jwx-go/crypto-signer/tree/main/aws) - AWS KMS wrapper that implements [`crypto.Signer`](https://pkg.go.dev/crypto#Signer)
# Credits
* Initial work on this library was generously sponsored by HDE Inc (https://www.hde.co.jp)
* Lots of code, especially JWE was taken from go-jose library (https://github.com/square/go-jose)
* Lots of individual contributors have helped this project over the years. Thank each and everyone of you very much.

View File

@ -1,14 +0,0 @@
#!/bin/bash
# Script to perform code generation. This exists to overcome
# the fact that go:generate doesn't really allow you to change directories
set -e
pushd internal/cmd/genreadfile
go build -o genreadfile main.go
popd
./internal/cmd/genreadfile/genreadfile
rm internal/cmd/genreadfile/genreadfile

View File

@ -1,3 +0,0 @@
# JWA [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/jwa.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwa)
Package [github.com/lestrrat-go/jwx/jwa](./jwa) defines the various algorithm described in [RFC7518](https://tools.ietf.org/html/rfc7518)

View File

@ -1,14 +0,0 @@
#!/bin/bash
# Script to perform code generation. This exists to overcome
# the fact that go:generate doesn't really allow you to change directories
set -e
pushd internal/cmd/gentypes
go build -o gentypes main.go
popd
./internal/cmd/gentypes/gentypes -objects=internal/cmd/gentypes/objects.yml
rm internal/cmd/gentypes/gentypes

View File

@ -1,4 +0,0 @@
//go:generate ./gen.sh
// Package jwa defines the various algorithm described in https://tools.ietf.org/html/rfc7518
package jwa

View File

@ -1,41 +0,0 @@
package jwe
import (
"bytes"
"compress/flate"
"io/ioutil"
"github.com/lestrrat-go/jwx/internal/pool"
"github.com/lestrrat-go/jwx/jwa"
"github.com/pkg/errors"
)
func uncompress(plaintext []byte) ([]byte, error) {
return ioutil.ReadAll(flate.NewReader(bytes.NewReader(plaintext)))
}
func compress(plaintext []byte, alg jwa.CompressionAlgorithm) ([]byte, error) {
if alg == jwa.NoCompress {
return plaintext, nil
}
buf := pool.GetBytesBuffer()
defer pool.ReleaseBytesBuffer(buf)
w, _ := flate.NewWriter(buf, 1)
in := plaintext
for len(in) > 0 {
n, err := w.Write(in)
if err != nil {
return nil, errors.Wrap(err, `failed to write to compression writer`)
}
in = in[n:]
}
if err := w.Close(); err != nil {
return nil, errors.Wrap(err, "failed to close compression writer")
}
ret := make([]byte, buf.Len())
copy(ret, buf.Bytes())
return ret, nil
}

View File

@ -1,145 +0,0 @@
package jwe
import (
"context"
"sync"
"github.com/lestrrat-go/jwx/internal/base64"
"github.com/lestrrat-go/jwx/jwa"
"github.com/pkg/errors"
)
var encryptCtxPool = sync.Pool{
New: func() interface{} {
return &encryptCtx{}
},
}
func getEncryptCtx() *encryptCtx {
//nolint:forcetypeassert
return encryptCtxPool.Get().(*encryptCtx)
}
func releaseEncryptCtx(ctx *encryptCtx) {
ctx.protected = nil
ctx.contentEncrypter = nil
ctx.generator = nil
ctx.keyEncrypters = nil
ctx.compress = jwa.NoCompress
encryptCtxPool.Put(ctx)
}
// Encrypt takes the plaintext and encrypts into a JWE message.
func (e encryptCtx) Encrypt(plaintext []byte) (*Message, error) {
bk, err := e.generator.Generate()
if err != nil {
return nil, errors.Wrap(err, "failed to generate key")
}
cek := bk.Bytes()
if e.protected == nil {
// shouldn't happen, but...
e.protected = NewHeaders()
}
if err := e.protected.Set(ContentEncryptionKey, e.contentEncrypter.Algorithm()); err != nil {
return nil, errors.Wrap(err, `failed to set "enc" in protected header`)
}
compression := e.compress
if compression != jwa.NoCompress {
if err := e.protected.Set(CompressionKey, compression); err != nil {
return nil, errors.Wrap(err, `failed to set "zip" in protected header`)
}
}
// In JWE, multiple recipients may exist -- they receive an
// encrypted version of the CEK, using their key encryption
// algorithm of choice.
recipients := make([]Recipient, len(e.keyEncrypters))
for i, enc := range e.keyEncrypters {
r := NewRecipient()
if err := r.Headers().Set(AlgorithmKey, enc.Algorithm()); err != nil {
return nil, errors.Wrap(err, "failed to set header")
}
if v := enc.KeyID(); v != "" {
if err := r.Headers().Set(KeyIDKey, v); err != nil {
return nil, errors.Wrap(err, "failed to set header")
}
}
enckey, err := enc.Encrypt(cek)
if err != nil {
return nil, errors.Wrap(err, `failed to encrypt key`)
}
if enc.Algorithm() == jwa.ECDH_ES || enc.Algorithm() == jwa.DIRECT {
if len(e.keyEncrypters) > 1 {
return nil, errors.Errorf("unable to support multiple recipients for ECDH-ES")
}
cek = enckey.Bytes()
} else {
if err := r.SetEncryptedKey(enckey.Bytes()); err != nil {
return nil, errors.Wrap(err, "failed to set encrypted key")
}
}
if hp, ok := enckey.(populater); ok {
if err := hp.Populate(r.Headers()); err != nil {
return nil, errors.Wrap(err, "failed to populate")
}
}
recipients[i] = r
}
// If there's only one recipient, you want to include that in the
// protected header
if len(recipients) == 1 {
h, err := e.protected.Merge(context.TODO(), recipients[0].Headers())
if err != nil {
return nil, errors.Wrap(err, "failed to merge protected headers")
}
e.protected = h
}
aad, err := e.protected.Encode()
if err != nil {
return nil, errors.Wrap(err, "failed to base64 encode protected headers")
}
plaintext, err = compress(plaintext, compression)
if err != nil {
return nil, errors.Wrap(err, `failed to compress payload before encryption`)
}
// ...on the other hand, there's only one content cipher.
iv, ciphertext, tag, err := e.contentEncrypter.Encrypt(cek, plaintext, aad)
if err != nil {
return nil, errors.Wrap(err, "failed to encrypt payload")
}
msg := NewMessage()
decodedAad, err := base64.Decode(aad)
if err != nil {
return nil, errors.Wrap(err, "failed to decode base64")
}
if err := msg.Set(AuthenticatedDataKey, decodedAad); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, AuthenticatedDataKey)
}
if err := msg.Set(CipherTextKey, ciphertext); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, CipherTextKey)
}
if err := msg.Set(InitializationVectorKey, iv); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, InitializationVectorKey)
}
if err := msg.Set(ProtectedHeadersKey, e.protected); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, ProtectedHeadersKey)
}
if err := msg.Set(RecipientsKey, recipients); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, RecipientsKey)
}
if err := msg.Set(TagKey, tag); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, TagKey)
}
return msg, nil
}

View File

@ -1,14 +0,0 @@
#!/bin/bash
# Script to perform code generation. This exists to overcome
# the fact that go:generate doesn't really allow you to change directories
set -e
pushd internal/cmd/genheader
go build -o genheader main.go
popd
./internal/cmd/genheader/genheader -objects=internal/cmd/genheader/objects.yml
rm internal/cmd/genheader/genheader

View File

@ -1,101 +0,0 @@
package jwe
import (
"github.com/lestrrat-go/iter/mapiter"
"github.com/lestrrat-go/jwx/internal/iter"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwe/internal/keyenc"
"github.com/lestrrat-go/jwx/jwe/internal/keygen"
)
// Recipient holds the encrypted key and hints to decrypt the key
type Recipient interface {
Headers() Headers
EncryptedKey() []byte
SetHeaders(Headers) error
SetEncryptedKey([]byte) error
}
type stdRecipient struct {
headers Headers
encryptedKey []byte
}
// Message contains the entire encrypted JWE message. You should not
// expect to use Message for anything other than inspecting the
// state of an encrypted message. This is because encryption is
// highly context sensitive, and once we parse the original payload
// into an object, we may not always be able to recreate the exact
// context in which the encryption happened.
//
// For example, it is totally valid for if the protected header's
// integrity was calculated using a non-standard line breaks:
//
// {"a dummy":
// "protected header"}
//
// Once parsed, though, we can only serialize the protected header as:
//
// {"a dummy":"protected header"}
//
// which would obviously result in a contradicting integrity value
// if we tried to re-calculate it from a parsed message.
//nolint:govet
type Message struct {
authenticatedData []byte
cipherText []byte
initializationVector []byte
tag []byte
recipients []Recipient
protectedHeaders Headers
unprotectedHeaders Headers
// These two fields below are not available for the public consumers of this object.
// rawProtectedHeaders stores the original protected header buffer
rawProtectedHeaders []byte
// storeProtectedHeaders is a hint to be used in UnmarshalJSON().
// When this flag is true, UnmarshalJSON() will populate the
// rawProtectedHeaders field
storeProtectedHeaders bool
}
// contentEncrypter encrypts the content using the content using the
// encrypted key
type contentEncrypter interface {
Algorithm() jwa.ContentEncryptionAlgorithm
Encrypt([]byte, []byte, []byte) ([]byte, []byte, []byte, error)
}
//nolint:govet
type encryptCtx struct {
keyEncrypters []keyenc.Encrypter
protected Headers
contentEncrypter contentEncrypter
generator keygen.Generator
compress jwa.CompressionAlgorithm
}
// populater is an interface for things that may modify the
// JWE header. e.g. ByteWithECPrivateKey
type populater interface {
Populate(keygen.Setter) error
}
type Visitor = iter.MapVisitor
type VisitorFunc = iter.MapVisitorFunc
type HeaderPair = mapiter.Pair
type Iterator = mapiter.Iterator
// PostParser is used in conjunction with jwe.WithPostParser().
// This hook is called right after the JWE message has been parsed
// but before the actual decryption takes place during `jwe.Decrypt()`.
type PostParser interface {
PostParse(DecryptCtx) error
}
// PostParseFunc is a PostParser that is represented by a single function
type PostParseFunc func(DecryptCtx) error
func (fn PostParseFunc) PostParse(ctx DecryptCtx) error {
return fn(ctx)
}

View File

@ -1,23 +0,0 @@
// Automatically generated by internal/cmd/genreadfile/main.go. DO NOT EDIT
package jwe
import "os"
// ReadFileOption describes options that can be passed to ReadFile.
// Currently there are no options available that can be passed to ReadFile, but
// it is provided here for anticipated future additions
type ReadFileOption interface {
Option
readFileOption()
}
func ReadFile(path string, _ ...ReadFileOption) (*Message, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ParseReader(f)
}

View File

@ -1,377 +0,0 @@
//go:generate ./gen.sh
// Package jwe implements JWE as described in https://tools.ietf.org/html/rfc7516
package jwe
import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"io"
"io/ioutil"
"github.com/lestrrat-go/jwx/internal/base64"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/lestrrat-go/jwx/internal/keyconv"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwe/internal/content_crypt"
"github.com/lestrrat-go/jwx/jwe/internal/keyenc"
"github.com/lestrrat-go/jwx/jwe/internal/keygen"
"github.com/lestrrat-go/jwx/x25519"
"github.com/pkg/errors"
)
var registry = json.NewRegistry()
// Encrypt takes the plaintext payload and encrypts it in JWE compact format.
// `key` should be a public key, and it may be a raw key (e.g. rsa.PublicKey) or a jwk.Key
//
// Encrypt currently does not support multi-recipient messages.
func Encrypt(payload []byte, keyalg jwa.KeyEncryptionAlgorithm, key interface{}, contentalg jwa.ContentEncryptionAlgorithm, compressalg jwa.CompressionAlgorithm, options ...EncryptOption) ([]byte, error) {
var protected Headers
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identProtectedHeader{}:
protected = option.Value().(Headers)
}
}
if protected == nil {
protected = NewHeaders()
}
contentcrypt, err := content_crypt.NewGeneric(contentalg)
if err != nil {
return nil, errors.Wrap(err, `failed to create AES encrypter`)
}
var keyID string
if jwkKey, ok := key.(jwk.Key); ok {
keyID = jwkKey.KeyID()
var raw interface{}
if err := jwkKey.Raw(&raw); err != nil {
return nil, errors.Wrapf(err, `failed to retrieve raw key out of %T`, key)
}
key = raw
}
var enc keyenc.Encrypter
switch keyalg {
case jwa.RSA1_5:
var pubkey rsa.PublicKey
if err := keyconv.RSAPublicKey(&pubkey, key); err != nil {
return nil, errors.Wrapf(err, "failed to generate public key from key (%T)", key)
}
enc, err = keyenc.NewRSAPKCSEncrypt(keyalg, &pubkey)
if err != nil {
return nil, errors.Wrap(err, "failed to create RSA PKCS encrypter")
}
case jwa.RSA_OAEP, jwa.RSA_OAEP_256:
var pubkey rsa.PublicKey
if err := keyconv.RSAPublicKey(&pubkey, key); err != nil {
return nil, errors.Wrapf(err, "failed to generate public key from key (%T)", key)
}
enc, err = keyenc.NewRSAOAEPEncrypt(keyalg, &pubkey)
if err != nil {
return nil, errors.Wrap(err, "failed to create RSA OAEP encrypter")
}
case jwa.A128KW, jwa.A192KW, jwa.A256KW,
jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW,
jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW:
sharedkey, ok := key.([]byte)
if !ok {
return nil, errors.New("invalid key: []byte required")
}
switch keyalg {
case jwa.A128KW, jwa.A192KW, jwa.A256KW:
enc, err = keyenc.NewAES(keyalg, sharedkey)
case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW:
enc, err = keyenc.NewPBES2Encrypt(keyalg, sharedkey)
default:
enc, err = keyenc.NewAESGCMEncrypt(keyalg, sharedkey)
}
if err != nil {
return nil, errors.Wrap(err, "failed to create key wrap encrypter")
}
// NOTE: there was formerly a restriction, introduced
// in PR #26, which disallowed certain key/content
// algorithm combinations. This seemed bogus, and
// interop with the jose tool demonstrates it.
case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW:
var keysize int
switch keyalg {
case jwa.ECDH_ES:
// https://tools.ietf.org/html/rfc7518#page-15
// In Direct Key Agreement mode, the output of the Concat KDF MUST be a
// key of the same length as that used by the "enc" algorithm.
keysize = contentcrypt.KeySize()
case jwa.ECDH_ES_A128KW:
keysize = 16
case jwa.ECDH_ES_A192KW:
keysize = 24
case jwa.ECDH_ES_A256KW:
keysize = 32
}
switch key := key.(type) {
case x25519.PublicKey:
enc, err = keyenc.NewECDHESEncrypt(keyalg, contentalg, keysize, key)
default:
var pubkey ecdsa.PublicKey
if err := keyconv.ECDSAPublicKey(&pubkey, key); err != nil {
return nil, errors.Wrapf(err, "failed to generate public key from key (%T)", key)
}
enc, err = keyenc.NewECDHESEncrypt(keyalg, contentalg, keysize, &pubkey)
}
if err != nil {
return nil, errors.Wrap(err, "failed to create ECDHS key wrap encrypter")
}
case jwa.DIRECT:
sharedkey, ok := key.([]byte)
if !ok {
return nil, errors.New("invalid key: []byte required")
}
enc, _ = keyenc.NewNoop(keyalg, sharedkey)
default:
return nil, errors.Errorf(`invalid key encryption algorithm (%s)`, keyalg)
}
if keyID != "" {
enc.SetKeyID(keyID)
}
keysize := contentcrypt.KeySize()
encctx := getEncryptCtx()
defer releaseEncryptCtx(encctx)
encctx.protected = protected
encctx.contentEncrypter = contentcrypt
encctx.generator = keygen.NewRandom(keysize)
encctx.keyEncrypters = []keyenc.Encrypter{enc}
encctx.compress = compressalg
msg, err := encctx.Encrypt(payload)
if err != nil {
return nil, errors.Wrap(err, "failed to encrypt payload")
}
return Compact(msg)
}
// DecryptCtx is used internally when jwe.Decrypt is called, and is
// passed for hooks that you may pass into it.
//
// Regular users should not have to touch this object, but if you need advanced handling
// of messages, you might have to use it. Only use it when you really
// understand how JWE processing works in this library.
type DecryptCtx interface {
Algorithm() jwa.KeyEncryptionAlgorithm
SetAlgorithm(jwa.KeyEncryptionAlgorithm)
Key() interface{}
SetKey(interface{})
Message() *Message
SetMessage(*Message)
}
type decryptCtx struct {
alg jwa.KeyEncryptionAlgorithm
key interface{}
msg *Message
}
func (ctx *decryptCtx) Algorithm() jwa.KeyEncryptionAlgorithm {
return ctx.alg
}
func (ctx *decryptCtx) SetAlgorithm(v jwa.KeyEncryptionAlgorithm) {
ctx.alg = v
}
func (ctx *decryptCtx) Key() interface{} {
return ctx.key
}
func (ctx *decryptCtx) SetKey(v interface{}) {
ctx.key = v
}
func (ctx *decryptCtx) Message() *Message {
return ctx.msg
}
func (ctx *decryptCtx) SetMessage(m *Message) {
ctx.msg = m
}
// Decrypt takes the key encryption algorithm and the corresponding
// key to decrypt the JWE message, and returns the decrypted payload.
// The JWE message can be either compact or full JSON format.
//
// `key` must be a private key. It can be either in its raw format (e.g. *rsa.PrivateKey) or a jwk.Key
func Decrypt(buf []byte, alg jwa.KeyEncryptionAlgorithm, key interface{}, options ...DecryptOption) ([]byte, error) {
var ctx decryptCtx
ctx.key = key
ctx.alg = alg
var dst *Message
var postParse PostParser
//nolint:forcetypeassert
for _, option := range options {
switch option.Ident() {
case identMessage{}:
dst = option.Value().(*Message)
case identPostParser{}:
postParse = option.Value().(PostParser)
}
}
msg, err := parseJSONOrCompact(buf, true)
if err != nil {
return nil, errors.Wrap(err, "failed to parse buffer for Decrypt")
}
ctx.msg = msg
if postParse != nil {
if err := postParse.PostParse(&ctx); err != nil {
return nil, errors.Wrap(err, `failed to execute PostParser hook`)
}
}
payload, err := doDecryptCtx(&ctx)
if err != nil {
return nil, errors.Wrap(err, `failed to decrypt message`)
}
if dst != nil {
*dst = *msg
dst.rawProtectedHeaders = nil
dst.storeProtectedHeaders = false
}
return payload, nil
}
// Parse parses the JWE message into a Message object. The JWE message
// can be either compact or full JSON format.
func Parse(buf []byte) (*Message, error) {
return parseJSONOrCompact(buf, false)
}
func parseJSONOrCompact(buf []byte, storeProtectedHeaders bool) (*Message, error) {
buf = bytes.TrimSpace(buf)
if len(buf) == 0 {
return nil, errors.New("empty buffer")
}
if buf[0] == '{' {
return parseJSON(buf, storeProtectedHeaders)
}
return parseCompact(buf, storeProtectedHeaders)
}
// ParseString is the same as Parse, but takes a string.
func ParseString(s string) (*Message, error) {
return Parse([]byte(s))
}
// ParseReader is the same as Parse, but takes an io.Reader.
func ParseReader(src io.Reader) (*Message, error) {
buf, err := ioutil.ReadAll(src)
if err != nil {
return nil, errors.Wrap(err, `failed to read from io.Reader`)
}
return Parse(buf)
}
func parseJSON(buf []byte, storeProtectedHeaders bool) (*Message, error) {
m := NewMessage()
m.storeProtectedHeaders = storeProtectedHeaders
if err := json.Unmarshal(buf, &m); err != nil {
return nil, errors.Wrap(err, "failed to parse JSON")
}
return m, nil
}
func parseCompact(buf []byte, storeProtectedHeaders bool) (*Message, error) {
parts := bytes.Split(buf, []byte{'.'})
if len(parts) != 5 {
return nil, errors.Errorf(`compact JWE format must have five parts (%d)`, len(parts))
}
hdrbuf, err := base64.Decode(parts[0])
if err != nil {
return nil, errors.Wrap(err, `failed to parse first part of compact form`)
}
protected := NewHeaders()
if err := json.Unmarshal(hdrbuf, protected); err != nil {
return nil, errors.Wrap(err, "failed to parse header JSON")
}
ivbuf, err := base64.Decode(parts[2])
if err != nil {
return nil, errors.Wrap(err, "failed to base64 decode iv")
}
ctbuf, err := base64.Decode(parts[3])
if err != nil {
return nil, errors.Wrap(err, "failed to base64 decode content")
}
tagbuf, err := base64.Decode(parts[4])
if err != nil {
return nil, errors.Wrap(err, "failed to base64 decode tag")
}
m := NewMessage()
if err := m.Set(CipherTextKey, ctbuf); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, CipherTextKey)
}
if err := m.Set(InitializationVectorKey, ivbuf); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, InitializationVectorKey)
}
if err := m.Set(ProtectedHeadersKey, protected); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, ProtectedHeadersKey)
}
if err := m.makeDummyRecipient(string(parts[1]), protected); err != nil {
return nil, errors.Wrap(err, `failed to setup recipient`)
}
if err := m.Set(TagKey, tagbuf); err != nil {
return nil, errors.Wrapf(err, `failed to set %s`, TagKey)
}
if storeProtectedHeaders {
// This is later used for decryption.
m.rawProtectedHeaders = parts[0]
}
return m, nil
}
// RegisterCustomField allows users to specify that a private field
// be decoded as an instance of the specified type. This option has
// a global effect.
//
// For example, suppose you have a custom field `x-birthday`, which
// you want to represent as a string formatted in RFC3339 in JSON,
// but want it back as `time.Time`.
//
// In that case you would register a custom field as follows
//
// jwe.RegisterCustomField(`x-birthday`, timeT)
//
// Then `hdr.Get("x-birthday")` will still return an `interface{}`,
// but you can convert its type to `time.Time`
//
// bdayif, _ := hdr.Get(`x-birthday`)
// bday := bdayif.(time.Time)
func RegisterCustomField(name string, object interface{}) {
registry.Register(name, object)
}

View File

@ -1,648 +0,0 @@
package jwe
import (
"context"
"crypto/ecdsa"
"fmt"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/lestrrat-go/jwx/internal/pool"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/internal/base64"
"github.com/lestrrat-go/jwx/jwa"
"github.com/pkg/errors"
)
// NewRecipient creates a Recipient object
func NewRecipient() Recipient {
return &stdRecipient{
headers: NewHeaders(),
}
}
func (r *stdRecipient) SetHeaders(h Headers) error {
r.headers = h
return nil
}
func (r *stdRecipient) SetEncryptedKey(v []byte) error {
r.encryptedKey = v
return nil
}
func (r *stdRecipient) Headers() Headers {
return r.headers
}
func (r *stdRecipient) EncryptedKey() []byte {
return r.encryptedKey
}
type recipientMarshalProxy struct {
Headers Headers `json:"header"`
EncryptedKey string `json:"encrypted_key"`
}
func (r *stdRecipient) UnmarshalJSON(buf []byte) error {
var proxy recipientMarshalProxy
proxy.Headers = NewHeaders()
if err := json.Unmarshal(buf, &proxy); err != nil {
return errors.Wrap(err, `failed to unmarshal json into recipient`)
}
r.headers = proxy.Headers
decoded, err := base64.DecodeString(proxy.EncryptedKey)
if err != nil {
return errors.Wrap(err, `failed to decode "encrypted_key"`)
}
r.encryptedKey = decoded
return nil
}
func (r *stdRecipient) MarshalJSON() ([]byte, error) {
buf := pool.GetBytesBuffer()
defer pool.ReleaseBytesBuffer(buf)
buf.WriteString(`{"header":`)
hdrbuf, err := r.headers.MarshalJSON()
if err != nil {
return nil, errors.Wrap(err, `failed to marshal recipient header`)
}
buf.Write(hdrbuf)
buf.WriteString(`,"encrypted_key":"`)
buf.WriteString(base64.EncodeToString(r.encryptedKey))
buf.WriteString(`"}`)
ret := make([]byte, buf.Len())
copy(ret, buf.Bytes())
return ret, nil
}
// NewMessage creates a new message
func NewMessage() *Message {
return &Message{}
}
func (m *Message) AuthenticatedData() []byte {
return m.authenticatedData
}
func (m *Message) CipherText() []byte {
return m.cipherText
}
func (m *Message) InitializationVector() []byte {
return m.initializationVector
}
func (m *Message) Tag() []byte {
return m.tag
}
func (m *Message) ProtectedHeaders() Headers {
return m.protectedHeaders
}
func (m *Message) Recipients() []Recipient {
return m.recipients
}
func (m *Message) UnprotectedHeaders() Headers {
return m.unprotectedHeaders
}
const (
AuthenticatedDataKey = "aad"
CipherTextKey = "ciphertext"
CountKey = "p2c"
InitializationVectorKey = "iv"
ProtectedHeadersKey = "protected"
RecipientsKey = "recipients"
SaltKey = "p2s"
TagKey = "tag"
UnprotectedHeadersKey = "unprotected"
HeadersKey = "header"
EncryptedKeyKey = "encrypted_key"
)
func (m *Message) Set(k string, v interface{}) error {
switch k {
case AuthenticatedDataKey:
buf, ok := v.([]byte)
if !ok {
return errors.Errorf(`invalid value %T for %s key`, v, AuthenticatedDataKey)
}
m.authenticatedData = buf
case CipherTextKey:
buf, ok := v.([]byte)
if !ok {
return errors.Errorf(`invalid value %T for %s key`, v, CipherTextKey)
}
m.cipherText = buf
case InitializationVectorKey:
buf, ok := v.([]byte)
if !ok {
return errors.Errorf(`invalid value %T for %s key`, v, InitializationVectorKey)
}
m.initializationVector = buf
case ProtectedHeadersKey:
cv, ok := v.(Headers)
if !ok {
return errors.Errorf(`invalid value %T for %s key`, v, ProtectedHeadersKey)
}
m.protectedHeaders = cv
case RecipientsKey:
cv, ok := v.([]Recipient)
if !ok {
return errors.Errorf(`invalid value %T for %s key`, v, RecipientsKey)
}
m.recipients = cv
case TagKey:
buf, ok := v.([]byte)
if !ok {
return errors.Errorf(`invalid value %T for %s key`, v, TagKey)
}
m.tag = buf
case UnprotectedHeadersKey:
cv, ok := v.(Headers)
if !ok {
return errors.Errorf(`invalid value %T for %s key`, v, UnprotectedHeadersKey)
}
m.unprotectedHeaders = cv
default:
if m.unprotectedHeaders == nil {
m.unprotectedHeaders = NewHeaders()
}
return m.unprotectedHeaders.Set(k, v)
}
return nil
}
type messageMarshalProxy struct {
AuthenticatedData string `json:"aad,omitempty"`
CipherText string `json:"ciphertext"`
InitializationVector string `json:"iv,omitempty"`
ProtectedHeaders json.RawMessage `json:"protected"`
Recipients []json.RawMessage `json:"recipients,omitempty"`
Tag string `json:"tag,omitempty"`
UnprotectedHeaders Headers `json:"unprotected,omitempty"`
// For flattened structure. Headers is NOT a Headers type,
// so that we can detect its presence by checking proxy.Headers != nil
Headers json.RawMessage `json:"header,omitempty"`
EncryptedKey string `json:"encrypted_key,omitempty"`
}
func (m *Message) MarshalJSON() ([]byte, error) {
// This is slightly convoluted, but we need to encode the
// protected headers, so we do it by hand
buf := pool.GetBytesBuffer()
defer pool.ReleaseBytesBuffer(buf)
enc := json.NewEncoder(buf)
fmt.Fprintf(buf, `{`)
var wrote bool
if aad := m.AuthenticatedData(); len(aad) > 0 {
wrote = true
fmt.Fprintf(buf, `%#v:`, AuthenticatedDataKey)
if err := enc.Encode(base64.EncodeToString(aad)); err != nil {
return nil, errors.Wrapf(err, `failed to encode %s field`, AuthenticatedDataKey)
}
}
if cipherText := m.CipherText(); len(cipherText) > 0 {
if wrote {
fmt.Fprintf(buf, `,`)
}
wrote = true
fmt.Fprintf(buf, `%#v:`, CipherTextKey)
if err := enc.Encode(base64.EncodeToString(cipherText)); err != nil {
return nil, errors.Wrapf(err, `failed to encode %s field`, CipherTextKey)
}
}
if iv := m.InitializationVector(); len(iv) > 0 {
if wrote {
fmt.Fprintf(buf, `,`)
}
wrote = true
fmt.Fprintf(buf, `%#v:`, InitializationVectorKey)
if err := enc.Encode(base64.EncodeToString(iv)); err != nil {
return nil, errors.Wrapf(err, `failed to encode %s field`, InitializationVectorKey)
}
}
if h := m.ProtectedHeaders(); h != nil {
encodedHeaders, err := h.Encode()
if err != nil {
return nil, errors.Wrap(err, `failed to encode protected headers`)
}
if len(encodedHeaders) > 2 {
if wrote {
fmt.Fprintf(buf, `,`)
}
wrote = true
fmt.Fprintf(buf, `%#v:%#v`, ProtectedHeadersKey, string(encodedHeaders))
}
}
if recipients := m.Recipients(); len(recipients) > 0 {
if wrote {
fmt.Fprintf(buf, `,`)
}
if len(recipients) == 1 { // Use flattened format
fmt.Fprintf(buf, `%#v:`, HeadersKey)
if err := enc.Encode(recipients[0].Headers()); err != nil {
return nil, errors.Wrapf(err, `failed to encode %s field`, HeadersKey)
}
if ek := recipients[0].EncryptedKey(); len(ek) > 0 {
fmt.Fprintf(buf, `,%#v:`, EncryptedKeyKey)
if err := enc.Encode(base64.EncodeToString(ek)); err != nil {
return nil, errors.Wrapf(err, `failed to encode %s field`, EncryptedKeyKey)
}
}
} else {
fmt.Fprintf(buf, `%#v:`, RecipientsKey)
if err := enc.Encode(recipients); err != nil {
return nil, errors.Wrapf(err, `failed to encode %s field`, RecipientsKey)
}
}
}
if tag := m.Tag(); len(tag) > 0 {
if wrote {
fmt.Fprintf(buf, `,`)
}
fmt.Fprintf(buf, `%#v:`, TagKey)
if err := enc.Encode(base64.EncodeToString(tag)); err != nil {
return nil, errors.Wrapf(err, `failed to encode %s field`, TagKey)
}
}
if h := m.UnprotectedHeaders(); h != nil {
unprotected, err := json.Marshal(h)
if err != nil {
return nil, errors.Wrap(err, `failed to encode unprotected headers`)
}
if len(unprotected) > 2 {
fmt.Fprintf(buf, `,%#v:%#v`, UnprotectedHeadersKey, string(unprotected))
}
}
fmt.Fprintf(buf, `}`)
ret := make([]byte, buf.Len())
copy(ret, buf.Bytes())
return ret, nil
}
func (m *Message) UnmarshalJSON(buf []byte) error {
var proxy messageMarshalProxy
proxy.UnprotectedHeaders = NewHeaders()
if err := json.Unmarshal(buf, &proxy); err != nil {
return errors.Wrap(err, `failed to unmashal JSON into message`)
}
// Get the string value
var protectedHeadersStr string
if err := json.Unmarshal(proxy.ProtectedHeaders, &protectedHeadersStr); err != nil {
return errors.Wrap(err, `failed to decode protected headers (1)`)
}
// It's now in _quoted_ base64 string. Decode it
protectedHeadersRaw, err := base64.DecodeString(protectedHeadersStr)
if err != nil {
return errors.Wrap(err, "failed to base64 decoded protected headers buffer")
}
h := NewHeaders()
if err := json.Unmarshal(protectedHeadersRaw, h); err != nil {
return errors.Wrap(err, `failed to decode protected headers (2)`)
}
// if this were a flattened message, we would see a "header" and "ciphertext"
// field. TODO: do both of these conditions need to meet, or just one?
if proxy.Headers != nil || len(proxy.EncryptedKey) > 0 {
recipient := NewRecipient()
hdrs := NewHeaders()
if err := json.Unmarshal(proxy.Headers, hdrs); err != nil {
return errors.Wrap(err, `failed to decode headers field`)
}
if err := recipient.SetHeaders(hdrs); err != nil {
return errors.Wrap(err, `failed to set new headers`)
}
if v := proxy.EncryptedKey; len(v) > 0 {
buf, err := base64.DecodeString(v)
if err != nil {
return errors.Wrap(err, `failed to decode encrypted key`)
}
if err := recipient.SetEncryptedKey(buf); err != nil {
return errors.Wrap(err, `failed to set encrypted key`)
}
}
m.recipients = append(m.recipients, recipient)
} else {
for i, recipientbuf := range proxy.Recipients {
recipient := NewRecipient()
if err := json.Unmarshal(recipientbuf, recipient); err != nil {
return errors.Wrapf(err, `failed to decode recipient at index %d`, i)
}
m.recipients = append(m.recipients, recipient)
}
}
if src := proxy.AuthenticatedData; len(src) > 0 {
v, err := base64.DecodeString(src)
if err != nil {
return errors.Wrap(err, `failed to decode "aad"`)
}
m.authenticatedData = v
}
if src := proxy.CipherText; len(src) > 0 {
v, err := base64.DecodeString(src)
if err != nil {
return errors.Wrap(err, `failed to decode "ciphertext"`)
}
m.cipherText = v
}
if src := proxy.InitializationVector; len(src) > 0 {
v, err := base64.DecodeString(src)
if err != nil {
return errors.Wrap(err, `failed to decode "iv"`)
}
m.initializationVector = v
}
if src := proxy.Tag; len(src) > 0 {
v, err := base64.DecodeString(src)
if err != nil {
return errors.Wrap(err, `failed to decode "tag"`)
}
m.tag = v
}
m.protectedHeaders = h
if m.storeProtectedHeaders {
// this is later used for decryption
m.rawProtectedHeaders = base64.Encode(protectedHeadersRaw)
}
if iz, ok := proxy.UnprotectedHeaders.(isZeroer); ok {
if !iz.isZero() {
m.unprotectedHeaders = proxy.UnprotectedHeaders
}
}
if len(m.recipients) == 0 {
if err := m.makeDummyRecipient(proxy.EncryptedKey, m.protectedHeaders); err != nil {
return errors.Wrap(err, `failed to setup recipient`)
}
}
return nil
}
func (m *Message) makeDummyRecipient(enckeybuf string, protected Headers) error {
// Recipients in this case should not contain the content encryption key,
// so move that out
hdrs, err := protected.Clone(context.TODO())
if err != nil {
return errors.Wrap(err, `failed to clone headers`)
}
if err := hdrs.Remove(ContentEncryptionKey); err != nil {
return errors.Wrapf(err, "failed to remove %#v from public header", ContentEncryptionKey)
}
enckey, err := base64.DecodeString(enckeybuf)
if err != nil {
return errors.Wrap(err, `failed to decode encrypted key`)
}
if err := m.Set(RecipientsKey, []Recipient{
&stdRecipient{
headers: hdrs,
encryptedKey: enckey,
},
}); err != nil {
return errors.Wrapf(err, `failed to set %s`, RecipientsKey)
}
return nil
}
// Decrypt decrypts the message using the specified algorithm and key.
//
// `key` must be a private key in its "raw" format (i.e. something like
// *rsa.PrivateKey, instead of jwk.Key)
//
// This method is marked for deprecation. It will be removed from the API
// in the next major release. You should not rely on this method
// to work 100% of the time, especially when it was obtained via jwe.Parse
// instead of being constructed from scratch by this library.
func (m *Message) Decrypt(alg jwa.KeyEncryptionAlgorithm, key interface{}) ([]byte, error) {
var ctx decryptCtx
ctx.alg = alg
ctx.key = key
ctx.msg = m
return doDecryptCtx(&ctx)
}
func doDecryptCtx(dctx *decryptCtx) ([]byte, error) {
m := dctx.msg
alg := dctx.alg
key := dctx.key
if jwkKey, ok := key.(jwk.Key); ok {
var raw interface{}
if err := jwkKey.Raw(&raw); err != nil {
return nil, errors.Wrapf(err, `failed to retrieve raw key from %T`, key)
}
key = raw
}
var err error
ctx := context.TODO()
h, err := m.protectedHeaders.Clone(ctx)
if err != nil {
return nil, errors.Wrap(err, `failed to copy protected headers`)
}
h, err = h.Merge(ctx, m.unprotectedHeaders)
if err != nil {
return nil, errors.Wrap(err, "failed to merge headers for message decryption")
}
enc := m.protectedHeaders.ContentEncryption()
var aad []byte
if aadContainer := m.authenticatedData; aadContainer != nil {
aad = base64.Encode(aadContainer)
}
var computedAad []byte
if len(m.rawProtectedHeaders) > 0 {
computedAad = m.rawProtectedHeaders
} else {
// this is probably not required once msg.Decrypt is deprecated
var err error
computedAad, err = m.protectedHeaders.Encode()
if err != nil {
return nil, errors.Wrap(err, "failed to encode protected headers")
}
}
dec := NewDecrypter(alg, enc, key).
AuthenticatedData(aad).
ComputedAuthenticatedData(computedAad).
InitializationVector(m.initializationVector).
Tag(m.tag)
var plaintext []byte
var lastError error
// if we have no recipients, pretend like we only have one
recipients := m.recipients
if len(recipients) == 0 {
r := NewRecipient()
if err := r.SetHeaders(m.protectedHeaders); err != nil {
return nil, errors.Wrap(err, `failed to set headers to recipient`)
}
recipients = append(recipients, r)
}
for _, recipient := range recipients {
// strategy: try each recipient. If we fail in one of the steps,
// keep looping because there might be another key with the same algo
if recipient.Headers().Algorithm() != alg {
// algorithms don't match
continue
}
h2, err := h.Clone(ctx)
if err != nil {
lastError = errors.Wrap(err, `failed to copy headers (1)`)
continue
}
h2, err = h2.Merge(ctx, recipient.Headers())
if err != nil {
lastError = errors.Wrap(err, `failed to copy headers (2)`)
continue
}
switch alg {
case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW:
epkif, ok := h2.Get(EphemeralPublicKeyKey)
if !ok {
return nil, errors.New("failed to get 'epk' field")
}
switch epk := epkif.(type) {
case jwk.ECDSAPublicKey:
var pubkey ecdsa.PublicKey
if err := epk.Raw(&pubkey); err != nil {
return nil, errors.Wrap(err, "failed to get public key")
}
dec.PublicKey(&pubkey)
case jwk.OKPPublicKey:
var pubkey interface{}
if err := epk.Raw(&pubkey); err != nil {
return nil, errors.Wrap(err, "failed to get public key")
}
dec.PublicKey(pubkey)
default:
return nil, errors.Errorf("unexpected 'epk' type %T for alg %s", epkif, alg)
}
if apu := h2.AgreementPartyUInfo(); len(apu) > 0 {
dec.AgreementPartyUInfo(apu)
}
if apv := h2.AgreementPartyVInfo(); len(apv) > 0 {
dec.AgreementPartyVInfo(apv)
}
case jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW:
ivB64, ok := h2.Get(InitializationVectorKey)
if !ok {
return nil, errors.New("failed to get 'iv' field")
}
ivB64Str, ok := ivB64.(string)
if !ok {
return nil, errors.Errorf("unexpected type for 'iv': %T", ivB64)
}
tagB64, ok := h2.Get(TagKey)
if !ok {
return nil, errors.New("failed to get 'tag' field")
}
tagB64Str, ok := tagB64.(string)
if !ok {
return nil, errors.Errorf("unexpected type for 'tag': %T", tagB64)
}
iv, err := base64.DecodeString(ivB64Str)
if err != nil {
return nil, errors.Wrap(err, "failed to b64-decode 'iv'")
}
tag, err := base64.DecodeString(tagB64Str)
if err != nil {
return nil, errors.Wrap(err, "failed to b64-decode 'tag'")
}
dec.KeyInitializationVector(iv)
dec.KeyTag(tag)
case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW:
saltB64, ok := h2.Get(SaltKey)
if !ok {
return nil, errors.New("failed to get 'p2s' field")
}
saltB64Str, ok := saltB64.(string)
if !ok {
return nil, errors.Errorf("unexpected type for 'p2s': %T", saltB64)
}
count, ok := h2.Get(CountKey)
if !ok {
return nil, errors.New("failed to get 'p2c' field")
}
countFlt, ok := count.(float64)
if !ok {
return nil, errors.Errorf("unexpected type for 'p2c': %T", count)
}
salt, err := base64.DecodeString(saltB64Str)
if err != nil {
return nil, errors.Wrap(err, "failed to b64-decode 'salt'")
}
dec.KeySalt(salt)
dec.KeyCount(int(countFlt))
}
plaintext, err = dec.Decrypt(recipient.EncryptedKey(), m.cipherText)
if err != nil {
lastError = errors.Wrap(err, `failed to decrypt`)
continue
}
if h2.Compression() == jwa.Deflate {
buf, err := uncompress(plaintext)
if err != nil {
lastError = errors.Wrap(err, `failed to uncompress payload`)
continue
}
plaintext = buf
}
break
}
if plaintext == nil {
if lastError != nil {
return nil, errors.Errorf(`failed to find matching recipient to decrypt key (last error = %s)`, lastError)
}
return nil, errors.New("failed to find matching recipient")
}
return plaintext, nil
}

View File

@ -1,87 +0,0 @@
package jwe
import (
"context"
"github.com/lestrrat-go/option"
)
type Option = option.Interface
type identMessage struct{}
type identPostParser struct{}
type identPrettyFormat struct{}
type identProtectedHeader struct{}
type DecryptOption interface {
Option
decryptOption()
}
type decryptOption struct {
Option
}
func (*decryptOption) decryptOption() {}
type SerializerOption interface {
Option
serializerOption()
}
type serializerOption struct {
Option
}
func (*serializerOption) serializerOption() {}
type EncryptOption interface {
Option
encryptOption()
}
type encryptOption struct {
Option
}
func (*encryptOption) encryptOption() {}
// WithPrettyFormat specifies if the `jwe.JSON` serialization tool
// should generate pretty-formatted output
func WithPrettyFormat(b bool) SerializerOption {
return &serializerOption{option.New(identPrettyFormat{}, b)}
}
// Specify contents of the protected header. Some fields such as
// "enc" and "zip" will be overwritten when encryption is performed.
func WithProtectedHeaders(h Headers) EncryptOption {
cloned, _ := h.Clone(context.Background())
return &encryptOption{option.New(identProtectedHeader{}, cloned)}
}
// WithMessage provides a message object to be populated by `jwe.Decrpt`
// Using this option allows you to decrypt AND obtain the `jwe.Message`
// in one go.
//
// Note that you should NOT be using the message object for anything other
// than inspecting its contents. Particularly, do not expect the message
// reliable when you call `Decrypt` on it. `(jwe.Message).Decrypt` is
// slated to be deprecated in the next major version.
func WithMessage(m *Message) DecryptOption {
return &decryptOption{option.New(identMessage{}, m)}
}
// WithPostParser specifies the handler to be called immediately
// after the JWE message has been parsed, but before decryption
// takes place during `jwe.Decrypt`.
//
// This option exists to allow advanced users that require the use
// of information stored in the JWE message to determine how the
// decryption should be handled.
//
// For security reasons it is highly recommended that you thoroughly
// study how the process works before using this option. This is especially
// true if you are trying to infer key algorithms and keys to use to
// decrypt a message using non-standard hints.
func WithPostParser(p PostParser) DecryptOption {
return &decryptOption{option.New(identPostParser{}, p)}
}

View File

@ -1,94 +0,0 @@
package jwe
import (
"context"
"github.com/lestrrat-go/jwx/internal/base64"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/lestrrat-go/jwx/internal/pool"
"github.com/pkg/errors"
)
// Compact encodes the given message into a JWE compact serialization format.
//
// Currently `Compact()` does not take any options, but the API is
// set up as such to allow future expansions
func Compact(m *Message, _ ...SerializerOption) ([]byte, error) {
if len(m.recipients) != 1 {
return nil, errors.New("wrong number of recipients for compact serialization")
}
recipient := m.recipients[0]
// The protected header must be a merge between the message-wide
// protected header AND the recipient header
// There's something wrong if m.protectedHeaders is nil, but
// it could happen
if m.protectedHeaders == nil {
return nil, errors.New("invalid protected header")
}
ctx := context.TODO()
hcopy, err := m.protectedHeaders.Clone(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to copy protected header")
}
hcopy, err = hcopy.Merge(ctx, m.unprotectedHeaders)
if err != nil {
return nil, errors.Wrap(err, "failed to merge unprotected header")
}
hcopy, err = hcopy.Merge(ctx, recipient.Headers())
if err != nil {
return nil, errors.Wrap(err, "failed to merge recipient header")
}
protected, err := hcopy.Encode()
if err != nil {
return nil, errors.Wrap(err, "failed to encode header")
}
encryptedKey := base64.Encode(recipient.EncryptedKey())
iv := base64.Encode(m.initializationVector)
cipher := base64.Encode(m.cipherText)
tag := base64.Encode(m.tag)
buf := pool.GetBytesBuffer()
defer pool.ReleaseBytesBuffer(buf)
buf.Grow(len(protected) + len(encryptedKey) + len(iv) + len(cipher) + len(tag) + 4)
buf.Write(protected)
buf.WriteByte('.')
buf.Write(encryptedKey)
buf.WriteByte('.')
buf.Write(iv)
buf.WriteByte('.')
buf.Write(cipher)
buf.WriteByte('.')
buf.Write(tag)
result := make([]byte, buf.Len())
copy(result, buf.Bytes())
return result, nil
}
// JSON encodes the message into a JWE JSON serialization format.
//
// If `WithPrettyFormat(true)` is passed as an option, the returned
// value will be formatted using `json.MarshalIndent()`
func JSON(m *Message, options ...SerializerOption) ([]byte, error) {
var pretty bool
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identPrettyFormat{}:
pretty = option.Value().(bool)
}
}
if pretty {
return json.MarshalIndent(m, "", " ")
}
return json.Marshal(m)
}

View File

@ -1,274 +0,0 @@
# JWK [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/jwk.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk)
Package jwk implements JWK as described in [RFC7517](https://tools.ietf.org/html/rfc7517)
* Parse and work with RSA/EC/Symmetric/OKP JWK types
* Convert to and from JSON
* Convert to and from raw key types (e.g. *rsa.PrivateKey)
* Ability to keep a JWKS fresh.
How-to style documentation can be found in the [docs directory](../docs).
Examples are located in the examples directory ([jwk_example_test.go](../examples/jwk_example_test.go))
Supported key types:
| kty | Curve | Go Key Type |
|:----|:------------------------|:----------------------------------------------|
| RSA | N/A | rsa.PrivateKey / rsa.PublicKey (2) |
| EC | P-256<br>P-384<br>P-521<br>secp256k1 (1) | ecdsa.PrivateKey / ecdsa.PublicKey (2) |
| oct | N/A | []byte |
| OKP | Ed25519 (1) | ed25519.PrivateKey / ed25519.PublicKey (2) |
| | X25519 (1) | (jwx/)x25519.PrivateKey / x25519.PublicKey (2)|
* Note 1: Experimental
* Note 2: Either value or pointers accepted (e.g. rsa.PrivateKey or *rsa.PrivateKey)
# SYNOPSIS
## Parse a JWK or a JWK set
```go
// Parse a single JWK key.
key, err := jwk.ParseKey(...)
// Parse a JWK set (or a single JWK key)
set, err := jwk.Parse(...)
```
## Create JWK keys from raw keys
```go
func ExampleNew() {
// New returns different underlying types of jwk.Key objects
// depending on the input value.
// []byte -> jwk.SymmetricKey
{
raw := []byte("Lorem Ipsum")
key, err := jwk.New(raw)
if err != nil {
fmt.Printf("failed to create symmetric key: %s\n", err)
return
}
if _, ok := key.(jwk.SymmetricKey); !ok {
fmt.Printf("expected jwk.SymmetricKey, got %T\n", key)
return
}
}
// *rsa.PrivateKey -> jwk.RSAPrivateKey
// *rsa.PublicKey -> jwk.RSAPublicKey
{
raw, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Printf("failed to generate new RSA privatre key: %s\n", err)
return
}
key, err := jwk.New(raw)
if err != nil {
fmt.Printf("failed to create symmetric key: %s\n", err)
return
}
if _, ok := key.(jwk.RSAPrivateKey); !ok {
fmt.Printf("expected jwk.SymmetricKey, got %T\n", key)
return
}
// PublicKey is omitted for brevity
}
// *ecdsa.PrivateKey -> jwk.ECDSAPrivateKey
// *ecdsa.PublicKey -> jwk.ECDSAPublicKey
{
raw, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
fmt.Printf("failed to generate new ECDSA privatre key: %s\n", err)
return
}
key, err := jwk.New(raw)
if err != nil {
fmt.Printf("failed to create symmetric key: %s\n", err)
return
}
if _, ok := key.(jwk.ECDSAPrivateKey); !ok {
fmt.Printf("expected jwk.SymmetricKey, got %T\n", key)
return
}
// PublicKey is omitted for brevity
}
// OUTPUT:
}
```
# Get the JSON representation of a key
```go
func ExampleMarshalJSON() {
// to get the same values every time, we need to create a static source
// of "randomness"
rdr := bytes.NewReader([]byte("01234567890123456789012345678901234567890123456789ABCDEF"))
raw, err := ecdsa.GenerateKey(elliptic.P384(), rdr)
if err != nil {
fmt.Printf("failed to generate new ECDSA privatre key: %s\n", err)
return
}
key, err := jwk.New(raw)
if err != nil {
fmt.Printf("failed to create symmetric key: %s\n", err)
return
}
if _, ok := key.(jwk.ECDSAPrivateKey); !ok {
fmt.Printf("expected jwk.SymmetricKey, got %T\n", key)
return
}
key.Set(jwk.KeyIDKey, "mykey")
buf, err := json.MarshalIndent(key, "", " ")
if err != nil {
fmt.Printf("failed to marshal key into JSON: %s\n", err)
return
}
fmt.Printf("%s\n", buf)
// OUTPUT:
// {
// "kty": "EC",
// "crv": "P-384",
// "d": "ODkwMTIzNDU2Nzg5MDEyMz7deMbyLt8g4cjcxozuIoygLLlAeoQ1AfM9TSvxkFHJ",
// "kid": "mykey",
// "x": "gvvRMqm1w5aHn7sVNA2QUJeOVcedUnmiug6VhU834gzS9k87crVwu9dz7uLOdoQl",
// "y": "7fVF7b6J_6_g6Wu9RuJw8geWxEi5ja9Gp2TSdELm5u2E-M7IF-bsxqcdOj3n1n7N"
// }
}
```
# Auto-Refresh a key during a long running process
```go
func ExampleAutoRefresh() {
ctx, cancel := context.WithCancel(context.Background())
const googleCerts = `https://www.googleapis.com/oauth2/v3/certs`
ar := jwk.NewAutoRefresh(ctx)
// Tell *jwk.AutoRefresh that we only want to refresh this JWKS
// when it needs to (based on Cache-Control or Expires header from
// the HTTP response). If the calculated minimum refresh interval is less
// than 15 minutes, don't go refreshing any earlier than 15 minutes.
ar.Configure(googleCerts, jwk.WithMinRefreshInterval(15*time.Minute))
// Refresh the JWKS once before getting into the main loop.
// This allows you to check if the JWKS is available before we start
// a long-running program
_, err := ar.Refresh(ctx, googleCerts)
if err != nil {
fmt.Printf("failed to refresh google JWKS: %s\n", err)
return
}
// Pretend that this is your program's main loop
MAIN:
for {
select {
case <-ctx.Done():
break MAIN
default:
}
keyset, err := ar.Fetch(ctx, googleCerts)
if err != nil {
fmt.Printf("failed to fetch google JWKS: %s\n", err)
return
}
_ = keyset
// Do interesting stuff with the keyset... but here, we just
// sleep for a bit
time.Sleep(time.Second)
// Because we're a dummy program, we just cancel the loop now.
// If this were a real program, you prosumably loop forever
cancel()
}
// OUTPUT:
}
```
Parse and use a JWK key:
```go
import (
"encoding/json"
"log"
"github.com/lestrrat-go/jwx/jwk"
)
func main() {
set, err := jwk.Fetch(context.Background(), "https://www.googleapis.com/oauth2/v3/certs")
if err != nil {
log.Printf("failed to parse JWK: %s", err)
return
}
// Key sets can be serialized back to JSON
{
jsonbuf, err := json.Marshal(set)
if err != nil {
log.Printf("failed to marshal key set into JSON: %s", err)
return
}
log.Printf("%s", jsonbuf)
}
for it := set.Iterate(context.Background()); it.Next(context.Background()); {
pair := it.Pair()
key := pair.Value.(jwk.Key)
var rawkey interface{} // This is the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey
if err := key.Raw(&rawkey); err != nil {
log.Printf("failed to create public key: %s", err)
return
}
// Use rawkey for jws.Verify() or whatever.
_ = rawkey
// You can create jwk.Key from a raw key, too
fromRawKey, err := jwk.New(rawkey)
// Keys can be serialized back to JSON
jsonbuf, err := json.Marshal(key)
if err != nil {
log.Printf("failed to marshal key into JSON: %s", err)
return
}
log.Printf("%s", jsonbuf)
// If you know the underlying Key type (RSA, EC, Symmetric), you can
// create an empy instance first
// key := jwk.NewRSAPrivateKey()
// ..and then use json.Unmarshal
// json.Unmarshal(key, jsonbuf)
//
// but if you don't know the type first, you have an abstract type
// jwk.Key, which can't be used as the first argument to json.Unmarshal
//
// In this case, use jwk.Parse()
fromJsonKey, err := jwk.Parse(jsonbuf)
if err != nil {
log.Printf("failed to parse json: %s", err)
return
}
_ = fromJsonKey
_ = fromRawKey
}
}
```

View File

@ -1,85 +0,0 @@
package jwk
import (
"crypto/x509"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/lestrrat-go/jwx/internal/base64"
"github.com/pkg/errors"
)
func (c CertificateChain) MarshalJSON() ([]byte, error) {
certs := c.Get()
encoded := make([]string, len(certs))
for i := 0; i < len(certs); i++ {
encoded[i] = base64.EncodeToStringStd(certs[i].Raw)
}
return json.Marshal(encoded)
}
func (c *CertificateChain) UnmarshalJSON(buf []byte) error {
var list []string
if err := json.Unmarshal(buf, &list); err != nil {
return errors.Wrap(err, `failed to unmarshal JSON into []string`)
}
var tmp CertificateChain
if err := tmp.Accept(list); err != nil {
return err
}
*c = tmp
return nil
}
func (c CertificateChain) Get() []*x509.Certificate {
return c.certs
}
func (c *CertificateChain) Accept(v interface{}) error {
var list []string
switch x := v.(type) {
case string:
list = []string{x}
case []interface{}:
list = make([]string, len(x))
for i, e := range x {
if es, ok := e.(string); ok {
list[i] = es
continue
}
return errors.Errorf(`invalid list element type: expected string, got %T at element %d`, e, i)
}
case []string:
list = x
case CertificateChain:
certs := make([]*x509.Certificate, len(x.certs))
copy(certs, x.certs)
*c = CertificateChain{
certs: certs,
}
return nil
default:
return errors.Errorf(`invalid type for CertificateChain: %T`, v)
}
certs := make([]*x509.Certificate, len(list))
for i, e := range list {
buf, err := base64.DecodeString(e)
if err != nil {
return errors.Wrap(err, `failed to base64 decode list element`)
}
cert, err := x509.ParseCertificate(buf)
if err != nil {
return errors.Wrap(err, `failed to parse certificate`)
}
certs[i] = cert
}
*c = CertificateChain{
certs: certs,
}
return nil
}

View File

@ -1,14 +0,0 @@
#!/bin/bash
# Script to perform code generation. This exists to overcome
# the fact that go:generate doesn't really allow you to change directories
set -e
pushd internal/cmd/genheader
go build -o genheader main.go
popd
./internal/cmd/genheader/genheader -objects=internal/cmd/genheader/objects.yml
rm internal/cmd/genheader/genheader

View File

@ -1,29 +0,0 @@
// Automatically generated by internal/cmd/genreadfile/main.go. DO NOT EDIT
package jwk
import "os"
// ReadFileOption describes options that can be passed to ReadFile.
type ReadFileOption interface {
Option
readFileOption()
}
func ReadFile(path string, options ...ReadFileOption) (Set, error) {
var parseOptions []ParseOption
for _, option := range options {
switch option := option.(type) {
case ParseOption:
parseOptions = append(parseOptions, option)
}
}
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ParseReader(f, parseOptions...)
}

View File

@ -1,197 +0,0 @@
package jwk
import (
"crypto"
"time"
"github.com/lestrrat-go/backoff/v2"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/lestrrat-go/option"
)
type Option = option.Interface
type identHTTPClient struct{}
type identThumbprintHash struct{}
type identRefreshInterval struct{}
type identMinRefreshInterval struct{}
type identFetchBackoff struct{}
type identPEM struct{}
type identTypedField struct{}
type identLocalRegistry struct{}
type identFetchWhitelist struct{}
type identIgnoreParseError struct{}
// AutoRefreshOption is a type of Option that can be passed to the
// AutoRefresh object.
type AutoRefreshOption interface {
Option
autoRefreshOption()
}
type autoRefreshOption struct {
Option
}
func (*autoRefreshOption) autoRefreshOption() {}
// FetchOption is a type of Option that can be passed to `jwk.Fetch()`
// FetchOption also implements the `AutoRefreshOption`, and thus can
// safely be passed to `(*jwk.AutoRefresh).Configure()`
type FetchOption interface {
AutoRefreshOption
fetchOption()
}
type fetchOption struct {
Option
}
func (*fetchOption) autoRefreshOption() {}
func (*fetchOption) fetchOption() {}
// ParseOption is a type of Option that can be passed to `jwk.Parse()`
// ParseOption also implmentsthe `ReadFileOPtion` and `AutoRefreshOption`,
// and thus safely be passed to `jwk.ReadFile` and `(*jwk.AutoRefresh).Configure()`
type ParseOption interface {
ReadFileOption
AutoRefreshOption
parseOption()
}
type parseOption struct {
Option
}
func (*parseOption) autoRefreshOption() {}
func (*parseOption) parseOption() {}
func (*parseOption) readFileOption() {}
// WithHTTPClient allows users to specify the "net/http".Client object that
// is used when fetching jwk.Set objects.
func WithHTTPClient(cl HTTPClient) FetchOption {
return &fetchOption{option.New(identHTTPClient{}, cl)}
}
// WithFetchBackoff specifies the backoff policy to use when
// refreshing a JWKS from a remote server fails.
//
// This does not have any effect on initial `Fetch()`, or any of the `Refresh()` calls --
// the backoff is applied ONLY on the background refreshing goroutine.
func WithFetchBackoff(v backoff.Policy) FetchOption {
return &fetchOption{option.New(identFetchBackoff{}, v)}
}
func WithThumbprintHash(h crypto.Hash) Option {
return option.New(identThumbprintHash{}, h)
}
// WithRefreshInterval specifies the static interval between refreshes
// of jwk.Set objects controlled by jwk.AutoRefresh.
//
// Providing this option overrides the adaptive token refreshing based
// on Cache-Control/Expires header (and jwk.WithMinRefreshInterval),
// and refreshes will *always* happen in this interval.
func WithRefreshInterval(d time.Duration) AutoRefreshOption {
return &autoRefreshOption{
option.New(identRefreshInterval{}, d),
}
}
// WithMinRefreshInterval specifies the minimum refresh interval to be used
// when using AutoRefresh. This value is ONLY used if you did not specify
// a user-supplied static refresh interval via `WithRefreshInterval`.
//
// This value is used as a fallback value when tokens are refreshed.
//
// When we fetch the key from a remote URL, we first look at the max-age
// directive from Cache-Control response header. If this value is present,
// we compare the max-age value and the value specified by this option
// and take the larger one.
//
// Next we check for the Expires header, and similarly if the header is
// present, we compare it against the value specified by this option,
// and take the larger one.
//
// Finally, if neither of the above headers are present, we use the
// value specified by this option as the next refresh timing
//
// If unspecified, the minimum refresh interval is 1 hour
func WithMinRefreshInterval(d time.Duration) AutoRefreshOption {
return &autoRefreshOption{
option.New(identMinRefreshInterval{}, d),
}
}
// WithPEM specifies that the input to `Parse()` is a PEM encoded key.
func WithPEM(v bool) ParseOption {
return &parseOption{
option.New(identPEM{}, v),
}
}
type typedFieldPair struct {
Name string
Value interface{}
}
// WithTypedField allows a private field to be parsed into the object type of
// your choice. It works much like the RegisterCustomField, but the effect
// is only applicable to the jwt.Parse function call which receives this option.
//
// While this can be extremely useful, this option should be used with caution:
// There are many caveats that your entire team/user-base needs to be aware of,
// and therefore in general its use is discouraged. Only use it when you know
// what you are doing, and you document its use clearly for others.
//
// First and foremost, this is a "per-object" option. Meaning that given the same
// serialized format, it is possible to generate two objects whose internal
// representations may differ. That is, if you parse one _WITH_ the option,
// and the other _WITHOUT_, their internal representation may completely differ.
// This could potentially lead to problems.
//
// Second, specifying this option will slightly slow down the decoding process
// as it needs to consult multiple definitions sources (global and local), so
// be careful if you are decoding a large number of tokens, as the effects will stack up.
func WithTypedField(name string, object interface{}) ParseOption {
return &parseOption{
option.New(identTypedField{},
typedFieldPair{Name: name, Value: object},
),
}
}
// This option is only available for internal code. Users don't get to play with it
func withLocalRegistry(r *json.Registry) ParseOption {
return &parseOption{option.New(identLocalRegistry{}, r)}
}
// WithFetchWhitelist specifies the Whitelist object to use when
// fetching JWKs from a remote source. This option can be passed
// to both `jwk.Fetch()`, `jwk.NewAutoRefresh()`, and `(*jwk.AutoRefresh).Configure()`
func WithFetchWhitelist(w Whitelist) FetchOption {
return &fetchOption{option.New(identFetchWhitelist{}, w)}
}
// WithIgnoreParseError is only applicable when used with `jwk.Parse()`
// (i.e. to parse JWK sets). If passed to `jwk.ParseKey()`, the function
// will return an error no matter what the input is.
//
// DO NOT USE WITHOUT EXHAUSTING ALL OTHER ROUTES FIRST.
//
// The option specifies that errors found during parsing of individual
// keys are ignored. For example, if you had keys A, B, C where B is
// invalid (e.g. it does not contain the required fields), then the
// resulting JWKS will contain keys A and C only.
//
// This options exists as an escape hatch for those times when a
// key in a JWKS that is irrelevant for your use case is causing
// your JWKS parsing to fail, and you want to get to the rest of the
// keys in the JWKS.
//
// Again, DO NOT USE unless you have exhausted all other routes.
// When you use this option, you will not be able to tell if you are
// using a faulty JWKS, except for when there are JSON syntax errors.
func WithIgnoreParseError(b bool) ParseOption {
return &parseOption{option.New(identIgnoreParseError{}, b)}
}

View File

@ -1,653 +0,0 @@
package jwk
import (
"context"
"net/http"
"reflect"
"sync"
"time"
"github.com/lestrrat-go/backoff/v2"
"github.com/lestrrat-go/httpcc"
"github.com/pkg/errors"
)
// AutoRefresh is a container that keeps track of jwk.Set object by their source URLs.
// The jwk.Set objects are refreshed automatically behind the scenes.
//
// Before retrieving the jwk.Set objects, the user must pre-register the
// URLs they intend to use by calling `Configure()`
//
// ar := jwk.NewAutoRefresh(ctx)
// ar.Configure(url, options...)
//
// Once registered, you can call `Fetch()` to retrieve the jwk.Set object.
//
// All JWKS objects that are retrieved via the auto-fetch mechanism should be
// treated read-only, as they are shared among the consumers and this object.
type AutoRefresh struct {
errSink chan AutoRefreshError
cache map[string]Set
configureCh chan struct{}
removeCh chan removeReq
fetching map[string]chan struct{}
muErrSink sync.Mutex
muCache sync.RWMutex
muFetching sync.Mutex
muRegistry sync.RWMutex
registry map[string]*target
resetTimerCh chan *resetTimerReq
}
type target struct {
// The backoff policy to use when fetching the JWKS fails
backoff backoff.Policy
// The HTTP client to use. The user may opt to use a client which is
// aware of HTTP caching, or one that goes through a proxy
httpcl HTTPClient
// Interval between refreshes are calculated two ways.
// 1) You can set an explicit refresh interval by using WithRefreshInterval().
// In this mode, it doesn't matter what the HTTP response says in its
// Cache-Control or Expires headers
// 2) You can let us calculate the time-to-refresh based on the key's
// Cache-Control or Expires headers.
// First, the user provides us the absolute minimum interval before
// refreshes. We will never check for refreshes before this specified
// amount of time.
//
// Next, max-age directive in the Cache-Control header is consulted.
// If `max-age` is not present, we skip the following section, and
// proceed to the next option.
// If `max-age > user-supplied minimum interval`, then we use the max-age,
// otherwise the user-supplied minimum interval is used.
//
// Next, the value specified in Expires header is consulted.
// If the header is not present, we skip the following seciont and
// proceed to the next option.
// We take the time until expiration `expires - time.Now()`, and
// if `time-until-expiration > user-supplied minimum interval`, then
// we use the expires value, otherwise the user-supplied minimum interval is used.
//
// If all of the above fails, we used the user-supplied minimum interval
refreshInterval *time.Duration
minRefreshInterval time.Duration
url string
// The timer for refreshing the keyset. should not be set by anyone
// other than the refreshing goroutine
timer *time.Timer
// Semaphore to limit the number of concurrent refreshes in the background
sem chan struct{}
// for debugging, snapshoting
lastRefresh time.Time
nextRefresh time.Time
wl Whitelist
parseOptions []ParseOption
}
type resetTimerReq struct {
t *target
d time.Duration
}
// NewAutoRefresh creates a container that keeps track of JWKS objects which
// are automatically refreshed.
//
// The context object in the argument controls the life-span of the
// auto-refresh worker. If you are using this in a long running process, this
// should mostly be set to a context that ends when the main loop/part of your
// program exits:
//
// func MainLoop() {
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
// ar := jwk.AutoRefresh(ctx)
// for ... {
// ...
// }
// }
func NewAutoRefresh(ctx context.Context) *AutoRefresh {
af := &AutoRefresh{
cache: make(map[string]Set),
configureCh: make(chan struct{}),
removeCh: make(chan removeReq),
fetching: make(map[string]chan struct{}),
registry: make(map[string]*target),
resetTimerCh: make(chan *resetTimerReq),
}
go af.refreshLoop(ctx)
return af
}
func (af *AutoRefresh) getCached(url string) (Set, bool) {
af.muCache.RLock()
ks, ok := af.cache[url]
af.muCache.RUnlock()
if ok {
return ks, true
}
return nil, false
}
type removeReq struct {
replyCh chan error
url string
}
// Remove removes `url` from the list of urls being watched by jwk.AutoRefresh.
// If the url is not already registered, returns an error.
func (af *AutoRefresh) Remove(url string) error {
ch := make(chan error)
af.removeCh <- removeReq{replyCh: ch, url: url}
return <-ch
}
// Configure registers the url to be controlled by AutoRefresh, and also
// sets any options associated to it.
//
// Note that options are treated as a whole -- you can't just update
// one value. For example, if you did:
//
// ar.Configure(url, jwk.WithHTTPClient(...))
// ar.Configure(url, jwk.WithRefreshInterval(...))
// The the end result is that `url` is ONLY associated with the options
// given in the second call to `Configure()`, i.e. `jwk.WithRefreshInterval`.
// The other unspecified options, including the HTTP client, is set to
// their default values.
//
// Configuration must propagate between goroutines, and therefore are
// not atomic (But changes should be felt "soon enough" for practical
// purposes)
func (af *AutoRefresh) Configure(url string, options ...AutoRefreshOption) {
var httpcl HTTPClient = http.DefaultClient
var hasRefreshInterval bool
var refreshInterval time.Duration
var wl Whitelist
var parseOptions []ParseOption
minRefreshInterval := time.Hour
bo := backoff.Null()
for _, option := range options {
if v, ok := option.(ParseOption); ok {
parseOptions = append(parseOptions, v)
continue
}
//nolint:forcetypeassert
switch option.Ident() {
case identFetchBackoff{}:
bo = option.Value().(backoff.Policy)
case identRefreshInterval{}:
refreshInterval = option.Value().(time.Duration)
hasRefreshInterval = true
case identMinRefreshInterval{}:
minRefreshInterval = option.Value().(time.Duration)
case identHTTPClient{}:
httpcl = option.Value().(HTTPClient)
case identFetchWhitelist{}:
wl = option.Value().(Whitelist)
}
}
af.muRegistry.Lock()
t, ok := af.registry[url]
if ok {
if t.httpcl != httpcl {
t.httpcl = httpcl
}
if t.minRefreshInterval != minRefreshInterval {
t.minRefreshInterval = minRefreshInterval
}
if t.refreshInterval != nil {
if !hasRefreshInterval {
t.refreshInterval = nil
} else if *t.refreshInterval != refreshInterval {
*t.refreshInterval = refreshInterval
}
} else {
if hasRefreshInterval {
t.refreshInterval = &refreshInterval
}
}
if t.wl != wl {
t.wl = wl
}
t.parseOptions = parseOptions
} else {
t = &target{
backoff: bo,
httpcl: httpcl,
minRefreshInterval: minRefreshInterval,
url: url,
sem: make(chan struct{}, 1),
// This is a placeholder timer so we can call Reset() on it later
// Make it sufficiently in the future so that we don't have bogus
// events firing
timer: time.NewTimer(24 * time.Hour),
wl: wl,
parseOptions: parseOptions,
}
if hasRefreshInterval {
t.refreshInterval = &refreshInterval
}
// Record this in the registry
af.registry[url] = t
}
af.muRegistry.Unlock()
// Tell the backend to reconfigure itself
af.configureCh <- struct{}{}
}
func (af *AutoRefresh) releaseFetching(url string) {
// first delete the entry from the map, then close the channel or
// otherwise we may end up getting multiple groutines doing the fetch
af.muFetching.Lock()
fetchingCh, ok := af.fetching[url]
if !ok {
// Juuuuuuust in case. But shouldn't happen
af.muFetching.Unlock()
return
}
delete(af.fetching, url)
close(fetchingCh)
af.muFetching.Unlock()
}
// IsRegistered checks if `url` is registered already.
func (af *AutoRefresh) IsRegistered(url string) bool {
_, ok := af.getRegistered(url)
return ok
}
// Fetch returns a jwk.Set from the given url.
func (af *AutoRefresh) getRegistered(url string) (*target, bool) {
af.muRegistry.RLock()
t, ok := af.registry[url]
af.muRegistry.RUnlock()
return t, ok
}
// Fetch returns a jwk.Set from the given url.
//
// If it has previously been fetched, then a cached value is returned.
//
// If this the first time `url` was requested, an HTTP request will be
// sent, synchronously.
//
// When accessed via multiple goroutines concurrently, and the cache
// has not been populated yet, only the first goroutine is
// allowed to perform the initialization (HTTP fetch and cache population).
// All other goroutines will be blocked until the operation is completed.
//
// DO NOT modify the jwk.Set object returned by this method, as the
// objects are shared among all consumers and the backend goroutine
func (af *AutoRefresh) Fetch(ctx context.Context, url string) (Set, error) {
if _, ok := af.getRegistered(url); !ok {
return nil, errors.Errorf(`url %s must be configured using "Configure()" first`, url)
}
ks, found := af.getCached(url)
if found {
return ks, nil
}
return af.refresh(ctx, url)
}
// Refresh is the same as Fetch(), except that HTTP fetching is done synchronously.
//
// This is useful when you want to force an HTTP fetch instead of waiting
// for the background goroutine to do it, for example when you want to
// make sure the AutoRefresh cache is warmed up before starting your main loop
func (af *AutoRefresh) Refresh(ctx context.Context, url string) (Set, error) {
if _, ok := af.getRegistered(url); !ok {
return nil, errors.Errorf(`url %s must be configured using "Configure()" first`, url)
}
return af.refresh(ctx, url)
}
func (af *AutoRefresh) refresh(ctx context.Context, url string) (Set, error) {
// To avoid a thundering herd, only one goroutine per url may enter into this
// initial fetch phase.
af.muFetching.Lock()
fetchingCh, fetching := af.fetching[url]
// unlock happens in each of the if/else clauses because we need to perform
// the channel initialization when there is no channel present
if fetching {
af.muFetching.Unlock()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-fetchingCh:
}
} else {
fetchingCh = make(chan struct{})
af.fetching[url] = fetchingCh
af.muFetching.Unlock()
// Register a cleanup handler, to make sure we always
defer af.releaseFetching(url)
// The first time around, we need to fetch the keyset
if err := af.doRefreshRequest(ctx, url, false); err != nil {
return nil, errors.Wrapf(err, `failed to fetch resource pointed by %s`, url)
}
}
// the cache should now be populated
ks, ok := af.getCached(url)
if !ok {
return nil, errors.New("cache was not populated after explicit refresh")
}
return ks, nil
}
// Keeps looping, while refreshing the KeySet.
func (af *AutoRefresh) refreshLoop(ctx context.Context) {
// reflect.Select() is slow IF we are executing it over and over
// in a very fast iteration, but we assume here that refreshes happen
// seldom enough that being able to call one `select{}` with multiple
// targets / channels outweighs the speed penalty of using reflect.
//
const (
ctxDoneIdx = iota
configureIdx
resetTimerIdx
removeIdx
baseSelcasesLen
)
baseSelcases := make([]reflect.SelectCase, baseSelcasesLen)
baseSelcases[ctxDoneIdx] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(ctx.Done()),
}
baseSelcases[configureIdx] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(af.configureCh),
}
baseSelcases[resetTimerIdx] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(af.resetTimerCh),
}
baseSelcases[removeIdx] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(af.removeCh),
}
var targets []*target
var selcases []reflect.SelectCase
for {
// It seems silly, but it's much easier to keep track of things
// if we re-build the select cases every iteration
af.muRegistry.RLock()
if cap(targets) < len(af.registry) {
targets = make([]*target, 0, len(af.registry))
} else {
targets = targets[:0]
}
if cap(selcases) < len(af.registry) {
selcases = make([]reflect.SelectCase, 0, len(af.registry)+baseSelcasesLen)
} else {
selcases = selcases[:0]
}
selcases = append(selcases, baseSelcases...)
for _, data := range af.registry {
targets = append(targets, data)
selcases = append(selcases, reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(data.timer.C),
})
}
af.muRegistry.RUnlock()
chosen, recv, recvOK := reflect.Select(selcases)
switch chosen {
case ctxDoneIdx:
// <-ctx.Done(). Just bail out of this loop
return
case configureIdx:
// <-configureCh. rebuild the select list from the registry.
// since we're rebuilding everything for each iteration,
// we just need to start the loop all over again
continue
case resetTimerIdx:
// <-resetTimerCh. interrupt polling, and reset the timer on
// a single target. this needs to be handled inside this select
if !recvOK {
continue
}
req := recv.Interface().(*resetTimerReq) //nolint:forcetypeassert
t := req.t
d := req.d
if !t.timer.Stop() {
select {
case <-t.timer.C:
default:
}
}
t.timer.Reset(d)
case removeIdx:
// <-removeCh. remove the URL from future fetching
//nolint:forcetypeassert
req := recv.Interface().(removeReq)
replyCh := req.replyCh
url := req.url
af.muRegistry.Lock()
if _, ok := af.registry[url]; !ok {
replyCh <- errors.Errorf(`invalid url %q (not registered)`, url)
} else {
delete(af.registry, url)
replyCh <- nil
}
af.muRegistry.Unlock()
default:
// Do not fire a refresh in case the channel was closed.
if !recvOK {
continue
}
// Time to refresh a target
t := targets[chosen-baseSelcasesLen]
// Check if there are other goroutines still doing the refresh asynchronously.
// This could happen if the refreshing goroutine is stuck on a backoff
// waiting for the HTTP request to complete.
select {
case t.sem <- struct{}{}:
// There can only be one refreshing goroutine
default:
continue
}
go func() {
//nolint:errcheck
af.doRefreshRequest(ctx, t.url, true)
<-t.sem
}()
}
}
}
func (af *AutoRefresh) doRefreshRequest(ctx context.Context, url string, enableBackoff bool) error {
af.muRegistry.RLock()
t, ok := af.registry[url]
if !ok {
af.muRegistry.RUnlock()
return errors.Errorf(`url "%s" is not registered`, url)
}
// In case the refresh fails due to errors in fetching/parsing the JWKS,
// we want to retry. Create a backoff object,
parseOptions := t.parseOptions
fetchOptions := []FetchOption{WithHTTPClient(t.httpcl)}
if enableBackoff {
fetchOptions = append(fetchOptions, WithFetchBackoff(t.backoff))
}
if t.wl != nil {
fetchOptions = append(fetchOptions, WithFetchWhitelist(t.wl))
}
af.muRegistry.RUnlock()
res, err := fetch(ctx, url, fetchOptions...)
if err == nil {
if res.StatusCode != http.StatusOK {
// now, can there be a remote resource that responds with a status code
// other than 200 and still be valid...? naaaaaaahhhhhh....
err = errors.Errorf(`bad response status code (%d)`, res.StatusCode)
} else {
defer res.Body.Close()
keyset, parseErr := ParseReader(res.Body, parseOptions...)
if parseErr == nil {
// Got a new key set. replace the keyset in the target
af.muCache.Lock()
af.cache[url] = keyset
af.muCache.Unlock()
af.muRegistry.RLock()
nextInterval := calculateRefreshDuration(res, t.refreshInterval, t.minRefreshInterval)
af.muRegistry.RUnlock()
rtr := &resetTimerReq{
t: t,
d: nextInterval,
}
select {
case <-ctx.Done():
return ctx.Err()
case af.resetTimerCh <- rtr:
}
now := time.Now()
af.muRegistry.Lock()
t.lastRefresh = now.Local()
t.nextRefresh = now.Add(nextInterval).Local()
af.muRegistry.Unlock()
return nil
}
err = parseErr
}
}
// At this point if err != nil, we know that there was something wrong
// in either the fetching or the parsing. Send this error to be processed,
// but take the extra mileage to not block regular processing by
// discarding the error if we fail to send it through the channel
if err != nil {
select {
case af.errSink <- AutoRefreshError{Error: err, URL: url}:
default:
}
}
// We either failed to perform the HTTP GET, or we failed to parse the
// JWK set. Even in case of errors, we don't delete the old key.
// We persist the old key set, even if it may be stale so the user has something to work with
// TODO: maybe this behavior should be customizable?
// If we failed to get a single time, then queue another fetch in the future.
rtr := &resetTimerReq{
t: t,
d: calculateRefreshDuration(res, t.refreshInterval, t.minRefreshInterval),
}
select {
case <-ctx.Done():
return ctx.Err()
case af.resetTimerCh <- rtr:
}
return err
}
// ErrorSink sets a channel to receive JWK fetch errors, if any.
// Only the errors that occurred *after* the channel was set will be sent.
//
// The user is responsible for properly draining the channel. If the channel
// is not drained properly, errors will be discarded.
//
// To disable, set a nil channel.
func (af *AutoRefresh) ErrorSink(ch chan AutoRefreshError) {
af.muErrSink.Lock()
af.errSink = ch
af.muErrSink.Unlock()
}
func calculateRefreshDuration(res *http.Response, refreshInterval *time.Duration, minRefreshInterval time.Duration) time.Duration {
// This always has precedence
if refreshInterval != nil {
return *refreshInterval
}
if res != nil {
if v := res.Header.Get(`Cache-Control`); v != "" {
dir, err := httpcc.ParseResponse(v)
if err == nil {
maxAge, ok := dir.MaxAge()
if ok {
resDuration := time.Duration(maxAge) * time.Second
if resDuration > minRefreshInterval {
return resDuration
}
return minRefreshInterval
}
// fallthrough
}
// fallthrough
}
if v := res.Header.Get(`Expires`); v != "" {
expires, err := http.ParseTime(v)
if err == nil {
resDuration := time.Until(expires)
if resDuration > minRefreshInterval {
return resDuration
}
return minRefreshInterval
}
// fallthrough
}
}
// Previous fallthroughs are a little redandunt, but hey, it's all good.
return minRefreshInterval
}
// TargetSnapshot is the structure returned by the Snapshot method.
// It contains information about a url that has been configured
// in AutoRefresh.
type TargetSnapshot struct {
URL string
NextRefresh time.Time
LastRefresh time.Time
}
func (af *AutoRefresh) Snapshot() <-chan TargetSnapshot {
af.muRegistry.Lock()
ch := make(chan TargetSnapshot, len(af.registry))
for url, t := range af.registry {
ch <- TargetSnapshot{
URL: url,
NextRefresh: t.nextRefresh,
LastRefresh: t.lastRefresh,
}
}
af.muRegistry.Unlock()
close(ch)
return ch
}

View File

@ -1,14 +0,0 @@
#!/bin/bash
# Script to perform code generation. This exists to overcome
# the fact that go:generate doesn't really allow you to change directories
set -e
pushd internal/cmd/genheader
go build -o genheader main.go
popd
./internal/cmd/genheader/genheader -objects=internal/cmd/genheader/objects.yml
rm internal/cmd/genheader/genheader

View File

@ -1,23 +0,0 @@
// Automatically generated by internal/cmd/genreadfile/main.go. DO NOT EDIT
package jws
import "os"
// ReadFileOption describes options that can be passed to ReadFile.
// Currently there are no options available that can be passed to ReadFile, but
// it is provided here for anticipated future additions
type ReadFileOption interface {
Option
readFileOption()
}
func ReadFile(path string, _ ...ReadFileOption) (*Message, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ParseReader(f)
}

View File

@ -1,961 +0,0 @@
//go:generate ./gen.sh
// Package jws implements the digital signature on JSON based data
// structures as described in https://tools.ietf.org/html/rfc7515
//
// If you do not care about the details, the only things that you
// would need to use are the following functions:
//
// jws.Sign(payload, algorithm, key)
// jws.Verify(encodedjws, algorithm, key)
//
// To sign, simply use `jws.Sign`. `payload` is a []byte buffer that
// contains whatever data you want to sign. `alg` is one of the
// jwa.SignatureAlgorithm constants from package jwa. For RSA and
// ECDSA family of algorithms, you will need to prepare a private key.
// For HMAC family, you just need a []byte value. The `jws.Sign`
// function will return the encoded JWS message on success.
//
// To verify, use `jws.Verify`. It will parse the `encodedjws` buffer
// and verify the result using `algorithm` and `key`. Upon successful
// verification, the original payload is returned, so you can work on it.
package jws
import (
"bufio"
"bytes"
"context"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strings"
"sync"
"unicode"
"unicode/utf8"
"github.com/lestrrat-go/backoff/v2"
"github.com/lestrrat-go/jwx/internal/base64"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/lestrrat-go/jwx/internal/pool"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/x25519"
"github.com/pkg/errors"
)
var registry = json.NewRegistry()
type payloadSigner struct {
signer Signer
key interface{}
protected Headers
public Headers
}
func (s *payloadSigner) Sign(payload []byte) ([]byte, error) {
return s.signer.Sign(payload, s.key)
}
func (s *payloadSigner) Algorithm() jwa.SignatureAlgorithm {
return s.signer.Algorithm()
}
func (s *payloadSigner) ProtectedHeader() Headers {
return s.protected
}
func (s *payloadSigner) PublicHeader() Headers {
return s.public
}
var signers = make(map[jwa.SignatureAlgorithm]Signer)
var muSigner = &sync.Mutex{}
// Sign generates a signature for the given payload, and serializes
// it in compact serialization format. In this format you may NOT use
// multiple signers.
//
// The `alg` parameter is the identifier for the signature algorithm
// that should be used.
//
// For the `key` parameter, any of the following is accepted:
// * A "raw" key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc)
// * A crypto.Signer
// * A jwk.Key
//
// A `crypto.Signer` is used when the private part of a key is
// kept in an inaccessible location, such as hardware.
// `crypto.Signer` is currently supported for RSA, ECDSA, and EdDSA
// family of algorithms.
//
// If the key is a jwk.Key and the key contains a key ID (`kid` field),
// then it is added to the protected header generated by the signature
//
// The algorithm specified in the `alg` parameter must be able to support
// the type of key you provided, otherwise an error is returned.
//
// If you would like to pass custom headers, use the WithHeaders option.
//
// If the headers contain "b64" field, then the boolean value for the field
// is respected when creating the compact serialization form. That is,
// if you specify a header with `{"b64": false}`, then the payload is
// not base64 encoded.
//
// If you want to use a detached payload, use `jws.WithDetachedPayload()` as
// one of the options. When you use this option, you must always set the
// first parameter (`payload`) to `nil`, or the function will return an error
func Sign(payload []byte, alg jwa.SignatureAlgorithm, key interface{}, options ...SignOption) ([]byte, error) {
var hdrs Headers
var detached bool
for _, o := range options {
//nolint:forcetypeassert
switch o.Ident() {
case identHeaders{}:
hdrs = o.Value().(Headers)
case identDetachedPayload{}:
detached = true
if payload != nil {
return nil, errors.New(`jws.Sign: payload must be nil when jws.WithDetachedPayload() is specified`)
}
payload = o.Value().([]byte)
}
}
muSigner.Lock()
signer, ok := signers[alg]
if !ok {
v, err := NewSigner(alg)
if err != nil {
muSigner.Unlock()
return nil, errors.Wrap(err, `failed to create signer`)
}
signers[alg] = v
signer = v
}
muSigner.Unlock()
// XXX This is cheating. Ideally `detached` should be passed as a parameter
// but since this is an exported method, we can't change this without bumping
// major versions.... But we don't want to do that now, so we will cheat by
// making it part of the object
sig := &Signature{
protected: hdrs,
detached: detached,
}
_, signature, err := sig.Sign(payload, signer, key)
if err != nil {
return nil, errors.Wrap(err, `failed sign payload`)
}
return signature, nil
}
// SignMulti accepts multiple signers via the options parameter,
// and creates a JWS in JSON serialization format that contains
// signatures from applying aforementioned signers.
//
// Use `jws.WithSigner(...)` to specify values how to generate
// each signature in the `"signatures": [ ... ]` field.
func SignMulti(payload []byte, options ...Option) ([]byte, error) {
var signers []*payloadSigner
for _, o := range options {
//nolint:forcetypeassert
switch o.Ident() {
case identPayloadSigner{}:
signers = append(signers, o.Value().(*payloadSigner))
}
}
if len(signers) == 0 {
return nil, errors.New(`no signers provided`)
}
var result Message
result.payload = payload
result.signatures = make([]*Signature, 0, len(signers))
for i, signer := range signers {
protected := signer.ProtectedHeader()
if protected == nil {
protected = NewHeaders()
}
if err := protected.Set(AlgorithmKey, signer.Algorithm()); err != nil {
return nil, errors.Wrap(err, `failed to set "alg" header`)
}
if key, ok := signer.key.(jwk.Key); ok {
if kid := key.KeyID(); kid != "" {
if err := protected.Set(KeyIDKey, kid); err != nil {
return nil, errors.Wrap(err, `failed to set "kid" header`)
}
}
}
sig := &Signature{
headers: signer.PublicHeader(),
protected: protected,
}
_, _, err := sig.Sign(payload, signer.signer, signer.key)
if err != nil {
return nil, errors.Wrapf(err, `failed to generate signature for signer #%d (alg=%s)`, i, signer.Algorithm())
}
result.signatures = append(result.signatures, sig)
}
return json.Marshal(result)
}
type verifyCtx struct {
dst *Message
detachedPayload []byte
alg jwa.SignatureAlgorithm
key interface{}
useJKU bool
jwksFetcher JWKSetFetcher
// This is only used to differentiate compact/JSON serialization
// because certain features are enabled/disabled in each
isJSON bool
}
var allowNoneWhitelist = jwk.WhitelistFunc(func(string) bool {
return false
})
// VerifyAuto is a special case of Verify(), where verification is done
// using verifications parameters that can be obtained using the information
// that is carried within the JWS message itself.
//
// Currently it only supports verification via `jku` which will be fetched
// using the object specified in `jws.JWKSetFetcher`. Note that URLs in `jku` can
// only have https scheme.
//
// Using this function will result in your program accessing remote resources via https,
// and therefore extreme caution should be taken which urls can be accessed.
//
// Without specifying extra arguments, the default `jws.JWKSetFetcher` will be
// configured with a whitelist that rejects *ALL URLSs*. This is to
// protect users from unintentionally allowing their projects to
// make unwanted requests. Therefore you must explicitly provide an
// instance of `jwk.Whitelist` that does what you want.
//
// If you want open access to any URLs in the `jku`, you can do this by
// using `jwk.InsecureWhitelist` as the whitelist, but this should be avoided in
// most cases, especially if the payload comes from outside of a controlled
// environment.
//
// It is also advised that you consider using some sort of backoff via `jws.WithFetchBackoff`
//
// Alternatively, you can provide your own `jws.JWKSetFetcher`. In this case
// there is no way for the framework to force you to set a whitelist, so the
// default behavior is to allow any URLs. You are responsible for providing
// your own safety measures.
func VerifyAuto(buf []byte, options ...VerifyOption) ([]byte, error) {
var ctx verifyCtx
// enable JKU processing
ctx.useJKU = true
var fetchOptions []jwk.FetchOption
//nolint:forcetypeassert
for _, option := range options {
switch option.Ident() {
case identMessage{}:
ctx.dst = option.Value().(*Message)
case identDetachedPayload{}:
ctx.detachedPayload = option.Value().([]byte)
case identJWKSetFetcher{}:
ctx.jwksFetcher = option.Value().(JWKSetFetcher)
case identFetchWhitelist{}:
fetchOptions = append(fetchOptions, jwk.WithFetchWhitelist(option.Value().(jwk.Whitelist)))
case identFetchBackoff{}:
fetchOptions = append(fetchOptions, jwk.WithFetchBackoff(option.Value().(backoff.Policy)))
case identHTTPClient{}:
fetchOptions = append(fetchOptions, jwk.WithHTTPClient(option.Value().(*http.Client)))
}
}
// We shove the default Whitelist in the front of the option list.
// If the user provided one, it will overwrite our default value
if ctx.jwksFetcher == nil {
fetchOptions = append([]jwk.FetchOption{jwk.WithFetchWhitelist(allowNoneWhitelist)}, fetchOptions...)
ctx.jwksFetcher = NewJWKSetFetcher(fetchOptions...)
}
return ctx.verify(buf)
}
// Verify checks if the given JWS message is verifiable using `alg` and `key`.
// `key` may be a "raw" key (e.g. rsa.PublicKey) or a jwk.Key
//
// If the verification is successful, `err` is nil, and the content of the
// payload that was signed is returned. If you need more fine-grained
// control of the verification process, manually generate a
// `Verifier` in `verify` subpackage, and call `Verify` method on it.
// If you need to access signatures and JOSE headers in a JWS message,
// use `Parse` function to get `Message` object.
func Verify(buf []byte, alg jwa.SignatureAlgorithm, key interface{}, options ...VerifyOption) ([]byte, error) {
var ctx verifyCtx
ctx.alg = alg
ctx.key = key
//nolint:forcetypeassert
for _, option := range options {
switch option.Ident() {
case identMessage{}:
ctx.dst = option.Value().(*Message)
case identDetachedPayload{}:
ctx.detachedPayload = option.Value().([]byte)
default:
return nil, errors.Errorf(`invalid jws.VerifyOption %q passed`, `With`+strings.TrimPrefix(fmt.Sprintf(`%T`, option.Ident()), `jws.ident`))
}
}
return ctx.verify(buf)
}
func (ctx *verifyCtx) verify(buf []byte) ([]byte, error) {
buf = bytes.TrimSpace(buf)
if len(buf) == 0 {
return nil, errors.New(`attempt to verify empty buffer`)
}
if buf[0] == '{' {
return ctx.verifyJSON(buf)
}
return ctx.verifyCompact(buf)
}
// VerifySet uses keys store in a jwk.Set to verify the payload in `buf`.
//
// In order for `VerifySet()` to use a key in the given set, the
// `jwk.Key` object must have a valid "alg" field, and it also must
// have either an empty value or the value "sig" in the "use" field.
//
// Furthermore if the JWS signature asks for a spefici "kid", the
// `jwk.Key` must have the same "kid" as the signature.
func VerifySet(buf []byte, set jwk.Set) ([]byte, error) {
n := set.Len()
for i := 0; i < n; i++ {
key, ok := set.Get(i)
if !ok {
continue
}
if key.Algorithm() == "" { // algorithm is not
continue
}
if usage := key.KeyUsage(); usage != "" && usage != jwk.ForSignature.String() {
continue
}
buf, err := Verify(buf, jwa.SignatureAlgorithm(key.Algorithm()), key)
if err != nil {
continue
}
return buf, nil
}
return nil, errors.New(`failed to verify message with any of the keys in the jwk.Set object`)
}
func (ctx *verifyCtx) verifyJSON(signed []byte) ([]byte, error) {
ctx.isJSON = true
var m Message
m.SetDecodeCtx(collectRawCtx{})
defer m.clearRaw()
if err := json.Unmarshal(signed, &m); err != nil {
return nil, errors.Wrap(err, `failed to unmarshal JSON message`)
}
m.SetDecodeCtx(nil)
if len(m.payload) != 0 && ctx.detachedPayload != nil {
return nil, errors.New(`can't specify detached payload for JWS with payload`)
}
if ctx.detachedPayload != nil {
m.payload = ctx.detachedPayload
}
// Pre-compute the base64 encoded version of payload
var payload string
if m.b64 {
payload = base64.EncodeToString(m.payload)
} else {
payload = string(m.payload)
}
buf := pool.GetBytesBuffer()
defer pool.ReleaseBytesBuffer(buf)
for i, sig := range m.signatures {
buf.Reset()
var encodedProtectedHeader string
if rbp, ok := sig.protected.(interface{ rawBuffer() []byte }); ok {
if raw := rbp.rawBuffer(); raw != nil {
encodedProtectedHeader = base64.EncodeToString(raw)
}
}
if encodedProtectedHeader == "" {
protected, err := json.Marshal(sig.protected)
if err != nil {
return nil, errors.Wrapf(err, `failed to marshal "protected" for signature #%d`, i+1)
}
encodedProtectedHeader = base64.EncodeToString(protected)
}
buf.WriteString(encodedProtectedHeader)
buf.WriteByte('.')
buf.WriteString(payload)
if !ctx.useJKU {
if hdr := sig.protected; hdr != nil && hdr.KeyID() != "" {
if jwkKey, ok := ctx.key.(jwk.Key); ok {
if jwkKey.KeyID() != hdr.KeyID() {
continue
}
}
}
verifier, err := NewVerifier(ctx.alg)
if err != nil {
return nil, errors.Wrap(err, "failed to create verifier")
}
if _, err := ctx.tryVerify(verifier, sig.protected, buf.Bytes(), sig.signature, m.payload); err == nil {
if ctx.dst != nil {
*(ctx.dst) = m
}
return m.payload, nil
}
// Don't fallthrough or bail out. Try the next signature.
continue
}
if _, err := ctx.verifyJKU(sig.protected, buf.Bytes(), sig.signature, m.payload); err == nil {
if ctx.dst != nil {
*(ctx.dst) = m
}
return m.payload, nil
}
// try next
}
return nil, errors.New(`could not verify with any of the signatures`)
}
// get the value of b64 header field.
// If the field does not exist, returns true (default)
// Otherwise return the value specified by the header field.
func getB64Value(hdr Headers) bool {
b64raw, ok := hdr.Get("b64")
if !ok {
return true // default
}
b64, ok := b64raw.(bool) // default
if !ok {
return false
}
return b64
}
func (ctx *verifyCtx) verifyCompact(signed []byte) ([]byte, error) {
protected, payload, signature, err := SplitCompact(signed)
if err != nil {
return nil, errors.Wrap(err, `failed extract from compact serialization format`)
}
decodedSignature, err := base64.Decode(signature)
if err != nil {
return nil, errors.Wrap(err, `failed to decode signature`)
}
hdr := NewHeaders()
decodedProtected, err := base64.Decode(protected)
if err != nil {
return nil, errors.Wrap(err, `failed to decode headers`)
}
if err := json.Unmarshal(decodedProtected, hdr); err != nil {
return nil, errors.Wrap(err, `failed to decode headers`)
}
verifyBuf := pool.GetBytesBuffer()
defer pool.ReleaseBytesBuffer(verifyBuf)
verifyBuf.Write(protected)
verifyBuf.WriteByte('.')
if len(payload) == 0 && ctx.detachedPayload != nil {
if getB64Value(hdr) {
payload = base64.Encode(ctx.detachedPayload)
} else {
payload = ctx.detachedPayload
}
}
verifyBuf.Write(payload)
if !ctx.useJKU {
if hdr.KeyID() != "" {
if jwkKey, ok := ctx.key.(jwk.Key); ok {
if jwkKey.KeyID() != hdr.KeyID() {
return nil, errors.New(`"kid" fields do not match`)
}
}
}
verifier, err := NewVerifier(ctx.alg)
if err != nil {
return nil, errors.Wrap(err, "failed to create verifier")
}
return ctx.tryVerify(verifier, hdr, verifyBuf.Bytes(), decodedSignature, payload)
}
return ctx.verifyJKU(hdr, verifyBuf.Bytes(), decodedSignature, payload)
}
// JWKSetFetcher is used to fetch JWK Set spcified in the `jku` field.
type JWKSetFetcher interface {
Fetch(string) (jwk.Set, error)
}
// SimpleJWKSetFetcher is the default object used to fetch JWK Sets specified in `jku`,
// which uses `jwk.Fetch()`
//
// For more complicated cases, such as using `jwk.AutoRefetch`, you will have to
// create your custom instance of `jws.JWKSetFetcher`
type SimpleJWKSetFetcher struct {
options []jwk.FetchOption
}
func NewJWKSetFetcher(options ...jwk.FetchOption) *SimpleJWKSetFetcher {
return &SimpleJWKSetFetcher{options: options}
}
func (f *SimpleJWKSetFetcher) Fetch(u string) (jwk.Set, error) {
return jwk.Fetch(context.TODO(), u, f.options...)
}
type JWKSetFetchFunc func(string) (jwk.Set, error)
func (f JWKSetFetchFunc) Fetch(u string) (jwk.Set, error) {
return f(u)
}
func (ctx *verifyCtx) verifyJKU(hdr Headers, verifyBuf, decodedSignature, payload []byte) ([]byte, error) {
u := hdr.JWKSetURL()
if u == "" {
return nil, errors.New(`use of "jku" field specified, but the field is empty`)
}
uo, err := url.Parse(u)
if err != nil {
return nil, errors.Wrap(err, `failed to parse "jku"`)
}
if uo.Scheme != "https" {
return nil, errors.New(`url in "jku" must be HTTPS`)
}
set, err := ctx.jwksFetcher.Fetch(u)
if err != nil {
return nil, errors.Wrapf(err, `failed to fetch "jku"`)
}
// Because we're using a JWKS here, we MUST have "kid" that matches
// the payload
if hdr.KeyID() == "" {
return nil, errors.Errorf(`"kid" is required on the JWS message to use "jku"`)
}
key, ok := set.LookupKeyID(hdr.KeyID())
if !ok {
return nil, errors.New(`key specified via "kid" is not present in the JWK set specified by "jku"`)
}
// hooray, we found a key. Now the algorithm will have to be inferred.
algs, err := AlgorithmsForKey(key)
if err != nil {
return nil, errors.Wrapf(err, `failed to get a list of signature methods for key type %s`, key.KeyType())
}
// for each of these algorithms, just ... keep trying ...
ctx.key = key
hdrAlg := hdr.Algorithm()
for _, alg := range algs {
// if we have a "alg" field in the JWS, we can only proceed if
// the inferred algorithm matches
if hdrAlg != "" && hdrAlg != alg {
continue
}
verifier, err := NewVerifier(alg)
if err != nil {
return nil, errors.Wrap(err, "failed to create verifier")
}
if decoded, err := ctx.tryVerify(verifier, hdr, verifyBuf, decodedSignature, payload); err == nil {
return decoded, nil
}
}
return nil, errors.New(`failed to verify payload using key in "jku"`)
}
func (ctx *verifyCtx) tryVerify(verifier Verifier, hdr Headers, buf, decodedSignature, payload []byte) ([]byte, error) {
if err := verifier.Verify(buf, decodedSignature, ctx.key); err != nil {
return nil, errors.Wrap(err, `failed to verify message`)
}
var decodedPayload []byte
// When verifying JSON messages, we do not need to decode
// the payload, as we already have it
if !ctx.isJSON {
// This is a special case for RFC7797
if !getB64Value(hdr) { // it's not base64 encoded
decodedPayload = payload
}
if decodedPayload == nil {
v, err := base64.Decode(payload)
if err != nil {
return nil, errors.Wrap(err, `message verified, failed to decode payload`)
}
decodedPayload = v
}
// For compact serialization, we need to create and assign the message
// if requested
if ctx.dst != nil {
// Construct a new Message object
m := NewMessage()
m.SetPayload(decodedPayload)
sig := NewSignature()
sig.SetProtectedHeaders(hdr)
sig.SetSignature(decodedSignature)
m.AppendSignature(sig)
*(ctx.dst) = *m
}
}
return decodedPayload, nil
}
// This is an "optimized" ioutil.ReadAll(). It will attempt to read
// all of the contents from the reader IF the reader is of a certain
// concrete type.
func readAll(rdr io.Reader) ([]byte, bool) {
switch rdr.(type) {
case *bytes.Reader, *bytes.Buffer, *strings.Reader:
data, err := ioutil.ReadAll(rdr)
if err != nil {
return nil, false
}
return data, true
default:
return nil, false
}
}
// Parse parses contents from the given source and creates a jws.Message
// struct. The input can be in either compact or full JSON serialization.
func Parse(src []byte) (*Message, error) {
for i := 0; i < len(src); i++ {
r := rune(src[i])
if r >= utf8.RuneSelf {
r, _ = utf8.DecodeRune(src)
}
if !unicode.IsSpace(r) {
if r == '{' {
return parseJSON(src)
}
return parseCompact(src)
}
}
return nil, errors.New("invalid byte sequence")
}
// Parse parses contents from the given source and creates a jws.Message
// struct. The input can be in either compact or full JSON serialization.
func ParseString(src string) (*Message, error) {
return Parse([]byte(src))
}
// Parse parses contents from the given source and creates a jws.Message
// struct. The input can be in either compact or full JSON serialization.
func ParseReader(src io.Reader) (*Message, error) {
if data, ok := readAll(src); ok {
return Parse(data)
}
rdr := bufio.NewReader(src)
var first rune
for {
r, _, err := rdr.ReadRune()
if err != nil {
return nil, errors.Wrap(err, `failed to read rune`)
}
if !unicode.IsSpace(r) {
first = r
if err := rdr.UnreadRune(); err != nil {
return nil, errors.Wrap(err, `failed to unread rune`)
}
break
}
}
var parser func(io.Reader) (*Message, error)
if first == '{' {
parser = parseJSONReader
} else {
parser = parseCompactReader
}
m, err := parser(rdr)
if err != nil {
return nil, errors.Wrap(err, `failed to parse jws message`)
}
return m, nil
}
func parseJSONReader(src io.Reader) (result *Message, err error) {
var m Message
if err := json.NewDecoder(src).Decode(&m); err != nil {
return nil, errors.Wrap(err, `failed to unmarshal jws message`)
}
return &m, nil
}
func parseJSON(data []byte) (result *Message, err error) {
var m Message
if err := json.Unmarshal(data, &m); err != nil {
return nil, errors.Wrap(err, `failed to unmarshal jws message`)
}
return &m, nil
}
// SplitCompact splits a JWT and returns its three parts
// separately: protected headers, payload and signature.
func SplitCompact(src []byte) ([]byte, []byte, []byte, error) {
parts := bytes.Split(src, []byte("."))
if len(parts) < 3 {
return nil, nil, nil, errors.New(`invalid number of segments`)
}
return parts[0], parts[1], parts[2], nil
}
// SplitCompactString splits a JWT and returns its three parts
// separately: protected headers, payload and signature.
func SplitCompactString(src string) ([]byte, []byte, []byte, error) {
parts := strings.Split(src, ".")
if len(parts) < 3 {
return nil, nil, nil, errors.New(`invalid number of segments`)
}
return []byte(parts[0]), []byte(parts[1]), []byte(parts[2]), nil
}
// SplitCompactReader splits a JWT and returns its three parts
// separately: protected headers, payload and signature.
func SplitCompactReader(rdr io.Reader) ([]byte, []byte, []byte, error) {
if data, ok := readAll(rdr); ok {
return SplitCompact(data)
}
var protected []byte
var payload []byte
var signature []byte
var periods int
var state int
buf := make([]byte, 4096)
var sofar []byte
for {
// read next bytes
n, err := rdr.Read(buf)
// return on unexpected read error
if err != nil && err != io.EOF {
return nil, nil, nil, errors.Wrap(err, `unexpected end of input`)
}
// append to current buffer
sofar = append(sofar, buf[:n]...)
// loop to capture multiple '.' in current buffer
for loop := true; loop; {
var i = bytes.IndexByte(sofar, '.')
if i == -1 && err != io.EOF {
// no '.' found -> exit and read next bytes (outer loop)
loop = false
continue
} else if i == -1 && err == io.EOF {
// no '.' found -> process rest and exit
i = len(sofar)
loop = false
} else {
// '.' found
periods++
}
// Reaching this point means we have found a '.' or EOF and process the rest of the buffer
switch state {
case 0:
protected = sofar[:i]
state++
case 1:
payload = sofar[:i]
state++
case 2:
signature = sofar[:i]
}
// Shorten current buffer
if len(sofar) > i {
sofar = sofar[i+1:]
}
}
// Exit on EOF
if err == io.EOF {
break
}
}
if periods != 2 {
return nil, nil, nil, errors.New(`invalid number of segments`)
}
return protected, payload, signature, nil
}
// parseCompactReader parses a JWS value serialized via compact serialization.
func parseCompactReader(rdr io.Reader) (m *Message, err error) {
protected, payload, signature, err := SplitCompactReader(rdr)
if err != nil {
return nil, errors.Wrap(err, `invalid compact serialization format`)
}
return parse(protected, payload, signature)
}
func parseCompact(data []byte) (m *Message, err error) {
protected, payload, signature, err := SplitCompact(data)
if err != nil {
return nil, errors.Wrap(err, `invalid compact serialization format`)
}
return parse(protected, payload, signature)
}
func parse(protected, payload, signature []byte) (*Message, error) {
decodedHeader, err := base64.Decode(protected)
if err != nil {
return nil, errors.Wrap(err, `failed to decode protected headers`)
}
hdr := NewHeaders()
if err := json.Unmarshal(decodedHeader, hdr); err != nil {
return nil, errors.Wrap(err, `failed to parse JOSE headers`)
}
decodedPayload, err := base64.Decode(payload)
if err != nil {
return nil, errors.Wrap(err, `failed to decode payload`)
}
decodedSignature, err := base64.Decode(signature)
if err != nil {
return nil, errors.Wrap(err, `failed to decode signature`)
}
var msg Message
msg.payload = decodedPayload
msg.signatures = append(msg.signatures, &Signature{
protected: hdr,
signature: decodedSignature,
})
return &msg, nil
}
// RegisterCustomField allows users to specify that a private field
// be decoded as an instance of the specified type. This option has
// a global effect.
//
// For example, suppose you have a custom field `x-birthday`, which
// you want to represent as a string formatted in RFC3339 in JSON,
// but want it back as `time.Time`.
//
// In that case you would register a custom field as follows
//
// jwe.RegisterCustomField(`x-birthday`, timeT)
//
// Then `hdr.Get("x-birthday")` will still return an `interface{}`,
// but you can convert its type to `time.Time`
//
// bdayif, _ := hdr.Get(`x-birthday`)
// bday := bdayif.(time.Time)
//
func RegisterCustomField(name string, object interface{}) {
registry.Register(name, object)
}
// Helpers for signature verification
var rawKeyToKeyType = make(map[reflect.Type]jwa.KeyType)
var keyTypeToAlgorithms = make(map[jwa.KeyType][]jwa.SignatureAlgorithm)
func init() {
rawKeyToKeyType[reflect.TypeOf([]byte(nil))] = jwa.OctetSeq
rawKeyToKeyType[reflect.TypeOf(ed25519.PublicKey(nil))] = jwa.OKP
rawKeyToKeyType[reflect.TypeOf(rsa.PublicKey{})] = jwa.RSA
rawKeyToKeyType[reflect.TypeOf((*rsa.PublicKey)(nil))] = jwa.RSA
rawKeyToKeyType[reflect.TypeOf(ecdsa.PublicKey{})] = jwa.EC
rawKeyToKeyType[reflect.TypeOf((*ecdsa.PublicKey)(nil))] = jwa.EC
addAlgorithmForKeyType(jwa.OKP, jwa.EdDSA)
for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256, jwa.HS384, jwa.HS512} {
addAlgorithmForKeyType(jwa.OctetSeq, alg)
}
for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512} {
addAlgorithmForKeyType(jwa.RSA, alg)
}
for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256, jwa.ES384, jwa.ES512} {
addAlgorithmForKeyType(jwa.EC, alg)
}
}
func addAlgorithmForKeyType(kty jwa.KeyType, alg jwa.SignatureAlgorithm) {
keyTypeToAlgorithms[kty] = append(keyTypeToAlgorithms[kty], alg)
}
// AlgorithmsForKey returns the possible signature algorithms that can
// be used for a given key. It only takes in consideration keys/algorithms
// for verification purposes, as this is the only usage where one may need
// dynamically figure out which method to use.
func AlgorithmsForKey(key interface{}) ([]jwa.SignatureAlgorithm, error) {
var kty jwa.KeyType
switch key := key.(type) {
case jwk.Key:
kty = key.KeyType()
case rsa.PublicKey, *rsa.PublicKey, rsa.PrivateKey, *rsa.PrivateKey:
kty = jwa.RSA
case ecdsa.PublicKey, *ecdsa.PublicKey, ecdsa.PrivateKey, *ecdsa.PrivateKey:
kty = jwa.EC
case ed25519.PublicKey, ed25519.PrivateKey, x25519.PublicKey, x25519.PrivateKey:
kty = jwa.OKP
case []byte:
kty = jwa.OctetSeq
default:
return nil, errors.Errorf(`invalid key %T`, key)
}
algs, ok := keyTypeToAlgorithms[kty]
if !ok {
return nil, errors.Errorf(`invalid key type %q`, kty)
}
return algs, nil
}

View File

@ -1,119 +0,0 @@
package jws
import (
"net/http"
"github.com/lestrrat-go/backoff/v2"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/option"
)
type Option = option.Interface
type identPayloadSigner struct{}
type identDetachedPayload struct{}
type identHeaders struct{}
type identMessage struct{}
type identFetchBackoff struct{}
type identFetchWhitelist struct{}
type identHTTPClient struct{}
type identJWKSetFetcher struct{}
func WithSigner(signer Signer, key interface{}, public, protected Headers) Option {
return option.New(identPayloadSigner{}, &payloadSigner{
signer: signer,
key: key,
protected: protected,
public: public,
})
}
type SignOption interface {
Option
signOption()
}
type signOption struct {
Option
}
func (*signOption) signOption() {}
// WithHeaders allows you to specify extra header values to include in the
// final JWS message
func WithHeaders(h Headers) SignOption {
return &signOption{option.New(identHeaders{}, h)}
}
// VerifyOption describes an option that can be passed to the jws.Verify function
type VerifyOption interface {
Option
verifyOption()
}
type verifyOption struct {
Option
}
func (*verifyOption) verifyOption() {}
// WithMessage can be passed to Verify() to obtain the jws.Message upon
// a successful verification.
func WithMessage(m *Message) VerifyOption {
return &verifyOption{option.New(identMessage{}, m)}
}
type SignVerifyOption interface {
SignOption
VerifyOption
}
type signVerifyOption struct {
Option
}
func (*signVerifyOption) signOption() {}
func (*signVerifyOption) verifyOption() {}
// WithDetachedPayload can be used to both sign or verify a JWS message with a
// detached payload.
//
// When this option is used for `jws.Sign()`, the first parameter (normally the payload)
// must be set to `nil`.
//
// If you have to verify using this option, you should know exactly how and why this works.
func WithDetachedPayload(v []byte) SignVerifyOption {
return &signVerifyOption{option.New(identDetachedPayload{}, v)}
}
// WithFetchWhitelist specifies the whitelist object to be passed
// to `jwk.Fetch()` when `jws.VerifyAuto()` is used. If you do not
// specify a whitelist, `jws.VerifyAuto()` will ALWAYS fail.
//
// This option is ignored if WithJWKSetFetcher is specified.
func WithFetchWhitelist(wl jwk.Whitelist) VerifyOption {
return &verifyOption{option.New(identFetchWhitelist{}, wl)}
}
// WithFetchBackoff specifies the backoff.Policy object to be passed
// to `jwk.Fetch()` when `jws.VerifyAuto()` is used.
//
// This option is ignored if WithJWKSetFetcher is specified.
func WithFetchBackoff(b backoff.Policy) VerifyOption {
return &verifyOption{option.New(identFetchBackoff{}, b)}
}
// WithHTTPClient specifies the *http.Client object to be passed
// to `jwk.Fetch()` when `jws.VerifyAuto()` is used.
//
// This option is ignored if WithJWKSetFetcher is specified.
func WithHTTPClient(httpcl *http.Client) VerifyOption {
return &verifyOption{option.New(identHTTPClient{}, httpcl)}
}
// WithJWKSetFetcher specifies the JWKSetFetcher object to be
// used when `jws.VerifyAuto()`, for example, to use `jwk.AutoRefetch`
// instead of the default `jwk.Fetch()`
func WithJWKSetFetcher(f JWKSetFetcher) VerifyOption {
return &verifyOption{option.New(identJWKSetFetcher{}, f)}
}

View File

@ -1,9 +0,0 @@
#!/bin/bash
pushd internal/cmd/gentoken
go build -o gentoken main.go
popd
./internal/cmd/gentoken/gentoken -objects=internal/cmd/gentoken/objects.yml
rm internal/cmd/gentoken/gentoken

View File

@ -1,98 +0,0 @@
package types
import (
"strconv"
"time"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/pkg/errors"
)
// NumericDate represents the date format used in the 'nbf' claim
type NumericDate struct {
time.Time
}
func (n *NumericDate) Get() time.Time {
if n == nil {
return (time.Time{}).UTC()
}
return n.Time
}
func numericToTime(v interface{}, t *time.Time) bool {
var n int64
switch x := v.(type) {
case int64:
n = x
case int32:
n = int64(x)
case int16:
n = int64(x)
case int8:
n = int64(x)
case int:
n = int64(x)
case float32:
n = int64(x)
case float64:
n = int64(x)
default:
return false
}
*t = time.Unix(n, 0)
return true
}
func (n *NumericDate) Accept(v interface{}) error {
var t time.Time
switch x := v.(type) {
case string:
i, err := strconv.ParseInt(x[:], 10, 64)
if err != nil {
return errors.Errorf(`invalid epoch value %#v`, x)
}
t = time.Unix(i, 0)
case json.Number:
intval, err := x.Int64()
if err != nil {
return errors.Wrapf(err, `failed to convert json value %#v to int64`, x)
}
t = time.Unix(intval, 0)
case time.Time:
t = x
default:
if !numericToTime(v, &t) {
return errors.Errorf(`invalid type %T`, v)
}
}
n.Time = t.UTC()
return nil
}
// MarshalJSON translates from internal representation to JSON NumericDate
// See https://tools.ietf.org/html/rfc7519#page-6
func (n *NumericDate) MarshalJSON() ([]byte, error) {
if n.IsZero() {
return json.Marshal(nil)
}
return json.Marshal(n.Unix())
}
func (n *NumericDate) UnmarshalJSON(data []byte) error {
var v interface{}
if err := json.Unmarshal(data, &v); err != nil {
return errors.Wrap(err, `failed to unmarshal date`)
}
var n2 NumericDate
if err := n2.Accept(v); err != nil {
return errors.Wrap(err, `invalid value for NumericDate`)
}
*n = n2
return nil
}

View File

@ -1,29 +0,0 @@
// Automatically generated by internal/cmd/genreadfile/main.go. DO NOT EDIT
package jwt
import "os"
// ReadFileOption describes options that can be passed to ReadFile.
type ReadFileOption interface {
Option
readFileOption()
}
func ReadFile(path string, options ...ReadFileOption) (Token, error) {
var parseOptions []ParseOption
for _, option := range options {
switch option := option.(type) {
case ParseOption:
parseOptions = append(parseOptions, option)
}
}
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ParseReader(f, parseOptions...)
}

View File

@ -1,538 +0,0 @@
package jwt
import (
"context"
"net/http"
"time"
"github.com/lestrrat-go/backoff/v2"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwe"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jws"
"github.com/lestrrat-go/option"
)
type Option = option.Interface
// GlobalOption describes an Option that can be passed to `Settings()`.
type GlobalOption interface {
Option
globalOption()
}
type globalOption struct {
Option
}
func (*globalOption) globalOption() {}
// ParseRequestOption describes an Option that can be passed to `ParseRequest()`.
type ParseRequestOption interface {
ParseOption
httpParseOption()
}
type httpParseOption struct {
ParseOption
}
func (*httpParseOption) httpParseOption() {}
// ParseOption describes an Option that can be passed to `Parse()`.
// ParseOption also implements ReadFileOption, therefore it may be
// safely pass them to `jwt.ReadFile()`
type ParseOption interface {
ReadFileOption
parseOption()
}
type parseOption struct {
Option
}
func newParseOption(n interface{}, v interface{}) ParseOption {
return &parseOption{option.New(n, v)}
}
func (*parseOption) parseOption() {}
func (*parseOption) readFileOption() {}
// SignOption describes an Option that can be passed to Sign() or
// (jwt.Serializer).Sign
type SignOption interface {
Option
signOption()
}
type signOption struct {
Option
}
func newSignOption(n interface{}, v interface{}) SignOption {
return &signOption{option.New(n, v)}
}
func (*signOption) signOption() {}
// EncryptOption describes an Option that can be passed to Encrypt() or
// (jwt.Serializer).Encrypt
type EncryptOption interface {
Option
encryptOption()
}
type encryptOption struct {
Option
}
func newEncryptOption(n interface{}, v interface{}) EncryptOption {
return &encryptOption{option.New(n, v)}
}
func (*encryptOption) encryptOption() {}
// ValidateOption describes an Option that can be passed to Validate().
// ValidateOption also implements ParseOption, therefore it may be
// safely passed to `Parse()` (and thus `jwt.ReadFile()`)
type ValidateOption interface {
ParseOption
validateOption()
}
type validateOption struct {
ParseOption
}
func newValidateOption(n interface{}, v interface{}) ValidateOption {
return &validateOption{newParseOption(n, v)}
}
func (*validateOption) validateOption() {}
type identAcceptableSkew struct{}
type identClock struct{}
type identContext struct{}
type identDecrypt struct{}
type identDefault struct{}
type identFlattenAudience struct{}
type identInferAlgorithmFromKey struct{}
type identJweHeaders struct{}
type identJwsHeaders struct{}
type identKeySet struct{}
type identKeySetProvider struct{}
type identPedantic struct{}
type identValidator struct{}
type identToken struct{}
type identTypedClaim struct{}
type identValidate struct{}
type identVerify struct{}
type identVerifyAuto struct{}
type identFetchBackoff struct{}
type identFetchWhitelist struct{}
type identHTTPClient struct{}
type identJWKSetFetcher struct{}
type identHeaderKey struct{}
type identFormKey struct{}
type VerifyParameters interface {
Algorithm() jwa.SignatureAlgorithm
Key() interface{}
}
type verifyParams struct {
alg jwa.SignatureAlgorithm
key interface{}
}
func (p *verifyParams) Algorithm() jwa.SignatureAlgorithm {
return p.alg
}
func (p *verifyParams) Key() interface{} {
return p.key
}
// WithVerify forces the Parse method to verify the JWT message
// using the given key. XXX Should have been named something like
// WithVerificationKey
func WithVerify(alg jwa.SignatureAlgorithm, key interface{}) ParseOption {
return newParseOption(identVerify{}, &verifyParams{
alg: alg,
key: key,
})
}
// WithKeySet forces the Parse method to verify the JWT message
// using one of the keys in the given key set.
//
// The key and the JWT MUST have a proper `kid` field set.
// The key to use for signature verification is chosen by matching
// the Key ID of the JWT and the ID of the given key set.
//
// When using this option, keys MUST have a proper 'alg' field
// set. This is because we need to know the exact algorithm that
// you (the user) wants to use to verify the token. We do NOT
// trust the token's headers, because they can easily be tampered with.
//
// However, there _is_ a workaround if you do understand the risks
// of allowing a library to automatically choose a signature verification strategy,
// and you do not mind the verification process having to possibly
// attempt using multiple times before succeeding to verify. See
// `jwt.InferAlgorithmFromKey` option
//
// If you have only one key in the set, and are sure you want to
// use that key, you can use the `jwt.WithDefaultKey` option.
//
// If provided with WithKeySetProvider(), this option takes precedence.
func WithKeySet(set jwk.Set) ParseOption {
return newParseOption(identKeySet{}, set)
}
// UseDefaultKey is used in conjunction with the option WithKeySet
// to instruct the Parse method to default to the single key in a key
// set when no Key ID is included in the JWT. If the key set contains
// multiple keys then the default behavior is unchanged -- that is,
// the since we can't determine the key to use, it returns an error.
func UseDefaultKey(value bool) ParseOption {
return newParseOption(identDefault{}, value)
}
// WithToken specifies the token instance that is used when parsing
// JWT tokens.
func WithToken(t Token) ParseOption {
return newParseOption(identToken{}, t)
}
// WithHeaders is passed to `jwt.Sign()` function, to allow specifying arbitrary
// header values to be included in the header section of the jws message
//
// This option will be deprecated in the next major version. Use
// jwt.WithJwsHeaders() instead.
func WithHeaders(hdrs jws.Headers) SignOption {
return WithJwsHeaders(hdrs)
}
// WithJwsHeaders is passed to `jwt.Sign()` function or
// "jwt.Serializer".Sign() method, to allow specifying arbitrary
// header values to be included in the header section of the JWE message
func WithJwsHeaders(hdrs jws.Headers) SignOption {
return newSignOption(identJwsHeaders{}, hdrs)
}
// WithJweHeaders is passed to "jwt.Serializer".Encrypt() method to allow
// specifying arbitrary header values to be included in the protected header
// of the JWE message
func WithJweHeaders(hdrs jwe.Headers) EncryptOption {
return newEncryptOption(identJweHeaders{}, hdrs)
}
// WithValidate is passed to `Parse()` method to denote that the
// validation of the JWT token should be performed after a successful
// parsing of the incoming payload.
func WithValidate(b bool) ParseOption {
return newParseOption(identValidate{}, b)
}
// WithClock specifies the `Clock` to be used when verifying
// claims exp and nbf.
func WithClock(c Clock) ValidateOption {
return newValidateOption(identClock{}, c)
}
// WithAcceptableSkew specifies the duration in which exp and nbf
// claims may differ by. This value should be positive
func WithAcceptableSkew(dur time.Duration) ValidateOption {
return newValidateOption(identAcceptableSkew{}, dur)
}
// WithIssuer specifies that expected issuer value. If not specified,
// the value of issuer is not verified at all.
func WithIssuer(s string) ValidateOption {
return WithValidator(ClaimValueIs(IssuerKey, s))
}
// WithSubject specifies that expected subject value. If not specified,
// the value of subject is not verified at all.
func WithSubject(s string) ValidateOption {
return WithValidator(ClaimValueIs(SubjectKey, s))
}
// WithJwtID specifies that expected jti value. If not specified,
// the value of jti is not verified at all.
func WithJwtID(s string) ValidateOption {
return WithValidator(ClaimValueIs(JwtIDKey, s))
}
// WithAudience specifies that expected audience value.
// `Validate()` will return true if one of the values in the `aud` element
// matches this value. If not specified, the value of issuer is not
// verified at all.
func WithAudience(s string) ValidateOption {
return WithValidator(ClaimContainsString(AudienceKey, s))
}
// WithClaimValue specifies the expected value for a given claim
func WithClaimValue(name string, v interface{}) ValidateOption {
return WithValidator(ClaimValueIs(name, v))
}
// WithHeaderKey is used to specify header keys to search for tokens.
//
// While the type system allows this option to be passed to jwt.Parse() directly,
// doing so will have no effect. Only use it for HTTP request parsing functions
func WithHeaderKey(v string) ParseRequestOption {
return &httpParseOption{newParseOption(identHeaderKey{}, v)}
}
// WithFormKey is used to specify header keys to search for tokens.
//
// While the type system allows this option to be passed to jwt.Parse() directly,
// doing so will have no effect. Only use it for HTTP request parsing functions
func WithFormKey(v string) ParseRequestOption {
return &httpParseOption{newParseOption(identFormKey{}, v)}
}
// WithFlattenAudience specifies if the "aud" claim should be flattened
// to a single string upon the token being serialized to JSON.
//
// This is sometimes important when a JWT consumer does not understand that
// the "aud" claim can actually take the form of an array of strings.
//
// The default value is `false`, which means that "aud" claims are always
// rendered as a arrays of strings. This setting has a global effect,
// and will change the behavior for all JWT serialization.
func WithFlattenAudience(v bool) GlobalOption {
return &globalOption{option.New(identFlattenAudience{}, v)}
}
type claimPair struct {
Name string
Value interface{}
}
// WithTypedClaim allows a private claim to be parsed into the object type of
// your choice. It works much like the RegisterCustomField, but the effect
// is only applicable to the jwt.Parse function call which receives this option.
//
// While this can be extremely useful, this option should be used with caution:
// There are many caveats that your entire team/user-base needs to be aware of,
// and therefore in general its use is discouraged. Only use it when you know
// what you are doing, and you document its use clearly for others.
//
// First and foremost, this is a "per-object" option. Meaning that given the same
// serialized format, it is possible to generate two objects whose internal
// representations may differ. That is, if you parse one _WITH_ the option,
// and the other _WITHOUT_, their internal representation may completely differ.
// This could potentially lead to problems.
//
// Second, specifying this option will slightly slow down the decoding process
// as it needs to consult multiple definitions sources (global and local), so
// be careful if you are decoding a large number of tokens, as the effects will stack up.
//
// Finally, this option will also NOT work unless the tokens themselves support such
// parsing mechanism. For example, while tokens obtained from `jwt.New()` and
// `openid.New()` will respect this option, if you provide your own custom
// token type, it will need to implement the TokenWithDecodeCtx interface.
func WithTypedClaim(name string, object interface{}) ParseOption {
return newParseOption(identTypedClaim{}, claimPair{Name: name, Value: object})
}
// WithRequiredClaim specifies that the claim identified the given name
// must exist in the token. Only the existence of the claim is checked:
// the actual value associated with that field is not checked.
func WithRequiredClaim(name string) ValidateOption {
return WithValidator(IsRequired(name))
}
// WithMaxDelta specifies that given two claims `c1` and `c2` that represent time, the difference in
// time.Duration must be less than equal to the value specified by `d`. If `c1` or `c2` is the
// empty string, the current time (as computed by `time.Now` or the object passed via
// `WithClock()`) is used for the comparison.
//
// `c1` and `c2` are also assumed to be required, therefore not providing either claim in the
// token will result in an error.
//
// Because there is no way of reliably knowing how to parse private claims, we currently only
// support `iat`, `exp`, and `nbf` claims.
//
// If the empty string is passed to c1 or c2, then the current time (as calculated by time.Now() or
// the clock object provided via WithClock()) is used.
//
// For example, in order to specify that `exp` - `iat` should be less than 10*time.Second, you would write
//
// jwt.Validate(token, jwt.WithMaxDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey))
//
// If AcceptableSkew of 2 second is specified, the above will return valid for any value of
// `exp` - `iat` between 8 (10-2) and 12 (10+2).
func WithMaxDelta(dur time.Duration, c1, c2 string) ValidateOption {
return WithValidator(MaxDeltaIs(c1, c2, dur))
}
// WithMinDelta is almost exactly the same as WithMaxDelta, but force validation to fail if
// the difference between time claims are less than dur.
//
// For example, in order to specify that `exp` - `iat` should be greater than 10*time.Second, you would write
//
// jwt.Validate(token, jwt.WithMinDelta(10*time.Second, jwt.ExpirationKey, jwt.IssuedAtKey))
//
// The validation would fail if the difference is less than 10 seconds.
//
func WithMinDelta(dur time.Duration, c1, c2 string) ValidateOption {
return WithValidator(MinDeltaIs(c1, c2, dur))
}
// WithValidator validates the token with the given Validator.
//
// For example, in order to validate tokens that are only valid during August, you would write
//
// validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) error {
// if time.Now().Month() != 8 {
// return fmt.Errorf(`tokens are only valid during August!`)
// }
// return nil
// })
// err := jwt.Validate(token, jwt.WithValidator(validator))
//
func WithValidator(v Validator) ValidateOption {
return newValidateOption(identValidator{}, v)
}
type decryptParams struct {
alg jwa.KeyEncryptionAlgorithm
key interface{}
}
type DecryptParameters interface {
Algorithm() jwa.KeyEncryptionAlgorithm
Key() interface{}
}
func (dp *decryptParams) Algorithm() jwa.KeyEncryptionAlgorithm {
return dp.alg
}
func (dp *decryptParams) Key() interface{} {
return dp.key
}
// WithDecrypt allows users to specify parameters for decryption using
// `jwe.Decrypt`. You must specify this if your JWT is encrypted.
func WithDecrypt(alg jwa.KeyEncryptionAlgorithm, key interface{}) ParseOption {
return newParseOption(identDecrypt{}, &decryptParams{
alg: alg,
key: key,
})
}
// WithPedantic enables pedantic mode for parsing JWTs. Currently this only
// applies to checking for the correct `typ` and/or `cty` when necessary.
func WithPedantic(v bool) ParseOption {
return newParseOption(identPedantic{}, v)
}
// InferAlgorithmFromKey allows jwt.Parse to guess the signature algorithm
// passed to `jws.Verify()`, in case the key you provided does not have a proper `alg` header.
//
// Compared to providing explicit `alg` from the key this is slower, and in
// case our heuristics are wrong or outdated, may fail to verify the token.
// Also, automatic detection of signature verification methods are always
// more vulnerable for potential attack vectors.
//
// It is highly recommended that you fix your key to contain a proper `alg`
// header field instead of resorting to using this option, but sometimes
// it just needs to happen.
//
// Your JWT still need to have an `alg` field, and it must match one of the
// candidates that we produce for your key
func InferAlgorithmFromKey(v bool) ParseOption {
return newParseOption(identInferAlgorithmFromKey{}, v)
}
// KeySetProvider is an interface for objects that can choose the appropriate
// jwk.Set to be used when verifying JWTs
type KeySetProvider interface {
// KeySetFrom returns the jwk.Set to be used to verify the token.
// Keep in mind that the token at the point when the method is called is NOT VERIFIED.
// DO NOT trust the contents of the Token too much. For example, do not take the
// hint as to which signature algorithm to use from the token itself.
KeySetFrom(Token) (jwk.Set, error)
}
// KeySetProviderFunc is an implementation of KeySetProvider that is based
// on a function.
type KeySetProviderFunc func(Token) (jwk.Set, error)
func (fn KeySetProviderFunc) KeySetFrom(t Token) (jwk.Set, error) {
return fn(t)
}
// WithKeySetProvider allows users to specify an object to choose which
// jwk.Set to use for verification.
//
// If provided with WithKeySet(), WithKeySet() option takes precedence.
func WithKeySetProvider(p KeySetProvider) ParseOption {
return newParseOption(identKeySetProvider{}, p)
}
// WithContext allows you to specify a context.Context object to be used
// with `jwt.Validate()` option.
//
// Please be aware that in the next major release of this library,
// `jwt.Validate()`'s signature will change to include an explicit
// `context.Context` object.
func WithContext(ctx context.Context) ValidateOption {
return newValidateOption(identContext{}, ctx)
}
// WithVerifyAuto specifies that the JWS verification should be performed
// using `jws.VerifyAuto()`, which in turn attempts to verify the message
// using values that are stored within the JWS message.
//
// Only passing this option to `jwt.Parse()` will not result in a successful
// verification. Please make sure to carefully read the documentation in
// `jws.VerifyAuto()`, and provide the necessary Whitelist object via
// `jwt.WithFetchWhitelist()`
//
// You might also consider using a backoff policy by using `jwt.WithFetchBackoff()`
// to control the number of requests being made.
func WithVerifyAuto(v bool) ParseOption {
return newParseOption(identVerifyAuto{}, v)
}
// WithFetchWhitelist specifies the `jwk.Whitelist` object that should be
// passed to `jws.VerifyAuto()`, which in turn will be passed to `jwk.Fetch()`
//
// This is a wrapper over `jws.WithFetchWhitelist()` that can be passed
// to `jwt.Parse()`, and will be ignored if you spcify `jws.WithJWKSetFetcher()`
func WithFetchWhitelist(wl jwk.Whitelist) ParseOption {
return newParseOption(identFetchWhitelist{}, wl)
}
// WithHTTPClient specifies the `*http.Client` object that should be
// passed to `jws.VerifyAuto()`, which in turn will be passed to `jwk.Fetch()`
//
// This is a wrapper over `jws.WithHTTPClient()` that can be passed
// to `jwt.Parse()`, and will be ignored if you spcify `jws.WithJWKSetFetcher()`
func WithHTTPClient(httpcl *http.Client) ParseOption {
return newParseOption(identHTTPClient{}, httpcl)
}
// WithFetchBackoff specifies the `backoff.Policy` object that should be
// passed to `jws.VerifyAuto()`, which in turn will be passed to `jwk.Fetch()`
//
// This is a wrapper over `jws.WithFetchBackoff()` that can be passed
// to `jwt.Parse()`, and will be ignored if you spcify `jws.WithJWKSetFetcher()`
func WithFetchBackoff(b backoff.Policy) ParseOption {
return newParseOption(identFetchBackoff{}, b)
}
// WithJWKSetFetcher specifies the `jws.JWKSetFetcher` object that should be
// passed to `jws.VerifyAuto()`
//
// This is a wrapper over `jws.WithJWKSetFetcher()` that can be passed
// to `jwt.Parse()`.
func WithJWKSetFetcher(f jws.JWKSetFetcher) ParseOption {
return newParseOption(identJWKSetFetcher{}, f)
}

View File

@ -33,3 +33,5 @@ coverage.out
# I redirect my test output to files named "out" way too often
out
cmd/jwx/jwx

View File

@ -28,7 +28,6 @@ linters:
- gofumpt
- golint #deprecated
- gomnd
- gomoddirectives # I think it's broken
- gosec
- govet
- interfacer # deprecated

75
vendor/github.com/lestrrat-go/jwx/v2/Changes generated vendored Normal file
View File

@ -0,0 +1,75 @@
Changes
=======
v2 has many incompatibilities with v1. To see the full list of differences between
v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes-v2.md)
v2.0.2 - 23 May 2022
[Bug Fixes][Security]
* [jwe] An old bug from at least 7 years ago existed in handling AES-CBC unpadding,
where the unpad operation might remove more bytes than necessary (#744)
This affects all jwx code that is available before v2.0.2 and v1.2.25.
[New Features]
* [jwt] RFC3339 timestamps are also accepted for Numeric Date types in JWT tokens.
This allows users to parse servers that errnously use RFC3339 timestamps in
some pre-defined fields. You can change this behavior by setting
`jwt.WithNumericDateParsePedantic` to `false`
* [jwt] `jwt.WithNumericDateParsePedantic` has been added. This is a global
option that is set using `jwt.Settings`
v2.0.1 - 06 May 2022
* [jwk] `jwk.Set` had erronously been documented as not returning an error
when the same key already exists in the set. This is a behavior change
since v2, and it was missing in the docs (#730)
* [jwt] `jwt.ErrMissingRequiredClaim` has been deprecated. Please use
`jwt.ErrRequiredClaim` instead.
* [jwt] `jwt.WithNumericDateParsePrecision` and `jwt.WithNumericDateFormatPrecision`
have been added to parse and format fractional seconds. These options can be
passed to `jwt.Settings`.
The default precision is set to 0, and fractional portions are not parsed nor
formatted. The precision may be set up to 9.
* `golang.org/x/crypto` has been upgraded (#724)
* `io/ioutil` has been removed from the source code.
v2.0.0 - 24 Apr 2022
* This i the first v2 release, which represents a set of design changes
that were learnt over the previous 2 years. As a result the v2 API
should be much more consistent and uniform across packages, and
should be much more flexible to accomodate real-world needs.
For a complete list of changes, please see the Changes-v2.md file,
or check the diff at https://github.com/lestrrat-go/jwx/compare/v1...v2
[Miscellaneous]
* Minor house cleaning on code generation tools
[jwt]
* `jwt.ErrMissingRequiredClaim()` has been added
v2.0.0-beta2 - 16 Apr 2022
[jwk]
* Updated `jwk.Set` API and reflected pending changes from v1 which were
left over. Please see Changes-v2.md file for details.
* Added `jwk.CachedSet`, a shim over `jwk.Cache` that allows you to
have to write wrappers around `jwk.Cache` that retrieves a particular
`jwk.Set` out of it. You can use it to, for example, pass `jwk.CachedSet`
to a `jws.Verify`
cache := jwk.NewCache(ctx)
cache.Register(ctx, jwksURL)
cachedSet := jwk.NewCachedSet(cache, jwksURL)
jws.Verify(signed, jws.WithKeySet(cachedSet))
v2.0.0-beta1 - 09 Apr 2022
[Miscellaneous]
* Renamed Changes.v2 to Changes-v2.md
* Housecleaning for lint action.
* While v2 was not affected, ported over equivalent test for #681 to catch
regressions in the future.
* Please note that there is no stability guarantees on pre-releases.
v2.0.0-alpha1 - 04 Apr 2022
* Initial pre-release of v2 line. Please note that there is no stability guarantees
on pre-releases.

390
vendor/github.com/lestrrat-go/jwx/v2/Changes-v2.md generated vendored Normal file
View File

@ -0,0 +1,390 @@
# Incompatible Changes from v1 to v2
These are changes that are incompatible with the v1.x.x version.
* [tl;dr](#tldr) - If you don't feel like reading the details -- but you will read the details, right?
* [Detailed List of Changes](#detailed-list-of-changes) - A comprehensive list of changes from v1 to v2
# tl;dr
## JWT
```go
// most basic
jwt.Parse(serialized, jwt.WithKey(alg, key)) // NOTE: verification and validation are ENABLED by default!
jwt.Sign(token, jwt.WithKey(alg,key))
// with a jwk.Set
jwt.Parse(serialized, jwt.WithKeySet(set))
// UseDefault/InferAlgorithm with JWKS
jwt.Parse(serialized, jwt.WithKeySet(set,
jws.WithUseDefault(true), jws.WithInferAlgorithm(true))
// Use `jku`
jwt.Parse(serialized, jwt.WithVerifyAuto(...))
// Any other custom key provisioning (using functions in this
// example, but can be anything that fulfills jws.KeyProvider)
jwt.Parse(serialized, jwt.WithKeyProvider(jws.KeyProviderFunc(...)))
```
## JWK
```go
// jwk.New() was confusing. Renamed to fit the actual implementation
key, err := jwk.FromRaw(rawKey)
// Algorithm() now returns jwa.KeyAlgorithm type. `jws.Sign()`
// and other function that receive JWK algorithm names accept
// this new type, so you can use the same key and do the following
// (previosly you needed to type assert)
jws.Sign(payload, jws.WithKey(key.Algorithm(), key))
// If you need the specific type, type assert
key.Algorithm().(jwa.SignatureAlgorithm)
// jwk.AutoRefresh is no more. Use jwk.Cache
cache := jwk.NewCache(ctx, options...)
// Certificate chains are no longer jwk.CertificateChain type, but
// *(github.com/lestrrat-go/jwx/cert).Chain
cc := key.X509CertChain() // this is *cert.Chain now
```
## JWS
```go
// basic
jws.Sign(payload, jws.WithKey(alg, key))
jws.Sign(payload, jws.WithKey(alg, key), jws.WithKey(alg, key), jws.WithJSON(true))
jws.Verify(signed, jws.WithKey(alg, key))
// other ways to pass the key
jws.Sign(payload, jws.WithKeySet(jwks))
jws.Sign(payload, jws.WithKeyProvider(kp))
// retrieve the key that succeeded in verifying
var keyUsed interface{}
jws.Verify(signed, jws.WithKeySet(jwks), jws.WithKeyUsed(&keyUsed))
```
## JWE
```go
// basic
jwe.Encrypt(payload, jwe.WithKey(alg, key)) // other defaults are infered
jwe.Encrypt(payload, jwe.WithKey(alg, key), jwe.WithKey(alg, key), jwe.WithJSON(true))
jwe.Decrypt(encrypted, jwe.WithKey(alg, key))
// other ways to pass the key
jwe.Encrypt(payload, jwe.WithKeySet(jwks))
jwe.Encrypt(payload, jwe.WithKeyProvider(kp))
// retrieve the key that succeeded in decrypting
var keyUsed interface{}
jwe.Verify(signed, jwe.WithKeySet(jwks), jwe.WithKeyUsed(&keyUsed))
```
# Detailed List of Changes
## Module
* Module now requires go 1.16
* Use of github.com/pkg/errors is no more. If you were relying on bevaior
that depends on the errors being an instance of github.com/pkg/errors
then you need to change your code
* File-generation tools have been moved out of internal/ directories.
These files pre-dates Go modules, and they were in internal/ in order
to avoid being listed in the `go doc` -- however, now that we can
make them separate modules this is no longer necessary.
* New package `cert` has been added to handle `x5c` certificate
chains, and to work with certificates
* cert.Chain to store base64 encoded ASN.1 DER format certificates
* cert.EncodeBase64 to encode ASN.1 DER format certificate using base64
* cert.Create to create a base64 encoded ASN.1 DER format certificates
* cert.Parse to parse base64 encoded ASN.1 DER format certificates
## JWE
* `jwe.Compact()`'s signature has changed to
`jwe.Compact(*jwe.Message, ...jwe.CompactOption)`
* `jwe.JSON()` has been removed. You can generate JSON serialization
using `jwe.Encrypt(jwe.WitJSON())` or `json.Marshal(jwe.Message)`
* `(jwe.Message).Decrypt()` has been removed. Since formatting of the
original serialized message matters (including whitespace), using a parsed
object was inherently confusing.
* `jwe.Encrypt()` can now generate JWE messages in either compact or JSON
forms. By default, the compact form is used. JSON format can be
enabled by using the `jwe.WithJSON` option.
* `jwe.Encrypt()` can now accept multiple keys by passing multiple
`jwe.WithKey()` options. This can be used with `jwe.WithJSON` to
create JWE messages with multiple recipients.
* `jwe.DecryptEncryptOption()` has been renamed to `jwe.EncryptDecryptOption()`.
This is so that it is more uniform with `jws` equivalent of `jws.SignVerifyOption()`
where the producer (`Sign`) comes before the consumer (`Verify`) in the naming
* `jwe.WithCompact` and `jwe.WithJSON` options have been added
to control the serialization format.
* jwe.Decrypt()'s method signature has been changed to `jwt.Decrypt([]byte, ...jwe.DecryptOption) ([]byte, error)`.
These options can be stacked. Therefore, you could configure the
verification process to attempt a static key pair, a JWKS, and only
try other forms if the first two fails, for example.
- For static key pair, use `jwe.WithKey()`
- For static JWKS, use `jwe.WithKeySet()` (NOTE: InferAlgorithmFromKey like in `jws` package is NOT supported)
- For custom, possibly dynamic key provisioning, use `jwe.WithKeyProvider()`
* jwe.Decrypter has been unexported. Users did not need this.
* jwe.WithKeyProvider() has been added to specify arbitrary
code to specify which keys to try.
* jwe.KeyProvider interface has been added
* jwe.KeyProviderFunc has been added
* `WithPostParser()` has been removed. You can achieve the same effect
by using `jwe.WithKeyProvider()`. Because this was the only consumer for
`jwe.DecryptCtx`, this type has been removed as well.
* `x5c` field type has been changed to `*cert.Chain` instead of `[]string`
* Method signature for `jwe.Parse()` has been changed to include options,
but options are currently not used
* `jwe.ReadFile` now supports the option `jwe.WithFS` which allows you to
read data from arbitrary `fs.FS` objects
* jwe.WithKeyUsed has been added to allow users to retrieve
the key used for decryption. This is useful in cases you provided
multiple keys and you want to know which one was successful
## JWK
* `jwk.New()` has been renamed to `jwk.FromRaw()`, which hopefully will
make it easier for the users what the input should be.
* `jwk.Set` has many interface changes:
* Changed methods to match jwk.Key and its semantics:
* Field is now Get() (returns values for arbitrary fields other than keys). Fetching a key is done via Key()
* Remove() now removes arbitrary fields, not keys. to remove keys, use RemoveKey()
* Iterate has been added to iterate through all non-key fields.
* Add is now AddKey(Key) string, and returns an error when the same key is added
* Get is now Key(int) (Key, bool)
* Remove is now RemoveKey(Key) error
* Iterate is now Keys(context.Context) KeyIterator
* Clear is now Clear() error
* `jwk.CachedSet` has been added. You can create a `jwk.Set` that is backed by
`jwk.Cache` so you can do this:
```go
cache := jkw.NewCache(ctx)
cachedSet := jwk.NewCachedSet(cache, jwksURI)
// cachedSet is always the refreshed, cached version from jwk.Cache
jws.Verify(signed, jws.WithKeySet(cachedSet))
```
* `jwk.NewRSAPRivateKey()`, `jwk.NewECDSAPrivateKey()`, etc have been removed.
There is no longer any way to create concrete types of `jwk.Key`
* `jwk.Key` type no longer supports direct unmarshaling via `json.Unmarshal()`,
because you can no longer instantiate concrete `jwk.Key` types. You will need to
use `jwk.ParseKey()`. See the documentation for ways to parse JWKs.
* `(jwk.Key).Algorithm()` is now of `jwk.KeyAlgorithm` type. This field used
to be `string` and therefore could not be passed directly to `jwt.Sign()`
`jws.Sign()`, `jwe.Encrypt()`, et al. This is no longer the case, and
now you can pass it directly. See
https://github.com/lestrrat-go/jwx/blob/v2/docs/99-faq.md#why-is-jwkkeyalgorithm-and-jwakeyalgorithm-so-confusing
for more details
* `jwk.Fetcher` and `jwk.FetchFunc` has been added.
They represent something that can fetch a `jwk.Set`
* `jwk.CertificateChain` has been removed, use `*cert.Chain`
* `x5c` field type has been changed to `*cert.Chain` instead of `[]*x509.Certificate`
* `jwk.ReadFile` now supports the option `jwk.WithFS` which allows you to
read data from arbitrary `fs.FS` objects
* Added `jwk.PostFetcher`, `jwk.PostFetchFunc`, and `jwk.WithPostFetch` to
allow users to get at the `jwk.Set` that was fetched in `jwk.Cache`.
This will make it possible for users to supply extra information and edit
`jwk.Set` after it has been fetched and parsed, but before it is cached.
You could, for example, modify the `alg` field so that it's easier to
work with when you use it in `jws.Verify` later.
* Reworked `jwk.AutoRefresh` in terms of `github.com/lestrrat-go/httprc`
and renamed it `jwk.Cache`.
Major difference between `jwk.AutoRefresh` and `jwk.Cache` is that while
former used one `time.Timer` per resource, the latter uses a static timer
(based on `jwk.WithRefreshWindow()` value, default 15 minutes) that periodically
refreshes all resources that were due to be refreshed within that time frame.
This method may cause your updates to happen slightly later, but uses significantly
less resources and is less prone to clogging.
* Reimplemented `jwk.Fetch` in terms of `github.com/lestrrat-go/httprc`.
* Previously `jwk.Fetch` and `jwk.AutoRefresh` respected backoff options,
but this has been removed. This is to avoid unwanted clogging of the fetch workers
which is the default processing mode in `github.com/lestrrat-go/httprc`.
If you are using backoffs, you need to control your inputs more carefully so as to
not clog your fetch queue, and therefore you should be writing custom code that
suits your needs
## JWS
* `jws.Sign()` can now generate JWS messages in either compact or JSON
forms. By default, the compact form is used. JSON format can be
enabled by using the `jws.WithJSON` option.
* `jws.Sign()` can now accept multiple keys by passing multiple
`jws.WithKey()` options. This can be used with `jws.WithJSON` to
create JWS messages with multiple signatures.
* `jws.WithCompact` and `jws.WithJSON` options have been added
to control the serialization format.
* jws.Verify()'s method signature has been changed to `jwt.Verify([]byte, ...jws.VerifyOption) ([]byte, error)`.
These options can be stacked. Therefore, you could configure the
verification process to attempt a static key pair, a JWKS, and only
try other forms if the first two fails, for example.
- For static key pair, use `jws.WithKey()`
- For static JWKS, use `jws.WithKeySet()`
- For enabling verification using `jku`, use `jws.WithVerifyAuto()`
- For custom, possibly dynamic key provisioning, use `jws.WithKeyProvider()`
* jws.WithVerify() has been removed.
* jws.WithKey() has been added to specify an algorithm + key to
verify the payload with.
* jws.WithKeySet() has been added to specify a JWKS to be used for
verification. By default `kid` AND `alg` must match between the signature
and the key.
The option can take further suboptions:
```go
jws.Parse(serialized,
jws.WithKeySet(set,
// by default `kid` is required. set false to disable.
jws.WithRequireKid(false),
// optionally skip matching kid if there's exactly one key in set
jws.WithUseDefault(true),
// infer algorithm name from key type
jws.WithInferAlgorithm(true),
),
)
```
* `jws.VerifuAuto` has been removed in favor of using
`jws.WithVerifyAuto` option with `jws.Verify()`
* `jws.WithVerifyAuto` has been added to enable verification
using `jku`.
The first argument must be a jwk.Fetcher object, but can be
set to `nil` to use the default implementation which is `jwk.Fetch`
The rest of the arguments are treated as options passed to the
`(jwk.Fetcher).Fetch()` function.
* Remove `jws.WithPayloadSigner()`. This should be completely repleceable
using `jws.WithKey()`
* jws.WithKeyProvider() has been added to specify arbitrary
code to specify which keys to try.
* jws.KeyProvider interface has been added
* jws.KeyProviderFunc has been added
* jws.WithKeyUsed has been added to allow users to retrieve
the key used for verification. This is useful in cases you provided
multiple keys and you want to know which one was successful
* `x5c` field type has been changed to `*cert.Chain` instead of `[]string`
* `jws.ReadFile` now supports the option `jws.WithFS` which allows you to
read data from arbitrary `fs.FS` objects
## JWT
* `jwt.Parse` now verifies the signature and validates the token
by default. You must disable it explicitly using `jwt.WithValidate(false)`
and/or `jwt.WithVerify(false)` if you only want to parse the JWT message.
If you don't want either, a convenience function `jwt.ParseInsecure`
has been added.
* `jwt.Parse` can only parse raw JWT (JSON) or JWS (JSON or Compact).
It no longer accepts JWE messages.
* `jwt.WithDecrypt` has been removed
* `jwt.WithJweHeaders` has been removed
* `jwt.WithVerify()` has been renamed to `jwt.WithKey()`. The option can
be used for signing, encryption, and parsing.
* `jwt.Validator` has been changed to return `jwt.ValidationError`.
If you provide a custom validator, you should wrap the error with
`jwt.NewValidationError()`
* `jwt.UseDefault()` has been removed. You should use `jws.WithUseDefault()`
as a suboption in the `jwt.WithKeySet()` option.
```go
jwt.Parse(serialized, jwt.WithKeySet(set, jws.WithUseDefault(true)))
```
* `jwt.InferAlgorithmFromKey()` has been removed. You should use
`jws.WithInferAlgorithmFromKey()` as a suboption in the `jwt.WithKeySet()` option.
```go
jwt.Parse(serialized, jwt.WithKeySet(set, jws.WithInferAlgorithmFromKey(true)))
```
* jwt.WithKeySetProvider has been removed. Use `jwt.WithKeyProvider()`
instead. If jwt.WithKeyProvider seems a bit complicated, use a combination of
JWS parse, no-verify/validate JWT parse, and an extra JWS verify:
```go
msg, _ := jws.Parse(signed)
token, _ := jwt.Parse(msg.Payload(), jwt.WithVerify(false), jwt.WithValidate(false))
// Get information out of token, for example, `iss`
switch token.Issuer() {
case ...:
jws.Verify(signed, jwt.WithKey(...))
}
```
* `jwt.WithHeaders` and `jwt.WithJwsHeaders` have been removed.
You should be able to use the new `jwt.WithKey` option to pass headers
* `jwt.WithSignOption` and `jwt.WithEncryptOption` have been added as
escape hatches for options that are declared in `jws` and `jwe` packages
but not in `jwt`
* `jwt.ReadFile` now supports the option `jwt.WithFS` which allows you to
read data from arbitrary `fs.FS` objects
* `jwt.Sign()` has been changed so that it works more like the new `jws.Sign()`

View File

@ -4,7 +4,6 @@ generate:
@$(MAKE) generate-jwa generate-jwe generate-jwk generate-jws generate-jwt
generate-%:
@echo "> Generating for $(patsubst generate-%,%,$@)"
@go generate $(shell pwd -P)/$(patsubst generate-%,%,$@)
realclean:

248
vendor/github.com/lestrrat-go/jwx/v2/README.md generated vendored Normal file
View File

@ -0,0 +1,248 @@
# github.com/lestrrat-go/jwx/v2 ![](https://github.com/lestrrat-go/jwx/workflows/CI/badge.svg?branch=v2) [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v2.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2) [![codecov.io](https://codecov.io/github/lestrrat-go/jwx/coverage.svg?branch=v2)](https://codecov.io/github/lestrrat-go/jwx?branch=v2)
Go module implementing various JWx (JWA/JWE/JWK/JWS/JWT, otherwise known as JOSE) technologies.
If you are using this module in your product or your company, please add your product and/or company name in the [Wiki](https://github.com/lestrrat-go/jwx/wiki/Users)! It really helps keeping up our motivation.
# Features
* Complete coverage of JWA/JWE/JWK/JWS/JWT, not just JWT+minimum tool set.
* Supports JWS messages with multiple signatures, both compact and JSON serialization
* Supports JWS with detached payload
* Supports JWS with unencoded payload (RFC7797)
* Supports JWE messages with multiple recipients, both compact and JSON serialization
* Most operations work with either JWK or raw keys e.g. *rsa.PrivateKey, *ecdsa.PrivateKey, etc).
* Opinionated, but very uniform API. Everything is symmetric, and follows a standard convetion
* jws.Parse/Verify/Sign
* jwe.Parse/Encrypt/Decrypt
* Arguments are organized as explicit required paramters and optional WithXXXX() style options.
* Extra utilities
* `jwk.Cache` to always keep a JWKS up-to-date
Some more in-depth discussion on why you might want to use this library over others
can be found in the [Description section](#description)
# SYNOPSIS
<!-- INCLUDE(examples/jwx_readme_example_test.go) -->
```go
package examples_test
import (
"bytes"
"fmt"
"net/http"
"time"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/lestrrat-go/jwx/v2/jwt"
)
func ExampleJWX() {
// Parse, serialize, slice and dice JWKs!
privkey, err := jwk.ParseKey(jsonRSAPrivateKey)
if err != nil {
fmt.Printf("failed to parse JWK: %s\n", err)
return
}
pubkey, err := jwk.PublicKeyOf(privkey)
if err != nil {
fmt.Printf("failed to get public key: %s\n", err)
return
}
// Work with JWTs!
{
// Build a JWT!
tok, err := jwt.NewBuilder().
Issuer(`github.com/lestrrat-go/jwx`).
IssuedAt(time.Now()).
Build()
if err != nil {
fmt.Printf("failed to build token: %s\n", err)
return
}
// Sign a JWT!
signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256, privkey))
if err != nil {
fmt.Printf("failed to sign token: %s\n", err)
return
}
// Verify a JWT!
{
verifiedToken, err := jwt.Parse(signed, jwt.WithKey(jwa.RS256, pubkey))
if err != nil {
fmt.Printf("failed to verify JWS: %s\n", err)
return
}
_ = verifiedToken
}
// Work with *http.Request!
{
req, err := http.NewRequest(http.MethodGet, `https://github.com/lestrrat-go/jwx`, nil)
req.Header.Set(`Authorization`, fmt.Sprintf(`Bearer %s`, signed))
verifiedToken, err := jwt.ParseRequest(req, jwt.WithKey(jwa.RS256, pubkey))
if err != nil {
fmt.Printf("failed to verify token from HTTP request: %s\n", err)
return
}
_ = verifiedToken
}
}
// Encrypt and Decrypt arbitrary payload with JWE!
{
encrypted, err := jwe.Encrypt(payloadLoremIpsum, jwe.WithKey(jwa.RSA_OAEP, jwkRSAPublicKey))
if err != nil {
fmt.Printf("failed to encrypt payload: %s\n", err)
return
}
decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP, jwkRSAPrivateKey))
if err != nil {
fmt.Printf("failed to decrypt payload: %s\n", err)
return
}
if !bytes.Equal(decrypted, payloadLoremIpsum) {
fmt.Printf("verified payload did not match\n")
return
}
}
// Sign and Verify arbitrary payload with JWS!
{
signed, err := jws.Sign(payloadLoremIpsum, jws.WithKey(jwa.RS256, jwkRSAPrivateKey))
if err != nil {
fmt.Printf("failed to sign payload: %s\n", err)
return
}
verified, err := jws.Verify(signed, jws.WithKey(jwa.RS256, jwkRSAPublicKey))
if err != nil {
fmt.Printf("failed to verify payload: %s\n", err)
return
}
if !bytes.Equal(verified, payloadLoremIpsum) {
fmt.Printf("verified payload did not match\n")
return
}
}
// OUTPUT:
}
```
source: [examples/jwx_readme_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jwx_readme_example_test.go)
<!-- END INCLUDE -->
# How-to Documentation
* [API documentation](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2)
* [How-to style documentation](./docs)
* [Runnable Examples](./examples)
# Description
This Go module implements JWA, JWE, JWK, JWS, and JWT. Please see the following table for the list of
available packages:
| Package name | Notes |
|-----------------------------------------------------------|-------------------------------------------------|
| [jwt](https://github.com/lestrrat-go/jwx/tree/v2/jwt) | [RFC 7519](https://tools.ietf.org/html/rfc7519) |
| [jwk](https://github.com/lestrrat-go/jwx/tree/v2/jwk) | [RFC 7517](https://tools.ietf.org/html/rfc7517) + [RFC 7638](https://tools.ietf.org/html/rfc7638) |
| [jwa](https://github.com/lestrrat-go/jwx/tree/v2/jwa) | [RFC 7518](https://tools.ietf.org/html/rfc7518) |
| [jws](https://github.com/lestrrat-go/jwx/tree/v2/jws) | [RFC 7515](https://tools.ietf.org/html/rfc7515) + [RFC 7797](https://tools.ietf.org/html/rfc7797) |
| [jwe](https://github.com/lestrrat-go/jwx/tree/v2/jwe) | [RFC 7516](https://tools.ietf.org/html/rfc7516) |
## History
My goal was to write a server that heavily uses JWK and JWT. At first glance
the libraries that already exist seemed sufficient, but soon I realized that
1. To completely implement the protocols, I needed the entire JWT, JWK, JWS, JWE (and JWA, by necessity).
2. Most of the libraries that existed only deal with a subset of the various JWx specifications that were necessary to implement their specific needs
For example, a certain library looks like it had most of JWS, JWE, JWK covered, but then it lacked the ability to include private claims in its JWT responses. Another library had support of all the private claims, but completely lacked in its flexibility to generate various different response formats.
Because I was writing the server side (and the client side for testing), I needed the *entire* JOSE toolset to properly implement my server, **and** they needed to be *flexible* enough to fulfill the entire spec that I was writing.
So here's `github.com/lestrrat-go/jwx/v2`. This library is extensible, customizable, and hopefully well organized to the point that it is easy for you to slice and dice it.
## Why would I use this library?
There are several other major Go modules that handle JWT and related data formats,
so why should you use this library?
From a purely functional perspective, the only major difference is this:
Whereas most other projects only deal with what they seem necessary to handle
JWTs, this module handles the **_entire_** spectrum of JWS, JWE, JWK, and JWT.
That is, if you need to not only parse JWTs, but also to control JWKs, or
if you need to handle payloads that are NOT JWTs, you should probably consider
using this module. You should also note that JWT is built _on top_ of those
other technologies. You simply cannot have a complete JWT package without
implementing the entirety of JWS/JWE/JWK, which this library does.
Next, from an implementation perspective, this module differs significantly
from others in that it tries very hard to expose only the APIs, and not the
internal data. For example, individual JWT claims are not accessible through
struct field lookups. You need to use one of the getter methods.
This is because this library takes the stance that the end user is fully capable
and even willing to shoot themselves on the foot when presented with a lax
API. By making sure that users do not have access to open structs, we can protect
users from doing silly things like creating _incomplete_ structs, or access the
structs concurrently without any protection. This structure also allows
us to put extra smarts in the structs, such as doing the right thing when
you want to parse / write custom fields (this module does not require the user
to specify alternate structs to parse objects with custom fields)
In the end I think it comes down to your usage pattern, and priorities.
Some general guidelines that come to mind are:
* If you want a single library to handle everything JWx, such as using JWE, JWK, JWS, handling [auto-refreshing JWKs](https://github.com/lestrrat-go/jwx/blob/v2/docs/04-jwk.md#auto-refreshing-remote-keys), use this module.
* If you want to honor all possible custom fields transparently, use this module.
* If you want a standardized clean API, use this module.
Otherwise, feel free to choose something else.
# Contributions
## Issues
For bug reports and feature requests, please try to follow the issue templates as much as possible.
For either bug reports or feature requests, failing tests are even better.
## Pull Requests
Please make sure to include tests that excercise the changes you made.
If you are editing auto-generated files (those files with the `_gen.go` suffix, please make sure that you do the following:
1. Edit the generator, not the generated files (e.g. internal/cmd/genreadfile/main.go)
2. Run `make generate` (or `go generate`) to generate the new code
3. Commit _both_ the generator _and_ the generated files
## Discussions / Usage
Please try [discussions](https://github.com/lestrrat-go/jwx/tree/v2/discussions) first.
# Related Modules
* [github.com/lestrrat-go/echo-middileware-jwx](https://github.com/lestrrat-go/echo-middleware-jwx) - Sample Echo middleware
* [github.com/jwx-go/crypto-signer/gcp](https://github.com/jwx-go/crypto-signer/tree/main/gcp) - GCP KMS wrapper that implements [`crypto.Signer`](https://pkg.go.dev/crypto#Signer)
* [github.com/jwx-go/crypto-signer/aws](https://github.com/jwx-go/crypto-signer/tree/main/aws) - AWS KMS wrapper that implements [`crypto.Signer`](https://pkg.go.dev/crypto#Signer)
# Credits
* Initial work on this library was generously sponsored by HDE Inc (https://www.hde.co.jp)
* Lots of code, especially JWE was initially taken from go-jose library (https://github.com/square/go-jose)
* Lots of individual contributors have helped this project over the years. Thank each and everyone of you very much.

48
vendor/github.com/lestrrat-go/jwx/v2/cert/cert.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
package cert
import (
"crypto/x509"
stdlibb64 "encoding/base64"
"fmt"
"io"
"github.com/lestrrat-go/jwx/v2/internal/base64"
)
// Create is a wrapper around x509.CreateCertificate, but it additionally
// encodes it in base64 so that it can be easily added to `x5c` fields
func Create(rand io.Reader, template, parent *x509.Certificate, pub, priv interface{}) ([]byte, error) {
der, err := x509.CreateCertificate(rand, template, parent, pub, priv)
if err != nil {
return nil, fmt.Errorf(`failed to create x509 certificate: %w`, err)
}
return EncodeBase64(der)
}
// EncodeBase64 is a utility function to encode ASN.1 DER certificates
// using base64 encoding. This operation is normally done by `pem.Encode`
// but since PEM would include the markers (`-----BEGIN`, and the like)
// while `x5c` fields do not need this, this function can be used to
// shave off a few lines
func EncodeBase64(der []byte) ([]byte, error) {
enc := stdlibb64.StdEncoding
dst := make([]byte, enc.EncodedLen(len(der)))
enc.Encode(dst, der)
return dst, nil
}
// Parse is a utility function to decode a base64 encoded
// ASN.1 DER format certificate, and to parse the byte sequence.
// The certificate must be in PKIX format, and it must not contain PEM markers
func Parse(src []byte) (*x509.Certificate, error) {
dst, err := base64.Decode(src)
if err != nil {
return nil, fmt.Errorf(`failed to base64 decode the certificate: %w`, err)
}
cert, err := x509.ParseCertificate(dst)
if err != nil {
return nil, fmt.Errorf(`failed to parse x509 certificate: %w`, err)
}
return cert, nil
}

78
vendor/github.com/lestrrat-go/jwx/v2/cert/chain.go generated vendored Normal file
View File

@ -0,0 +1,78 @@
package cert
import (
"bytes"
"encoding/json"
"fmt"
)
// Chain represents a certificate chain as used in the `x5c` field of
// various objects within JOSE.
//
// It stores the certificates as a list of base64 encoded []byte
// sequence. By definition these values must PKIX encoded.
type Chain struct {
certificates [][]byte
}
func (cc Chain) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
buf.WriteByte('[')
for i, cert := range cc.certificates {
if i > 0 {
buf.WriteByte(',')
}
buf.WriteByte('"')
buf.Write(cert)
buf.WriteByte('"')
}
buf.WriteByte(']')
return buf.Bytes(), nil
}
func (cc *Chain) UnmarshalJSON(data []byte) error {
var tmp []string
if err := json.Unmarshal(data, &tmp); err != nil {
return fmt.Errorf(`failed to unmarshal certificate chain: %w`, err)
}
certs := make([][]byte, len(tmp))
for i, cert := range tmp {
certs[i] = []byte(cert)
}
cc.certificates = certs
return nil
}
// Get returns the n-th ASN.1 DER + base64 encoded certificate
// stored. `false` will be returned in the second argument if
// the corresponding index is out of range.
func (cc *Chain) Get(index int) ([]byte, bool) {
if index < 0 || index > len(cc.certificates) {
return nil, false
}
return cc.certificates[index], true
}
// Len returns the number of certificates stored in this Chain
func (cc *Chain) Len() int {
return len(cc.certificates)
}
var pemStart = []byte("----- BEGIN CERTIFICATE -----")
var pemEnd = []byte("----- END CERTIFICATE -----")
func (cc *Chain) AddString(der string) error {
return cc.Add([]byte(der))
}
func (cc *Chain) Add(der []byte) error {
// We're going to be nice and remove marker lines if they
// give it to us
der = bytes.TrimPrefix(der, pemStart)
der = bytes.TrimSuffix(der, pemEnd)
der = bytes.TrimSpace(der)
cc.certificates = append(cc.certificates, der)
return nil
}

2
vendor/github.com/lestrrat-go/jwx/v2/codecov.yml generated vendored Normal file
View File

@ -0,0 +1,2 @@
codecov:
allow_coverage_offsets: true

View File

@ -4,8 +4,7 @@ import (
"bytes"
"encoding/base64"
"encoding/binary"
"github.com/pkg/errors"
"fmt"
)
func Encode(src []byte) []byte {
@ -56,7 +55,7 @@ func Decode(src []byte) ([]byte, error) {
dst := make([]byte, enc.DecodedLen(len(src)))
n, err := enc.Decode(dst, src)
if err != nil {
return nil, errors.Wrap(err, `failed to decode source`)
return nil, fmt.Errorf(`failed to decode source: %w`, err)
}
return dst[:n], nil
}

View File

@ -7,7 +7,7 @@ import (
"math/big"
"sync"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/v2/jwa"
)
// data for available curves. Some algorithms may be compiled in/out

View File

@ -2,9 +2,9 @@ package iter
import (
"context"
"fmt"
"github.com/lestrrat-go/iter/mapiter"
"github.com/pkg/errors"
)
// MapVisitor is a specialized visitor for our purposes.
@ -30,7 +30,7 @@ func WalkMap(ctx context.Context, src mapiter.Source, visitor MapVisitor) error
func AsMap(ctx context.Context, src mapiter.Source) (map[string]interface{}, error) {
var m map[string]interface{}
if err := mapiter.AsMap(ctx, src, &m); err != nil {
return nil, errors.Wrap(err, `mapiter.AsMap failed`)
return nil, fmt.Errorf(`mapiter.AsMap failed: %w`, err)
}
return m, nil
}

View File

@ -2,11 +2,12 @@ package json
import (
"bytes"
"fmt"
"os"
"sync"
"sync/atomic"
"github.com/lestrrat-go/jwx/internal/base64"
"github.com/pkg/errors"
"github.com/lestrrat-go/jwx/v2/internal/base64"
)
var muGlobalConfig sync.RWMutex
@ -29,12 +30,12 @@ func Unmarshal(b []byte, v interface{}) error {
func AssignNextBytesToken(dst *[]byte, dec *Decoder) error {
var val string
if err := dec.Decode(&val); err != nil {
return errors.Wrap(err, `error reading next value`)
return fmt.Errorf(`error reading next value: %w`, err)
}
buf, err := base64.DecodeString(val)
if err != nil {
return errors.Errorf(`expected base64 encoded []byte (%T)`, val)
return fmt.Errorf(`expected base64 encoded []byte (%T)`, val)
}
*dst = buf
return nil
@ -43,7 +44,7 @@ func AssignNextBytesToken(dst *[]byte, dec *Decoder) error {
func ReadNextStringToken(dec *Decoder) (string, error) {
var val string
if err := dec.Decode(&val); err != nil {
return "", errors.Wrap(err, `error reading next value`)
return "", fmt.Errorf(`error reading next value: %w`, err)
}
return val, nil
}
@ -103,3 +104,10 @@ func NewDecodeCtx(r *Registry) DecodeCtx {
func (dc *decodeCtx) Registry() *Registry {
return dc.registry
}
func Dump(v interface{}) {
enc := NewEncoder(os.Stdout)
enc.SetIndent("", " ")
//nolint:errchkjson
_ = enc.Encode(v)
}

View File

@ -1,10 +1,9 @@
package json
import (
"fmt"
"reflect"
"sync"
"github.com/pkg/errors"
)
type Registry struct {
@ -40,14 +39,14 @@ func (r *Registry) Decode(dec *Decoder, name string) (interface{}, error) {
if typ, ok := r.data[name]; ok {
ptr := reflect.New(typ).Interface()
if err := dec.Decode(ptr); err != nil {
return nil, errors.Wrapf(err, `failed to decode field %s`, name)
return nil, fmt.Errorf(`failed to decode field %s: %w`, name, err)
}
return reflect.ValueOf(ptr).Elem().Interface(), nil
}
var decoded interface{}
if err := dec.Decode(&decoded); err != nil {
return nil, errors.Wrapf(err, `failed to decode field %s`, name)
return nil, fmt.Errorf(`failed to decode field %s: %w`, name, err)
}
return decoded, nil
}

View File

@ -4,10 +4,10 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"fmt"
"github.com/lestrrat-go/blackmagic"
"github.com/lestrrat-go/jwx/jwk"
"github.com/pkg/errors"
"github.com/lestrrat-go/jwx/v2/jwk"
"golang.org/x/crypto/ed25519"
)
@ -18,7 +18,7 @@ func RSAPrivateKey(dst, src interface{}) error {
if jwkKey, ok := src.(jwk.Key); ok {
var raw rsa.PrivateKey
if err := jwkKey.Raw(&raw); err != nil {
return errors.Wrapf(err, `failed to produce rsa.PrivateKey from %T`, src)
return fmt.Errorf(`failed to produce rsa.PrivateKey from %T: %w`, src, err)
}
src = &raw
}
@ -30,7 +30,7 @@ func RSAPrivateKey(dst, src interface{}) error {
case *rsa.PrivateKey:
ptr = src
default:
return errors.Errorf(`expected rsa.PrivateKey or *rsa.PrivateKey, got %T`, src)
return fmt.Errorf(`expected rsa.PrivateKey or *rsa.PrivateKey, got %T`, src)
}
return blackmagic.AssignIfCompatible(dst, ptr)
@ -43,7 +43,7 @@ func RSAPublicKey(dst, src interface{}) error {
if jwkKey, ok := src.(jwk.Key); ok {
var raw rsa.PublicKey
if err := jwkKey.Raw(&raw); err != nil {
return errors.Wrapf(err, `failed to produce rsa.PublicKey from %T`, src)
return fmt.Errorf(`failed to produce rsa.PublicKey from %T: %w`, src, err)
}
src = &raw
}
@ -55,7 +55,7 @@ func RSAPublicKey(dst, src interface{}) error {
case *rsa.PublicKey:
ptr = src
default:
return errors.Errorf(`expected rsa.PublicKey or *rsa.PublicKey, got %T`, src)
return fmt.Errorf(`expected rsa.PublicKey or *rsa.PublicKey, got %T`, src)
}
return blackmagic.AssignIfCompatible(dst, ptr)
@ -67,7 +67,7 @@ func ECDSAPrivateKey(dst, src interface{}) error {
if jwkKey, ok := src.(jwk.Key); ok {
var raw ecdsa.PrivateKey
if err := jwkKey.Raw(&raw); err != nil {
return errors.Wrapf(err, `failed to produce ecdsa.PrivateKey from %T`, src)
return fmt.Errorf(`failed to produce ecdsa.PrivateKey from %T: %w`, src, err)
}
src = &raw
}
@ -79,7 +79,7 @@ func ECDSAPrivateKey(dst, src interface{}) error {
case *ecdsa.PrivateKey:
ptr = src
default:
return errors.Errorf(`expected ecdsa.PrivateKey or *ecdsa.PrivateKey, got %T`, src)
return fmt.Errorf(`expected ecdsa.PrivateKey or *ecdsa.PrivateKey, got %T`, src)
}
return blackmagic.AssignIfCompatible(dst, ptr)
}
@ -90,7 +90,7 @@ func ECDSAPublicKey(dst, src interface{}) error {
if jwkKey, ok := src.(jwk.Key); ok {
var raw ecdsa.PublicKey
if err := jwkKey.Raw(&raw); err != nil {
return errors.Wrapf(err, `failed to produce ecdsa.PublicKey from %T`, src)
return fmt.Errorf(`failed to produce ecdsa.PublicKey from %T: %w`, src, err)
}
src = &raw
}
@ -102,7 +102,7 @@ func ECDSAPublicKey(dst, src interface{}) error {
case *ecdsa.PublicKey:
ptr = src
default:
return errors.Errorf(`expected ecdsa.PublicKey or *ecdsa.PublicKey, got %T`, src)
return fmt.Errorf(`expected ecdsa.PublicKey or *ecdsa.PublicKey, got %T`, src)
}
return blackmagic.AssignIfCompatible(dst, ptr)
}
@ -111,13 +111,13 @@ func ByteSliceKey(dst, src interface{}) error {
if jwkKey, ok := src.(jwk.Key); ok {
var raw []byte
if err := jwkKey.Raw(&raw); err != nil {
return errors.Wrapf(err, `failed to produce []byte from %T`, src)
return fmt.Errorf(`failed to produce []byte from %T: %w`, src, err)
}
src = raw
}
if _, ok := src.([]byte); !ok {
return errors.Errorf(`expected []byte, got %T`, src)
return fmt.Errorf(`expected []byte, got %T`, src)
}
return blackmagic.AssignIfCompatible(dst, src)
}
@ -126,7 +126,7 @@ func Ed25519PrivateKey(dst, src interface{}) error {
if jwkKey, ok := src.(jwk.Key); ok {
var raw ed25519.PrivateKey
if err := jwkKey.Raw(&raw); err != nil {
return errors.Wrapf(err, `failed to produce ed25519.PrivateKey from %T`, src)
return fmt.Errorf(`failed to produce ed25519.PrivateKey from %T: %w`, src, err)
}
src = &raw
}
@ -138,7 +138,7 @@ func Ed25519PrivateKey(dst, src interface{}) error {
case *ed25519.PrivateKey:
ptr = src
default:
return errors.Errorf(`expected ed25519.PrivateKey or *ed25519.PrivateKey, got %T`, src)
return fmt.Errorf(`expected ed25519.PrivateKey or *ed25519.PrivateKey, got %T`, src)
}
return blackmagic.AssignIfCompatible(dst, ptr)
}
@ -147,7 +147,7 @@ func Ed25519PublicKey(dst, src interface{}) error {
if jwkKey, ok := src.(jwk.Key); ok {
var raw ed25519.PublicKey
if err := jwkKey.Raw(&raw); err != nil {
return errors.Wrapf(err, `failed to produce ed25519.PublicKey from %T`, src)
return fmt.Errorf(`failed to produce ed25519.PublicKey from %T: %w`, src, err)
}
src = &raw
}
@ -161,17 +161,17 @@ func Ed25519PublicKey(dst, src interface{}) error {
case *crypto.PublicKey:
tmp, ok := (*src).(ed25519.PublicKey)
if !ok {
return errors.New(`failed to retrieve ed25519.PublicKey out of *crypto.PublicKey`)
return fmt.Errorf(`failed to retrieve ed25519.PublicKey out of *crypto.PublicKey`)
}
ptr = &tmp
case crypto.PublicKey:
tmp, ok := src.(ed25519.PublicKey)
if !ok {
return errors.New(`failed to retrieve ed25519.PublicKey out of crypto.PublicKey`)
return fmt.Errorf(`failed to retrieve ed25519.PublicKey out of crypto.PublicKey`)
}
ptr = &tmp
default:
return errors.Errorf(`expected ed25519.PublicKey or *ed25519.PublicKey, got %T`, src)
return fmt.Errorf(`expected ed25519.PublicKey or *ed25519.PublicKey, got %T`, src)
}
return blackmagic.AssignIfCompatible(dst, ptr)
}

3
vendor/github.com/lestrrat-go/jwx/v2/jwa/README.md generated vendored Normal file
View File

@ -0,0 +1,3 @@
# JWA [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v2/jwa.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwa)
Package [github.com/lestrrat-go/jwx/v2/jwa](./jwa) defines the various algorithm described in [RFC7518](https://tools.ietf.org/html/rfc7518)

View File

@ -6,8 +6,6 @@ import (
"fmt"
"sort"
"sync"
"github.com/pkg/errors"
)
// CompressionAlgorithm represents the compression algorithms as described in https://tools.ietf.org/html/rfc7518#section-7.3
@ -55,12 +53,12 @@ func (v *CompressionAlgorithm) Accept(value interface{}) error {
case string:
s = x
default:
return errors.Errorf(`invalid type for jwa.CompressionAlgorithm: %T`, value)
return fmt.Errorf(`invalid type for jwa.CompressionAlgorithm: %T`, value)
}
tmp = CompressionAlgorithm(s)
}
if _, ok := allCompressionAlgorithms[tmp]; !ok {
return errors.Errorf(`invalid jwa.CompressionAlgorithm value`)
return fmt.Errorf(`invalid jwa.CompressionAlgorithm value`)
}
*v = tmp

View File

@ -6,8 +6,6 @@ import (
"fmt"
"sort"
"sync"
"github.com/pkg/errors"
)
// ContentEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-5
@ -63,12 +61,12 @@ func (v *ContentEncryptionAlgorithm) Accept(value interface{}) error {
case string:
s = x
default:
return errors.Errorf(`invalid type for jwa.ContentEncryptionAlgorithm: %T`, value)
return fmt.Errorf(`invalid type for jwa.ContentEncryptionAlgorithm: %T`, value)
}
tmp = ContentEncryptionAlgorithm(s)
}
if _, ok := allContentEncryptionAlgorithms[tmp]; !ok {
return errors.Errorf(`invalid jwa.ContentEncryptionAlgorithm value`)
return fmt.Errorf(`invalid jwa.ContentEncryptionAlgorithm value`)
}
*v = tmp

View File

@ -6,8 +6,6 @@ import (
"fmt"
"sort"
"sync"
"github.com/pkg/errors"
)
// EllipticCurveAlgorithm represents the algorithms used for EC keys
@ -66,12 +64,12 @@ func (v *EllipticCurveAlgorithm) Accept(value interface{}) error {
case string:
s = x
default:
return errors.Errorf(`invalid type for jwa.EllipticCurveAlgorithm: %T`, value)
return fmt.Errorf(`invalid type for jwa.EllipticCurveAlgorithm: %T`, value)
}
tmp = EllipticCurveAlgorithm(s)
}
if _, ok := allEllipticCurveAlgorithms[tmp]; !ok {
return errors.Errorf(`invalid jwa.EllipticCurveAlgorithm value`)
return fmt.Errorf(`invalid jwa.EllipticCurveAlgorithm value`)
}
*v = tmp

61
vendor/github.com/lestrrat-go/jwx/v2/jwa/jwa.go generated vendored Normal file
View File

@ -0,0 +1,61 @@
//go:generate ../tools/cmd/genjwa.sh
// Package jwa defines the various algorithm described in https://tools.ietf.org/html/rfc7518
package jwa
import "fmt"
// KeyAlgorithm is a workaround for jwk.Key being able to contain different
// types of algorithms in its `alg` field.
//
// Previously the storage for the `alg` field was represented as a string,
// but this caused some users to wonder why the field was not typed appropriately
// like other fields.
//
// Ideally we would like to keep track of Signature Algorithms and
// Content Encryption Algorithms separately, and force the APIs to
// type-check at compile time, but this allows users to pass a value from a
// jwk.Key directly
type KeyAlgorithm interface {
String() string
}
// InvalidKeyAlgorithm represents an algorithm that the library is not aware of.
type InvalidKeyAlgorithm string
func (s InvalidKeyAlgorithm) String() string {
return string(s)
}
func (InvalidKeyAlgorithm) Accept(_ interface{}) error {
return fmt.Errorf(`jwa.InvalidKeyAlgorithm does not support Accept() method calls`)
}
// KeyAlgorithmFrom takes either a string, `jwa.SignatureAlgorithm` or `jwa.KeyEncryptionAlgorithm`
// and returns a `jwa.KeyAlgorithm`.
//
// If the value cannot be handled, it returns an `jwa.InvalidKeyAlgorithm`
// object instead of returning an error. This design choice was made to allow
// users to directly pass the return value to functions such as `jws.Sign()`
func KeyAlgorithmFrom(v interface{}) KeyAlgorithm {
switch v := v.(type) {
case SignatureAlgorithm:
return v
case KeyEncryptionAlgorithm:
return v
case string:
var salg SignatureAlgorithm
if err := salg.Accept(v); err == nil {
return salg
}
var kealg KeyEncryptionAlgorithm
if err := kealg.Accept(v); err == nil {
return kealg
}
return InvalidKeyAlgorithm(v)
default:
return InvalidKeyAlgorithm(fmt.Sprintf("%s", v))
}
}

View File

@ -6,8 +6,6 @@ import (
"fmt"
"sort"
"sync"
"github.com/pkg/errors"
)
// KeyEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-4.1
@ -85,12 +83,12 @@ func (v *KeyEncryptionAlgorithm) Accept(value interface{}) error {
case string:
s = x
default:
return errors.Errorf(`invalid type for jwa.KeyEncryptionAlgorithm: %T`, value)
return fmt.Errorf(`invalid type for jwa.KeyEncryptionAlgorithm: %T`, value)
}
tmp = KeyEncryptionAlgorithm(s)
}
if _, ok := allKeyEncryptionAlgorithms[tmp]; !ok {
return errors.Errorf(`invalid jwa.KeyEncryptionAlgorithm value`)
return fmt.Errorf(`invalid jwa.KeyEncryptionAlgorithm value`)
}
*v = tmp

View File

@ -6,8 +6,6 @@ import (
"fmt"
"sort"
"sync"
"github.com/pkg/errors"
)
// KeyType represents the key type ("kty") that are supported
@ -60,12 +58,12 @@ func (v *KeyType) Accept(value interface{}) error {
case string:
s = x
default:
return errors.Errorf(`invalid type for jwa.KeyType: %T`, value)
return fmt.Errorf(`invalid type for jwa.KeyType: %T`, value)
}
tmp = KeyType(s)
}
if _, ok := allKeyTypes[tmp]; !ok {
return errors.Errorf(`invalid jwa.KeyType value`)
return fmt.Errorf(`invalid jwa.KeyType value`)
}
*v = tmp

View File

@ -6,8 +6,6 @@ import (
"fmt"
"sort"
"sync"
"github.com/pkg/errors"
)
// SignatureAlgorithm represents the various signature algorithms as described in https://tools.ietf.org/html/rfc7518#section-3.1
@ -81,12 +79,12 @@ func (v *SignatureAlgorithm) Accept(value interface{}) error {
case string:
s = x
default:
return errors.Errorf(`invalid type for jwa.SignatureAlgorithm: %T`, value)
return fmt.Errorf(`invalid type for jwa.SignatureAlgorithm: %T`, value)
}
tmp = SignatureAlgorithm(s)
}
if _, ok := allSignatureAlgorithms[tmp]; !ok {
return errors.Errorf(`invalid jwa.SignatureAlgorithm value`)
return fmt.Errorf(`invalid jwa.SignatureAlgorithm value`)
}
*v = tmp

View File

@ -1,4 +1,4 @@
# JWE [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/jwe.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwe)
# JWE [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v2/jwe.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jwe)
Package jwe implements JWE as described in [RFC7516](https://tools.ietf.org/html/rfc7516)
@ -59,7 +59,7 @@ func ExampleEncrypt() {
payload := []byte("Lorem Ipsum")
encrypted, err := jwe.Encrypt(payload, jwa.RSA1_5, &privkey.PublicKey, jwa.A128CBC_HS256, jwa.NoCompress)
encrypted, err := jwe.Encrypt(payload, jwe.WithKey(jwa.RSA1_5, &privkey.PublicKey), jwe.WithContentEncryption(jwa.A128CBC_HS256))
if err != nil {
log.Printf("failed to encrypt payload: %s", err)
return
@ -79,7 +79,7 @@ func ExampleDecrypt() {
return
}
decrypted, err := jwe.Decrypt(encrypted, jwa.RSA1_5, privkey)
decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA1_5, privkey))
if err != nil {
log.Printf("failed to decrypt: %s", err)
return

36
vendor/github.com/lestrrat-go/jwx/v2/jwe/compress.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
package jwe
import (
"bytes"
"compress/flate"
"fmt"
"io"
"github.com/lestrrat-go/jwx/v2/internal/pool"
)
func uncompress(plaintext []byte) ([]byte, error) {
return io.ReadAll(flate.NewReader(bytes.NewReader(plaintext)))
}
func compress(plaintext []byte) ([]byte, error) {
buf := pool.GetBytesBuffer()
defer pool.ReleaseBytesBuffer(buf)
w, _ := flate.NewWriter(buf, 1)
in := plaintext
for len(in) > 0 {
n, err := w.Write(in)
if err != nil {
return nil, fmt.Errorf(`failed to write to compression writer: %w`, err)
}
in = in[n:]
}
if err := w.Close(); err != nil {
return nil, fmt.Errorf(`failed to close compression writer: %w`, err)
}
ret := make([]byte, buf.Len())
copy(ret, buf.Bytes())
return ret, nil
}

View File

@ -7,23 +7,23 @@ import (
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"golang.org/x/crypto/pbkdf2"
"github.com/lestrrat-go/jwx/internal/keyconv"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwe/internal/cipher"
"github.com/lestrrat-go/jwx/jwe/internal/content_crypt"
"github.com/lestrrat-go/jwx/jwe/internal/keyenc"
"github.com/lestrrat-go/jwx/x25519"
"github.com/pkg/errors"
"github.com/lestrrat-go/jwx/v2/internal/keyconv"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe/internal/cipher"
"github.com/lestrrat-go/jwx/v2/jwe/internal/content_crypt"
"github.com/lestrrat-go/jwx/v2/jwe/internal/keyenc"
"github.com/lestrrat-go/jwx/v2/x25519"
)
// Decrypter is responsible for taking various components to decrypt a message.
// decrypter is responsible for taking various components to decrypt a message.
// its operation is not concurrency safe. You must provide locking yourself
//nolint:govet
type Decrypter struct {
type decrypter struct {
aad []byte
apu []byte
apv []byte
@ -41,7 +41,7 @@ type Decrypter struct {
keycount int
}
// NewDecrypter Creates a new Decrypter instance. You must supply the
// newDecrypter Creates a new Decrypter instance. You must supply the
// rest of parameters via their respective setter methods before
// calling Decrypt().
//
@ -49,103 +49,103 @@ type Decrypter struct {
// *rsa.PrivateKey, instead of jwk.Key)
//
// You should consider this object immutable once you assign values to it.
func NewDecrypter(keyalg jwa.KeyEncryptionAlgorithm, ctalg jwa.ContentEncryptionAlgorithm, privkey interface{}) *Decrypter {
return &Decrypter{
func newDecrypter(keyalg jwa.KeyEncryptionAlgorithm, ctalg jwa.ContentEncryptionAlgorithm, privkey interface{}) *decrypter {
return &decrypter{
ctalg: ctalg,
keyalg: keyalg,
privkey: privkey,
}
}
func (d *Decrypter) AgreementPartyUInfo(apu []byte) *Decrypter {
func (d *decrypter) AgreementPartyUInfo(apu []byte) *decrypter {
d.apu = apu
return d
}
func (d *Decrypter) AgreementPartyVInfo(apv []byte) *Decrypter {
func (d *decrypter) AgreementPartyVInfo(apv []byte) *decrypter {
d.apv = apv
return d
}
func (d *Decrypter) AuthenticatedData(aad []byte) *Decrypter {
func (d *decrypter) AuthenticatedData(aad []byte) *decrypter {
d.aad = aad
return d
}
func (d *Decrypter) ComputedAuthenticatedData(aad []byte) *Decrypter {
func (d *decrypter) ComputedAuthenticatedData(aad []byte) *decrypter {
d.computedAad = aad
return d
}
func (d *Decrypter) ContentEncryptionAlgorithm(ctalg jwa.ContentEncryptionAlgorithm) *Decrypter {
func (d *decrypter) ContentEncryptionAlgorithm(ctalg jwa.ContentEncryptionAlgorithm) *decrypter {
d.ctalg = ctalg
return d
}
func (d *Decrypter) InitializationVector(iv []byte) *Decrypter {
func (d *decrypter) InitializationVector(iv []byte) *decrypter {
d.iv = iv
return d
}
func (d *Decrypter) KeyCount(keycount int) *Decrypter {
func (d *decrypter) KeyCount(keycount int) *decrypter {
d.keycount = keycount
return d
}
func (d *Decrypter) KeyInitializationVector(keyiv []byte) *Decrypter {
func (d *decrypter) KeyInitializationVector(keyiv []byte) *decrypter {
d.keyiv = keyiv
return d
}
func (d *Decrypter) KeySalt(keysalt []byte) *Decrypter {
func (d *decrypter) KeySalt(keysalt []byte) *decrypter {
d.keysalt = keysalt
return d
}
func (d *Decrypter) KeyTag(keytag []byte) *Decrypter {
func (d *decrypter) KeyTag(keytag []byte) *decrypter {
d.keytag = keytag
return d
}
// PublicKey sets the public key to be used in decoding EC based encryptions.
// The key must be in its "raw" format (i.e. *ecdsa.PublicKey, instead of jwk.Key)
func (d *Decrypter) PublicKey(pubkey interface{}) *Decrypter {
func (d *decrypter) PublicKey(pubkey interface{}) *decrypter {
d.pubkey = pubkey
return d
}
func (d *Decrypter) Tag(tag []byte) *Decrypter {
func (d *decrypter) Tag(tag []byte) *decrypter {
d.tag = tag
return d
}
func (d *Decrypter) ContentCipher() (content_crypt.Cipher, error) {
func (d *decrypter) ContentCipher() (content_crypt.Cipher, error) {
if d.cipher == nil {
switch d.ctalg {
case jwa.A128GCM, jwa.A192GCM, jwa.A256GCM, jwa.A128CBC_HS256, jwa.A192CBC_HS384, jwa.A256CBC_HS512:
cipher, err := cipher.NewAES(d.ctalg)
if err != nil {
return nil, errors.Wrapf(err, `failed to build content cipher for %s`, d.ctalg)
return nil, fmt.Errorf(`failed to build content cipher for %s: %w`, d.ctalg, err)
}
d.cipher = cipher
default:
return nil, errors.Errorf(`invalid content cipher algorithm (%s)`, d.ctalg)
return nil, fmt.Errorf(`invalid content cipher algorithm (%s)`, d.ctalg)
}
}
return d.cipher, nil
}
func (d *Decrypter) Decrypt(recipientKey, ciphertext []byte) (plaintext []byte, err error) {
func (d *decrypter) Decrypt(recipientKey, ciphertext []byte) (plaintext []byte, err error) {
cek, keyerr := d.DecryptKey(recipientKey)
if keyerr != nil {
err = errors.Wrap(keyerr, `failed to decrypt key`)
err = fmt.Errorf(`failed to decrypt key: %w`, keyerr)
return
}
cipher, ciphererr := d.ContentCipher()
if ciphererr != nil {
err = errors.Wrap(ciphererr, `failed to fetch content crypt cipher`)
err = fmt.Errorf(`failed to fetch content crypt cipher: %w`, ciphererr)
return
}
@ -156,14 +156,14 @@ func (d *Decrypter) Decrypt(recipientKey, ciphertext []byte) (plaintext []byte,
plaintext, err = cipher.Decrypt(cek, d.iv, ciphertext, d.tag, computedAad)
if err != nil {
err = errors.Wrap(err, `failed to decrypt payload`)
err = fmt.Errorf(`failed to decrypt payload: %w`, err)
return
}
return plaintext, nil
}
func (d *Decrypter) decryptSymmetricKey(recipientKey, cek []byte) ([]byte, error) {
func (d *decrypter) decryptSymmetricKey(recipientKey, cek []byte) ([]byte, error) {
switch d.keyalg {
case jwa.DIRECT:
return cek, nil
@ -189,48 +189,48 @@ func (d *Decrypter) decryptSymmetricKey(recipientKey, cek []byte) ([]byte, error
case jwa.A128KW, jwa.A192KW, jwa.A256KW:
block, err := aes.NewCipher(cek)
if err != nil {
return nil, errors.Wrap(err, `failed to create new AES cipher`)
return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err)
}
jek, err := keyenc.Unwrap(block, recipientKey)
if err != nil {
return nil, errors.Wrap(err, `failed to unwrap key`)
return nil, fmt.Errorf(`failed to unwrap key: %w`, err)
}
return jek, nil
case jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW:
if len(d.keyiv) != 12 {
return nil, errors.Errorf("GCM requires 96-bit iv, got %d", len(d.keyiv)*8)
return nil, fmt.Errorf("GCM requires 96-bit iv, got %d", len(d.keyiv)*8)
}
if len(d.keytag) != 16 {
return nil, errors.Errorf("GCM requires 128-bit tag, got %d", len(d.keytag)*8)
return nil, fmt.Errorf("GCM requires 128-bit tag, got %d", len(d.keytag)*8)
}
block, err := aes.NewCipher(cek)
if err != nil {
return nil, errors.Wrap(err, `failed to create new AES cipher`)
return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err)
}
aesgcm, err := cryptocipher.NewGCM(block)
if err != nil {
return nil, errors.Wrap(err, `failed to create new GCM wrap`)
return nil, fmt.Errorf(`failed to create new GCM wrap: %w`, err)
}
ciphertext := recipientKey[:]
ciphertext = append(ciphertext, d.keytag...)
jek, err := aesgcm.Open(nil, d.keyiv, ciphertext, nil)
if err != nil {
return nil, errors.Wrap(err, `failed to decode key`)
return nil, fmt.Errorf(`failed to decode key: %w`, err)
}
return jek, nil
default:
return nil, errors.Errorf("decrypt key: unsupported algorithm %s", d.keyalg)
return nil, fmt.Errorf("decrypt key: unsupported algorithm %s", d.keyalg)
}
}
func (d *Decrypter) DecryptKey(recipientKey []byte) (cek []byte, err error) {
func (d *decrypter) DecryptKey(recipientKey []byte) (cek []byte, err error) {
if d.keyalg.IsSymmetric() {
var ok bool
cek, ok = d.privkey.([]byte)
if !ok {
return nil, errors.Errorf("decrypt key: []byte is required as the key to build %s key decrypter (got %T)", d.keyalg, d.privkey)
return nil, fmt.Errorf("decrypt key: []byte is required as the key to build %s key decrypter (got %T)", d.keyalg, d.privkey)
}
return d.decryptSymmetricKey(recipientKey, cek)
@ -238,42 +238,42 @@ func (d *Decrypter) DecryptKey(recipientKey []byte) (cek []byte, err error) {
k, err := d.BuildKeyDecrypter()
if err != nil {
return nil, errors.Wrap(err, `failed to build key decrypter`)
return nil, fmt.Errorf(`failed to build key decrypter: %w`, err)
}
cek, err = k.Decrypt(recipientKey)
if err != nil {
return nil, errors.Wrap(err, `failed to decrypt key`)
return nil, fmt.Errorf(`failed to decrypt key: %w`, err)
}
return cek, nil
}
func (d *Decrypter) BuildKeyDecrypter() (keyenc.Decrypter, error) {
func (d *decrypter) BuildKeyDecrypter() (keyenc.Decrypter, error) {
cipher, err := d.ContentCipher()
if err != nil {
return nil, errors.Wrap(err, `failed to fetch content crypt cipher`)
return nil, fmt.Errorf(`failed to fetch content crypt cipher: %w`, err)
}
switch alg := d.keyalg; alg {
case jwa.RSA1_5:
var privkey rsa.PrivateKey
if err := keyconv.RSAPrivateKey(&privkey, d.privkey); err != nil {
return nil, errors.Wrapf(err, "*rsa.PrivateKey is required as the key to build %s key decrypter", alg)
return nil, fmt.Errorf(`*rsa.PrivateKey is required as the key to build %s key decrypter: %w`, alg, err)
}
return keyenc.NewRSAPKCS15Decrypt(alg, &privkey, cipher.KeySize()/2), nil
case jwa.RSA_OAEP, jwa.RSA_OAEP_256:
var privkey rsa.PrivateKey
if err := keyconv.RSAPrivateKey(&privkey, d.privkey); err != nil {
return nil, errors.Wrapf(err, "*rsa.PrivateKey is required as the key to build %s key decrypter", alg)
return nil, fmt.Errorf(`*rsa.PrivateKey is required as the key to build %s key decrypter: %w`, alg, err)
}
return keyenc.NewRSAOAEPDecrypt(alg, &privkey)
case jwa.A128KW, jwa.A192KW, jwa.A256KW:
sharedkey, ok := d.privkey.([]byte)
if !ok {
return nil, errors.Errorf("[]byte is required as the key to build %s key decrypter", alg)
return nil, fmt.Errorf("[]byte is required as the key to build %s key decrypter", alg)
}
return keyenc.NewAES(alg, sharedkey)
@ -284,17 +284,17 @@ func (d *Decrypter) BuildKeyDecrypter() (keyenc.Decrypter, error) {
default:
var pubkey ecdsa.PublicKey
if err := keyconv.ECDSAPublicKey(&pubkey, d.pubkey); err != nil {
return nil, errors.Wrapf(err, "*ecdsa.PublicKey is required as the key to build %s key decrypter", alg)
return nil, fmt.Errorf(`*ecdsa.PublicKey is required as the key to build %s key decrypter: %w`, alg, err)
}
var privkey ecdsa.PrivateKey
if err := keyconv.ECDSAPrivateKey(&privkey, d.privkey); err != nil {
return nil, errors.Wrapf(err, "*ecdsa.PrivateKey is required as the key to build %s key decrypter", alg)
return nil, fmt.Errorf(`*ecdsa.PrivateKey is required as the key to build %s key decrypter: %w`, alg, err)
}
return keyenc.NewECDHESDecrypt(alg, d.ctalg, &pubkey, d.apu, d.apv, &privkey), nil
}
default:
return nil, errors.Errorf(`unsupported algorithm for key decryption (%s)`, alg)
return nil, fmt.Errorf(`unsupported algorithm for key decryption (%s)`, alg)
}
}

View File

@ -2,13 +2,13 @@ package jwe
import (
"context"
"fmt"
"github.com/lestrrat-go/jwx/internal/base64"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/lestrrat-go/jwx/v2/internal/base64"
"github.com/lestrrat-go/jwx/v2/internal/json"
"github.com/lestrrat-go/iter/mapiter"
"github.com/lestrrat-go/jwx/internal/iter"
"github.com/pkg/errors"
"github.com/lestrrat-go/jwx/v2/internal/iter"
)
type isZeroer interface {
@ -64,7 +64,7 @@ func (h *stdHeaders) AsMap(ctx context.Context) (map[string]interface{}, error)
func (h *stdHeaders) Clone(ctx context.Context) (Headers, error) {
dst := NewHeaders()
if err := h.Copy(ctx, dst); err != nil {
return nil, errors.Wrap(err, `failed to copy header contents to new object`)
return nil, fmt.Errorf(`failed to copy header contents to new object: %w`, err)
}
return dst, nil
}
@ -74,7 +74,7 @@ func (h *stdHeaders) Copy(ctx context.Context, dst Headers) error {
//nolint:forcetypeassert
key := pair.Key.(string)
if err := dst.Set(key, pair.Value); err != nil {
return errors.Wrapf(err, `failed to set header %q`, key)
return fmt.Errorf(`failed to set header %q: %w`, key, err)
}
}
return nil
@ -85,13 +85,13 @@ func (h *stdHeaders) Merge(ctx context.Context, h2 Headers) (Headers, error) {
if h != nil {
if err := h.Copy(ctx, h3); err != nil {
return nil, errors.Wrap(err, `failed to copy headers from receiver`)
return nil, fmt.Errorf(`failed to copy headers from receiver: %w`, err)
}
}
if h2 != nil {
if err := h2.Copy(ctx, h3); err != nil {
return nil, errors.Wrap(err, `failed to copy headers from argument`)
return nil, fmt.Errorf(`failed to copy headers from argument: %w`, err)
}
}
@ -101,7 +101,7 @@ func (h *stdHeaders) Merge(ctx context.Context, h2 Headers) (Headers, error) {
func (h *stdHeaders) Encode() ([]byte, error) {
buf, err := json.Marshal(h)
if err != nil {
return nil, errors.Wrap(err, `failed to marshal headers to JSON prior to encoding`)
return nil, fmt.Errorf(`failed to marshal headers to JSON prior to encoding: %w`, err)
}
return base64.Encode(buf), nil
@ -111,11 +111,11 @@ func (h *stdHeaders) Decode(buf []byte) error {
// base64 json string -> json object representation of header
decoded, err := base64.Decode(buf)
if err != nil {
return errors.Wrap(err, "failed to unmarshal base64 encoded buffer")
return fmt.Errorf(`failed to unmarshal base64 encoded buffer: %w`, err)
}
if err := json.Unmarshal(decoded, h); err != nil {
return errors.Wrap(err, "failed to unmarshal buffer")
return fmt.Errorf(`failed to unmarshal buffer: %w`, err)
}
return nil

View File

@ -5,15 +5,16 @@ package jwe
import (
"bytes"
"context"
"fmt"
"sort"
"sync"
"github.com/lestrrat-go/jwx/internal/base64"
"github.com/lestrrat-go/jwx/internal/json"
"github.com/lestrrat-go/jwx/internal/pool"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/pkg/errors"
"github.com/lestrrat-go/jwx/v2/cert"
"github.com/lestrrat-go/jwx/v2/internal/base64"
"github.com/lestrrat-go/jwx/v2/internal/json"
"github.com/lestrrat-go/jwx/v2/internal/pool"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
)
const (
@ -51,7 +52,7 @@ type Headers interface {
JWKSetURL() string
KeyID() string
Type() string
X509CertChain() []string
X509CertChain() *cert.Chain
X509CertThumbprint() string
X509CertThumbprintS256() string
X509URL() string
@ -86,7 +87,7 @@ type stdHeaders struct {
jwkSetURL *string
keyID *string
typ *string
x509CertChain []string
x509CertChain *cert.Chain
x509CertThumbprint *string
x509CertThumbprintS256 *string
x509URL *string
@ -194,7 +195,7 @@ func (h *stdHeaders) Type() string {
return *(h.typ)
}
func (h *stdHeaders) X509CertChain() []string {
func (h *stdHeaders) X509CertChain() *cert.Chain {
h.mu.RLock()
defer h.mu.RUnlock()
return h.x509CertChain
@ -394,100 +395,100 @@ func (h *stdHeaders) setNoLock(name string, value interface{}) error {
h.agreementPartyUInfo = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, AgreementPartyUInfoKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, AgreementPartyUInfoKey, value)
case AgreementPartyVInfoKey:
if v, ok := value.([]byte); ok {
h.agreementPartyVInfo = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, AgreementPartyVInfoKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, AgreementPartyVInfoKey, value)
case AlgorithmKey:
if v, ok := value.(jwa.KeyEncryptionAlgorithm); ok {
h.algorithm = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, AlgorithmKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, AlgorithmKey, value)
case CompressionKey:
if v, ok := value.(jwa.CompressionAlgorithm); ok {
h.compression = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, CompressionKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, CompressionKey, value)
case ContentEncryptionKey:
if v, ok := value.(jwa.ContentEncryptionAlgorithm); ok {
if v == "" {
return errors.New(`"enc" field cannot be an empty string`)
return fmt.Errorf(`"enc" field cannot be an empty string`)
}
h.contentEncryption = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, ContentEncryptionKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, ContentEncryptionKey, value)
case ContentTypeKey:
if v, ok := value.(string); ok {
h.contentType = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, ContentTypeKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, ContentTypeKey, value)
case CriticalKey:
if v, ok := value.([]string); ok {
h.critical = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, CriticalKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, CriticalKey, value)
case EphemeralPublicKeyKey:
if v, ok := value.(jwk.Key); ok {
h.ephemeralPublicKey = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, EphemeralPublicKeyKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, EphemeralPublicKeyKey, value)
case JWKKey:
if v, ok := value.(jwk.Key); ok {
h.jwk = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, JWKKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, JWKKey, value)
case JWKSetURLKey:
if v, ok := value.(string); ok {
h.jwkSetURL = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, JWKSetURLKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, JWKSetURLKey, value)
case KeyIDKey:
if v, ok := value.(string); ok {
h.keyID = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, KeyIDKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value)
case TypeKey:
if v, ok := value.(string); ok {
h.typ = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, TypeKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, TypeKey, value)
case X509CertChainKey:
if v, ok := value.([]string); ok {
if v, ok := value.(*cert.Chain); ok {
h.x509CertChain = v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
case X509CertThumbprintKey:
if v, ok := value.(string); ok {
h.x509CertThumbprint = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value)
case X509CertThumbprintS256Key:
if v, ok := value.(string); ok {
h.x509CertThumbprintS256 = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value)
return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value)
case X509URLKey:
if v, ok := value.(string); ok {
h.x509URL = &v
return nil
}
return errors.Errorf(`invalid value for %s key: %T`, X509URLKey, value)
return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value)
default:
if h.privateParams == nil {
h.privateParams = map[string]interface{}{}
@ -561,7 +562,7 @@ LOOP:
for {
tok, err := dec.Token()
if err != nil {
return errors.Wrap(err, `error reading token`)
return fmt.Errorf(`error reading token: %w`, err)
}
switch tok := tok.(type) {
case json.Delim:
@ -570,95 +571,95 @@ LOOP:
if tok == '}' { // End of object
break LOOP
} else if tok != '{' {
return errors.Errorf(`expected '{', but got '%c'`, tok)
return fmt.Errorf(`expected '{', but got '%c'`, tok)
}
case string: // Objects can only have string keys
switch tok {
case AgreementPartyUInfoKey:
if err := json.AssignNextBytesToken(&h.agreementPartyUInfo, dec); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, AgreementPartyUInfoKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, AgreementPartyUInfoKey, err)
}
case AgreementPartyVInfoKey:
if err := json.AssignNextBytesToken(&h.agreementPartyVInfo, dec); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, AgreementPartyVInfoKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, AgreementPartyVInfoKey, err)
}
case AlgorithmKey:
var decoded jwa.KeyEncryptionAlgorithm
if err := dec.Decode(&decoded); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, AlgorithmKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err)
}
h.algorithm = &decoded
case CompressionKey:
var decoded jwa.CompressionAlgorithm
if err := dec.Decode(&decoded); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, CompressionKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, CompressionKey, err)
}
h.compression = &decoded
case ContentEncryptionKey:
var decoded jwa.ContentEncryptionAlgorithm
if err := dec.Decode(&decoded); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, ContentEncryptionKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, ContentEncryptionKey, err)
}
h.contentEncryption = &decoded
case ContentTypeKey:
if err := json.AssignNextStringToken(&h.contentType, dec); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, ContentTypeKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, ContentTypeKey, err)
}
case CriticalKey:
var decoded []string
if err := dec.Decode(&decoded); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, CriticalKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, CriticalKey, err)
}
h.critical = decoded
case EphemeralPublicKeyKey:
var buf json.RawMessage
if err := dec.Decode(&buf); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, EphemeralPublicKeyKey)
return fmt.Errorf(`failed to decode value for key %s:%w`, EphemeralPublicKeyKey, err)
}
key, err := jwk.ParseKey(buf)
if err != nil {
return errors.Wrapf(err, `failed to parse JWK for key %s`, EphemeralPublicKeyKey)
return fmt.Errorf(`failed to parse JWK for key %s: %w`, EphemeralPublicKeyKey, err)
}
h.ephemeralPublicKey = key
case JWKKey:
var buf json.RawMessage
if err := dec.Decode(&buf); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, JWKKey)
return fmt.Errorf(`failed to decode value for key %s:%w`, JWKKey, err)
}
key, err := jwk.ParseKey(buf)
if err != nil {
return errors.Wrapf(err, `failed to parse JWK for key %s`, JWKKey)
return fmt.Errorf(`failed to parse JWK for key %s: %w`, JWKKey, err)
}
h.jwk = key
case JWKSetURLKey:
if err := json.AssignNextStringToken(&h.jwkSetURL, dec); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, JWKSetURLKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, JWKSetURLKey, err)
}
case KeyIDKey:
if err := json.AssignNextStringToken(&h.keyID, dec); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, KeyIDKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err)
}
case TypeKey:
if err := json.AssignNextStringToken(&h.typ, dec); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, TypeKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, TypeKey, err)
}
case X509CertChainKey:
var decoded []string
var decoded cert.Chain
if err := dec.Decode(&decoded); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, X509CertChainKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
}
h.x509CertChain = decoded
h.x509CertChain = &decoded
case X509CertThumbprintKey:
if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, X509CertThumbprintKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err)
}
case X509CertThumbprintS256Key:
if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, X509CertThumbprintS256Key)
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err)
}
case X509URLKey:
if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil {
return errors.Wrapf(err, `failed to decode value for key %s`, X509URLKey)
return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err)
}
default:
decoded, err := registry.Decode(dec, tok)
@ -668,7 +669,7 @@ LOOP:
h.setNoLock(tok, decoded)
}
default:
return errors.Errorf(`invalid token %T`, tok)
return fmt.Errorf(`invalid token %T`, tok)
}
}
return nil
@ -702,7 +703,7 @@ func (h stdHeaders) MarshalJSON() ([]byte, error) {
buf.WriteRune('"')
default:
if err := enc.Encode(v); err != nil {
errors.Errorf(`failed to encode value for field %s`, f)
return nil, fmt.Errorf(`failed to encode value for field %s`, f)
}
buf.Truncate(buf.Len() - 1)
}

159
vendor/github.com/lestrrat-go/jwx/v2/jwe/interface.go generated vendored Normal file
View File

@ -0,0 +1,159 @@
package jwe
import (
"github.com/lestrrat-go/iter/mapiter"
"github.com/lestrrat-go/jwx/v2/internal/iter"
"github.com/lestrrat-go/jwx/v2/jwe/internal/keygen"
)
// Recipient holds the encrypted key and hints to decrypt the key
type Recipient interface {
Headers() Headers
EncryptedKey() []byte
SetHeaders(Headers) error
SetEncryptedKey([]byte) error
}
type stdRecipient struct {
// Comments on each field are taken from https://datatracker.ietf.org/doc/html/rfc7516
//
// header
// The "header" member MUST be present and contain the value JWE Per-
// Recipient Unprotected Header when the JWE Per-Recipient
// Unprotected Header value is non-empty; otherwise, it MUST be
// absent. This value is represented as an unencoded JSON object,
// rather than as a string. These Header Parameter values are not
// integrity protected.
//
// At least one of the "header", "protected", and "unprotected" members
// MUST be present so that "alg" and "enc" Header Parameter values are
// conveyed for each recipient computation.
//
// JWX note: see Message.unprotectedHeaders
headers Headers
// encrypted_key
// The "encrypted_key" member MUST be present and contain the value
// BASE64URL(JWE Encrypted Key) when the JWE Encrypted Key value is
// non-empty; otherwise, it MUST be absent.
encryptedKey []byte
}
// Message contains the entire encrypted JWE message. You should not
// expect to use Message for anything other than inspecting the
// state of an encrypted message. This is because encryption is
// highly context sensitive, and once we parse the original payload
// into an object, we may not always be able to recreate the exact
// context in which the encryption happened.
//
// For example, it is totally valid for if the protected header's
// integrity was calculated using a non-standard line breaks:
//
// {"a dummy":
// "protected header"}
//
// Once parsed, though, we can only serialize the protected header as:
//
// {"a dummy":"protected header"}
//
// which would obviously result in a contradicting integrity value
// if we tried to re-calculate it from a parsed message.
//nolint:govet
type Message struct {
// Comments on each field are taken from https://datatracker.ietf.org/doc/html/rfc7516
//
// protected
// The "protected" member MUST be present and contain the value
// BASE64URL(UTF8(JWE Protected Header)) when the JWE Protected
// Header value is non-empty; otherwise, it MUST be absent. These
// Header Parameter values are integrity protected.
protectedHeaders Headers
// unprotected
// The "unprotected" member MUST be present and contain the value JWE
// Shared Unprotected Header when the JWE Shared Unprotected Header
// value is non-empty; otherwise, it MUST be absent. This value is
// represented as an unencoded JSON object, rather than as a string.
// These Header Parameter values are not integrity protected.
//
// JWX note: This field is NOT mutually exclusive with per-recipient
// headers within the implmentation because... it's too much work.
// It is _never_ populated (we don't provide a way to do this) upon encryption.
// When decrypting, if present its values are always merged with
// per-recipient header.
unprotectedHeaders Headers
// iv
// The "iv" member MUST be present and contain the value
// BASE64URL(JWE Initialization Vector) when the JWE Initialization
// Vector value is non-empty; otherwise, it MUST be absent.
initializationVector []byte
// aad
// The "aad" member MUST be present and contain the value
// BASE64URL(JWE AAD)) when the JWE AAD value is non-empty;
// otherwise, it MUST be absent. A JWE AAD value can be included to
// supply a base64url-encoded value to be integrity protected but not
// encrypted.
authenticatedData []byte
// ciphertext
// The "ciphertext" member MUST be present and contain the value
// BASE64URL(JWE Ciphertext).
cipherText []byte
// tag
// The "tag" member MUST be present and contain the value
// BASE64URL(JWE Authentication Tag) when the JWE Authentication Tag
// value is non-empty; otherwise, it MUST be absent.
tag []byte
// recipients
// The "recipients" member value MUST be an array of JSON objects.
// Each object contains information specific to a single recipient.
// This member MUST be present with exactly one array element per
// recipient, even if some or all of the array element values are the
// empty JSON object "{}" (which can happen when all Header Parameter
// values are shared between all recipients and when no encrypted key
// is used, such as when doing Direct Encryption).
//
// Some Header Parameters, including the "alg" parameter, can be shared
// among all recipient computations. Header Parameters in the JWE
// Protected Header and JWE Shared Unprotected Header values are shared
// among all recipients.
//
// The Header Parameter values used when creating or validating per-
// recipient ciphertext and Authentication Tag values are the union of
// the three sets of Header Parameter values that may be present: (1)
// the JWE Protected Header represented in the "protected" member, (2)
// the JWE Shared Unprotected Header represented in the "unprotected"
// member, and (3) the JWE Per-Recipient Unprotected Header represented
// in the "header" member of the recipient's array element. The union
// of these sets of Header Parameters comprises the JOSE Header. The
// Header Parameter names in the three locations MUST be disjoint.
recipients []Recipient
// TODO: Additional members can be present in both the JSON objects defined
// above; if not understood by implementations encountering them, they
// MUST be ignored.
// privateParams map[string]interface{}
// These two fields below are not available for the public consumers of this object.
// rawProtectedHeaders stores the original protected header buffer
rawProtectedHeaders []byte
// storeProtectedHeaders is a hint to be used in UnmarshalJSON().
// When this flag is true, UnmarshalJSON() will populate the
// rawProtectedHeaders field
storeProtectedHeaders bool
}
// populater is an interface for things that may modify the
// JWE header. e.g. ByteWithECPrivateKey
type populater interface {
Populate(keygen.Setter) error
}
type Visitor = iter.MapVisitor
type VisitorFunc = iter.MapVisitorFunc
type HeaderPair = mapiter.Pair
type Iterator = mapiter.Iterator

View File

@ -9,8 +9,6 @@ import (
"encoding/binary"
"fmt"
"hash"
"github.com/pkg/errors"
)
const (
@ -38,7 +36,7 @@ func unpad(buf []byte, n int) ([]byte, error) {
// First, `buf` must be a multiple of `n`
if rem != 0 {
return nil, errors.Errorf("input buffer must be multiple of block size %d", n)
return nil, fmt.Errorf("input buffer must be multiple of block size %d", n)
}
// Find the last byte, which is the encoded padding
@ -60,7 +58,7 @@ func unpad(buf []byte, n int) ([]byte, error) {
// we also don't check against lbuf-i in range, because we have established expected <= lbuf
for i := 1; i < expected; i++ {
if buf[lbuf-i] != last {
return nil, errors.New(`invalid padding`)
return nil, fmt.Errorf(`invalid padding`)
}
}
@ -84,7 +82,7 @@ func New(key []byte, f BlockCipherFunc) (hmac *Hmac, err error) {
bc, ciphererr := f(ekey)
if ciphererr != nil {
err = errors.Wrap(ciphererr, `failed to execute block cipher function`)
err = fmt.Errorf(`failed to execute block cipher function: %w`, ciphererr)
return
}
@ -97,7 +95,7 @@ func New(key []byte, f BlockCipherFunc) (hmac *Hmac, err error) {
case 32:
hfunc = sha512.New
default:
return nil, errors.Errorf("unsupported key size %d", keysize)
return nil, fmt.Errorf("unsupported key size %d", keysize)
}
return &Hmac{
@ -133,7 +131,7 @@ func (c Hmac) ComputeAuthTag(aad, nonce, ciphertext []byte) ([]byte, error) {
h := hmac.New(c.hash, c.integrityKey)
if _, err := h.Write(buf); err != nil {
return nil, errors.Wrap(err, "failed to write ComputeAuthTag using Hmac")
return nil, fmt.Errorf(`failed to write ComputeAuthTag using Hmac: %w`, err)
}
s := h.Sum(nil)
return s[:c.tagsize], nil
@ -182,7 +180,7 @@ func (c Hmac) Seal(dst, nonce, plaintext, data []byte) []byte {
// Open fulfills the crypto.AEAD interface
func (c Hmac) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
if len(ciphertext) < c.keysize {
return nil, errors.New("invalid ciphertext (too short)")
return nil, fmt.Errorf(`invalid ciphertext (too short)`)
}
tagOffset := len(ciphertext) - c.tagsize
@ -198,11 +196,11 @@ func (c Hmac) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
expectedTag, err := c.ComputeAuthTag(data, nonce, ciphertext[:tagOffset])
if err != nil {
return nil, errors.Wrap(err, `failed to compute auth tag`)
return nil, fmt.Errorf(`failed to compute auth tag: %w`, err)
}
if subtle.ConstantTimeCompare(expectedTag, tag) != 1 {
return nil, errors.New("invalid ciphertext (tag mismatch)")
return nil, fmt.Errorf(`invalid ciphertext (tag mismatch)`)
}
cbc := cipher.NewCBCDecrypter(c.blockCipher, nonce)
@ -211,7 +209,7 @@ func (c Hmac) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
plaintext, err := unpad(buf, c.blockCipher.BlockSize())
if err != nil {
return nil, errors.Wrap(err, `failed to generate plaintext from decrypted blocks`)
return nil, fmt.Errorf(`failed to generate plaintext from decrypted blocks: %w`, err)
}
ret := ensureSize(dst, len(plaintext))
out := ret[len(dst):]

View File

@ -5,10 +5,9 @@ import (
"crypto/cipher"
"fmt"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwe/internal/aescbc"
"github.com/lestrrat-go/jwx/jwe/internal/keygen"
"github.com/pkg/errors"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe/internal/aescbc"
"github.com/lestrrat-go/jwx/v2/jwe/internal/keygen"
)
var gcm = &gcmFetcher{}
@ -17,12 +16,12 @@ var cbc = &cbcFetcher{}
func (f gcmFetcher) Fetch(key []byte) (cipher.AEAD, error) {
aescipher, err := aes.NewCipher(key)
if err != nil {
return nil, errors.Wrap(err, "cipher: failed to create AES cipher for GCM")
return nil, fmt.Errorf(`cipher: failed to create AES cipher for GCM: %w`, err)
}
aead, err := cipher.NewGCM(aescipher)
if err != nil {
return nil, errors.Wrap(err, `failed to create GCM for cipher`)
return nil, fmt.Errorf(`failed to create GCM for cipher: %w`, err)
}
return aead, nil
}
@ -30,7 +29,7 @@ func (f gcmFetcher) Fetch(key []byte) (cipher.AEAD, error) {
func (f cbcFetcher) Fetch(key []byte) (cipher.AEAD, error) {
aead, err := aescbc.New(key, aes.NewCipher)
if err != nil {
return nil, errors.Wrap(err, "cipher: failed to create AES cipher for CBC")
return nil, fmt.Errorf(`cipher: failed to create AES cipher for CBC: %w`, err)
}
return aead, nil
}
@ -73,7 +72,7 @@ func NewAES(alg jwa.ContentEncryptionAlgorithm) (*AesContentCipher, error) {
keysize = tagsize * 2
fetcher = cbc
default:
return nil, errors.Errorf("failed to create AES content cipher: invalid algorithm (%s)", alg)
return nil, fmt.Errorf("failed to create AES content cipher: invalid algorithm (%s)", alg)
}
return &AesContentCipher{
@ -83,11 +82,11 @@ func NewAES(alg jwa.ContentEncryptionAlgorithm) (*AesContentCipher, error) {
}, nil
}
func (c AesContentCipher) Encrypt(cek, plaintext, aad []byte) (iv, ciphertext, tag []byte, err error) {
func (c AesContentCipher) Encrypt(cek, plaintext, aad []byte) (iv, ciphertxt, tag []byte, err error) {
var aead cipher.AEAD
aead, err = c.fetch.Fetch(cek)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to fetch AEAD")
return nil, nil, nil, fmt.Errorf(`failed to fetch AEAD: %w`, err)
}
// Seal may panic (argh!), so protect ourselves from that
@ -97,9 +96,9 @@ func (c AesContentCipher) Encrypt(cek, plaintext, aad []byte) (iv, ciphertext, t
case error:
err = e
default:
err = errors.Errorf("%s", e)
err = fmt.Errorf("%s", e)
}
err = errors.Wrap(err, "failed to encrypt")
err = fmt.Errorf(`failed to encrypt: %w`, err)
}
}()
@ -110,7 +109,7 @@ func (c AesContentCipher) Encrypt(cek, plaintext, aad []byte) (iv, ciphertext, t
bs, err = c.NonceGenerator.Generate()
}
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to generate nonce")
return nil, nil, nil, fmt.Errorf(`failed to generate nonce: %w`, err)
}
iv = bs.Bytes()
@ -122,8 +121,8 @@ func (c AesContentCipher) Encrypt(cek, plaintext, aad []byte) (iv, ciphertext, t
}
tag = combined[tagoffset:]
ciphertext = make([]byte, tagoffset)
copy(ciphertext, combined[:tagoffset])
ciphertxt = make([]byte, tagoffset)
copy(ciphertxt, combined[:tagoffset])
return
}
@ -131,7 +130,7 @@ func (c AesContentCipher) Encrypt(cek, plaintext, aad []byte) (iv, ciphertext, t
func (c AesContentCipher) Decrypt(cek, iv, ciphertxt, tag, aad []byte) (plaintext []byte, err error) {
aead, err := c.fetch.Fetch(cek)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch AEAD data")
return nil, fmt.Errorf(`failed to fetch AEAD data: %w`, err)
}
// Open may panic (argh!), so protect ourselves from that
@ -141,9 +140,9 @@ func (c AesContentCipher) Decrypt(cek, iv, ciphertxt, tag, aad []byte) (plaintex
case error:
err = e
default:
err = errors.Errorf("%s", e)
err = fmt.Errorf(`%s`, e)
}
err = errors.Wrap(err, "failed to decrypt")
err = fmt.Errorf(`failed to decrypt: %w`, err)
return
}
}()
@ -154,7 +153,7 @@ func (c AesContentCipher) Decrypt(cek, iv, ciphertxt, tag, aad []byte) (plaintex
buf, aeaderr := aead.Open(nil, iv, combined, aad)
if aeaderr != nil {
err = errors.Wrap(aeaderr, `aead.Open failed`)
err = fmt.Errorf(`aead.Open failed: %w`, aeaderr)
return
}
plaintext = buf

View File

@ -3,7 +3,7 @@ package cipher
import (
"crypto/cipher"
"github.com/lestrrat-go/jwx/jwe/internal/keygen"
"github.com/lestrrat-go/jwx/v2/jwe/internal/keygen"
)
const (

View File

@ -3,8 +3,7 @@ package concatkdf
import (
"crypto"
"encoding/binary"
"github.com/pkg/errors"
"fmt"
)
type KDF struct {
@ -48,13 +47,13 @@ func (k *KDF) Read(out []byte) (int, error) {
h.Reset()
if err := binary.Write(h, binary.BigEndian, round); err != nil {
return 0, errors.Wrap(err, "failed to write round using kdf")
return 0, fmt.Errorf(`failed to write round using kdf: %w`, err)
}
if _, err := h.Write(k.z); err != nil {
return 0, errors.Wrap(err, "failed to write z using kdf")
return 0, fmt.Errorf(`failed to write z using kdf: %w`, err)
}
if _, err := h.Write(k.otherinfo); err != nil {
return 0, errors.Wrap(err, "failed to write other info using kdf")
return 0, fmt.Errorf(`failed to write other info using kdf: %w`, err)
}
k.buf = append(k.buf, h.Sum(nil)...)

View File

@ -1,9 +1,10 @@
package content_crypt //nolint:golint
import (
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwe/internal/cipher"
"github.com/pkg/errors"
"fmt"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe/internal/cipher"
)
func (c Generic) Algorithm() jwa.ContentEncryptionAlgorithm {
@ -13,7 +14,7 @@ func (c Generic) Algorithm() jwa.ContentEncryptionAlgorithm {
func (c Generic) Encrypt(cek, plaintext, aad []byte) ([]byte, []byte, []byte, error) {
iv, encrypted, tag, err := c.cipher.Encrypt(cek, plaintext, aad)
if err != nil {
return nil, nil, nil, errors.Wrap(err, `failed to crypt content`)
return nil, nil, nil, fmt.Errorf(`failed to crypt content: %w`, err)
}
return iv, encrypted, tag, nil
@ -26,7 +27,7 @@ func (c Generic) Decrypt(cek, iv, ciphertext, tag, aad []byte) ([]byte, error) {
func NewGeneric(alg jwa.ContentEncryptionAlgorithm) (*Generic, error) {
c, err := cipher.NewAES(alg)
if err != nil {
return nil, errors.Wrap(err, `aes crypt: failed to create content cipher`)
return nil, fmt.Errorf(`aes crypt: failed to create content cipher: %w`, err)
}
return &Generic{

View File

@ -1,8 +1,8 @@
package content_crypt //nolint:golint
import (
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwe/internal/cipher"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe/internal/cipher"
)
// Generic encrypts a message by applying all the necessary

View File

@ -4,8 +4,8 @@ import (
"crypto/rsa"
"hash"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwe/internal/keygen"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe/internal/keygen"
)
// Encrypter is an interface for things that can encrypt keys

Some files were not shown because too many files have changed in this diff Show More