mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 00:26:36 +03:00
lib/jwt: opensource jwt library (#10426)
### Describe Your Changes It was [decided](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9439#issuecomment-3612299461) that OIDC authentication in vmauth will be part of open source repo. That requires opensourcing lib/jwt. PR does not contain any changes in logic, just copy-paste from enterprise repository. Related to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9439 ### Checklist The following checks are **mandatory**: - [ ] My change adheres to [VictoriaMetrics contributing guidelines](https://docs.victoriametrics.com/victoriametrics/contributing/#pull-request-checklist). - [ ] My change adheres to [VictoriaMetrics development goals](https://docs.victoriametrics.com/victoriametrics/goals/).
This commit is contained in:
58
lib/jwt/algo.go
Normal file
58
lib/jwt/algo.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
_ "crypto/sha256" // to register a hash
|
||||
_ "crypto/sha512" // to register a hash
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Verifier is used to verify tokens.
|
||||
type Verifier interface {
|
||||
Verify(token *Token) error
|
||||
}
|
||||
|
||||
// Algorithm for signing and verifying.
|
||||
type Algorithm string
|
||||
|
||||
func (a Algorithm) String() string { return string(a) }
|
||||
|
||||
// Algorithm names for signing and verifying.
|
||||
const (
|
||||
RS256 Algorithm = "RS256"
|
||||
RS384 Algorithm = "RS384"
|
||||
RS512 Algorithm = "RS512"
|
||||
|
||||
ES256 Algorithm = "ES256"
|
||||
ES384 Algorithm = "ES384"
|
||||
ES512 Algorithm = "ES512"
|
||||
|
||||
PS256 Algorithm = "PS256"
|
||||
PS384 Algorithm = "PS384"
|
||||
PS512 Algorithm = "PS512"
|
||||
)
|
||||
|
||||
// JWT sign, verify, build and parse errors.
|
||||
var (
|
||||
// ErrNilKey indicates that key is nil.
|
||||
ErrNilKey = errors.New("key is nil")
|
||||
|
||||
// ErrInvalidKey indicates that key is not valid.
|
||||
ErrInvalidKey = errors.New("key is not valid")
|
||||
|
||||
// ErrUnsupportedAlg indicates that given algorithm is not supported.
|
||||
ErrUnsupportedAlg = errors.New("algorithm is not supported")
|
||||
|
||||
// ErrInvalidSignature indicates that signature is not valid.
|
||||
ErrInvalidSignature = errors.New("signature is not valid")
|
||||
)
|
||||
|
||||
func hashPayload(hash crypto.Hash, payload []byte) ([]byte, error) {
|
||||
hasher := hash.New()
|
||||
|
||||
if _, err := hasher.Write(payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed := hasher.Sum(nil)
|
||||
return signed, nil
|
||||
}
|
||||
104
lib/jwt/algo_es.go
Normal file
104
lib/jwt/algo_es.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// newVerifierES returns a new ECDSA-based verifier.
|
||||
func newVerifierES(alg Algorithm, key *ecdsa.PublicKey) (*esAlg, error) {
|
||||
if key == nil {
|
||||
return nil, ErrNilKey
|
||||
}
|
||||
hash, err := getParamsES(alg, roundBytes(key.Params().BitSize)*2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &esAlg{
|
||||
alg: alg,
|
||||
hash: hash,
|
||||
publicKey: key,
|
||||
signSize: signSize(key),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func signSize(key *ecdsa.PublicKey) int {
|
||||
return roundBytes(key.Params().BitSize) * 2
|
||||
}
|
||||
|
||||
func getAlgorithmForKey(key *ecdsa.PublicKey) Algorithm {
|
||||
switch signSize(key) {
|
||||
case 64:
|
||||
return ES256
|
||||
case 96:
|
||||
return ES384
|
||||
case 132:
|
||||
return ES512
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getParamsES(alg Algorithm, size int) (crypto.Hash, error) {
|
||||
var hash crypto.Hash
|
||||
var keySize int
|
||||
|
||||
switch alg {
|
||||
case ES256:
|
||||
hash, keySize = crypto.SHA256, 64
|
||||
case ES384:
|
||||
hash, keySize = crypto.SHA384, 96
|
||||
case ES512:
|
||||
hash, keySize = crypto.SHA512, 132
|
||||
default:
|
||||
return 0, ErrUnsupportedAlg
|
||||
}
|
||||
|
||||
if keySize != size {
|
||||
return 0, ErrInvalidKey
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
type esAlg struct {
|
||||
alg Algorithm
|
||||
hash crypto.Hash
|
||||
publicKey *ecdsa.PublicKey
|
||||
signSize int
|
||||
}
|
||||
|
||||
func (es *esAlg) SignSize() int {
|
||||
return es.signSize
|
||||
}
|
||||
|
||||
func (es *esAlg) Verify(token *Token) error {
|
||||
return es.verify(token.payload, token.signature)
|
||||
}
|
||||
|
||||
func (es *esAlg) verify(payload, signature []byte) error {
|
||||
if len(signature) != es.SignSize() {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
|
||||
digest, err := hashPayload(es.hash, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pivot := es.SignSize() / 2
|
||||
r := big.NewInt(0).SetBytes(signature[:pivot])
|
||||
s := big.NewInt(0).SetBytes(signature[pivot:])
|
||||
|
||||
if !ecdsa.Verify(es.publicKey, digest, r, s) {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func roundBytes(n int) int {
|
||||
res := n / 8
|
||||
if n%8 > 0 {
|
||||
return res + 1
|
||||
}
|
||||
return res
|
||||
}
|
||||
77
lib/jwt/algo_ps.go
Normal file
77
lib/jwt/algo_ps.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
// newVerifierPS returns a new RSA-PSS-based signer.
|
||||
func newVerifierPS(alg Algorithm, key *rsa.PublicKey) (*psAlg, error) {
|
||||
if key == nil {
|
||||
return nil, ErrNilKey
|
||||
}
|
||||
hash, opts, err := getParamsPS(alg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &psAlg{
|
||||
alg: alg,
|
||||
hash: hash,
|
||||
publicKey: key,
|
||||
opts: opts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getParamsPS(alg Algorithm) (crypto.Hash, *rsa.PSSOptions, error) {
|
||||
switch alg {
|
||||
case PS256:
|
||||
return crypto.SHA256, optsPS256, nil
|
||||
case PS384:
|
||||
return crypto.SHA384, optsPS384, nil
|
||||
case PS512:
|
||||
return crypto.SHA512, optsPS512, nil
|
||||
default:
|
||||
return 0, nil, ErrUnsupportedAlg
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
optsPS256 = &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
Hash: crypto.SHA256,
|
||||
}
|
||||
|
||||
optsPS384 = &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
Hash: crypto.SHA384,
|
||||
}
|
||||
|
||||
optsPS512 = &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
Hash: crypto.SHA512,
|
||||
}
|
||||
)
|
||||
|
||||
type psAlg struct {
|
||||
alg Algorithm
|
||||
hash crypto.Hash
|
||||
publicKey *rsa.PublicKey
|
||||
opts *rsa.PSSOptions
|
||||
}
|
||||
|
||||
func (ps *psAlg) Verify(token *Token) error {
|
||||
return ps.verify(token.payload, token.signature)
|
||||
}
|
||||
|
||||
func (ps *psAlg) verify(payload, signature []byte) error {
|
||||
digest, err := hashPayload(ps.hash, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errVerify := rsa.VerifyPSS(ps.publicKey, ps.hash, digest, signature, ps.opts)
|
||||
if errVerify != nil {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
60
lib/jwt/algo_rs.go
Normal file
60
lib/jwt/algo_rs.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
// newVerifierRS returns a new RSA-based verifier.
|
||||
func newVerifierRS(alg Algorithm, key *rsa.PublicKey) (*rsAlg, error) {
|
||||
if key == nil {
|
||||
return nil, ErrNilKey
|
||||
}
|
||||
hash, err := getHashRS(alg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rsAlg{
|
||||
alg: alg,
|
||||
hash: hash,
|
||||
publicKey: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getHashRS(alg Algorithm) (crypto.Hash, error) {
|
||||
var hash crypto.Hash
|
||||
switch alg {
|
||||
case RS256:
|
||||
hash = crypto.SHA256
|
||||
case RS384:
|
||||
hash = crypto.SHA384
|
||||
case RS512:
|
||||
hash = crypto.SHA512
|
||||
default:
|
||||
return 0, ErrUnsupportedAlg
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
type rsAlg struct {
|
||||
alg Algorithm
|
||||
hash crypto.Hash
|
||||
publicKey *rsa.PublicKey
|
||||
}
|
||||
|
||||
func (rs *rsAlg) Verify(token *Token) error {
|
||||
return rs.verify(token.payload, token.signature)
|
||||
}
|
||||
|
||||
func (rs *rsAlg) verify(payload, signature []byte) error {
|
||||
digest, err := hashPayload(rs.hash, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errVerify := rsa.VerifyPKCS1v15(rs.publicKey, rs.hash, digest, signature)
|
||||
if errVerify != nil {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
282
lib/jwt/jwt.go
Normal file
282
lib/jwt/jwt.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
prefix = "Bearer "
|
||||
)
|
||||
|
||||
const (
|
||||
read = 1 << iota
|
||||
write
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrHeaderMissing missing header.
|
||||
ErrHeaderMissing = fmt.Errorf("jwt authorization header is missing")
|
||||
// ErrVMAccessFieldMissing missing vm_access field.
|
||||
ErrVMAccessFieldMissing = fmt.Errorf("missing `vm_access` claim")
|
||||
// ErrBadTokenFormat incorrect format for token
|
||||
ErrBadTokenFormat = fmt.Errorf("bad token format, must be jwt")
|
||||
)
|
||||
|
||||
// Token represents jwt token
|
||||
// https://auth0.com/docs/tokens/json-web-tokens
|
||||
type Token struct {
|
||||
header *header
|
||||
body *body
|
||||
payload, signature []byte
|
||||
}
|
||||
|
||||
type header struct {
|
||||
Alg string `json:"alg"`
|
||||
Typ string `json:"typ"`
|
||||
Kid string `json:"kid"`
|
||||
}
|
||||
|
||||
type body struct {
|
||||
// expired at time unix_ts
|
||||
Exp int64 `json:"exp"`
|
||||
// issued at time unix_ts
|
||||
Iat int64 `json:"iat"`
|
||||
Jti string `json:"jti,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
VMAccess *access `json:"vm_access"`
|
||||
}
|
||||
|
||||
// Labels defines labels added to filters or incoming time series.
|
||||
type Labels map[string]string
|
||||
|
||||
// AsExtraLabels - converts labels to label=value pairs.
|
||||
func (l Labels) AsExtraLabels() []string {
|
||||
if len(l) == 0 {
|
||||
return nil
|
||||
}
|
||||
res := make([]string, 0, len(l))
|
||||
for k, v := range l {
|
||||
res = append(res, k+"="+v)
|
||||
}
|
||||
// sort for consistent uri.
|
||||
sort.Slice(res, func(i, j int) bool {
|
||||
return res[i] < res[j]
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
type access struct {
|
||||
Tenant TenantID `json:"tenant_id,omitempty"`
|
||||
Labels Labels `json:"extra_labels,omitempty"`
|
||||
// promql filters applied to each select query
|
||||
ExtraFilters []string `json:"extra_filters,omitempty"`
|
||||
// role can be denied as 1 = read, 2 = write, 3 = read and write
|
||||
// 0 = unconfigured - read and write
|
||||
Mode int `json:"mode,omitempty"`
|
||||
}
|
||||
|
||||
// TenantID represents tenantID.
|
||||
type TenantID struct {
|
||||
ProjectID int32 `json:"project_id"`
|
||||
AccountID int32 `json:"account_id"`
|
||||
}
|
||||
|
||||
// String implements interface.
|
||||
func (tid TenantID) String() string {
|
||||
return fmt.Sprintf("%d:%d", tid.AccountID, tid.ProjectID)
|
||||
}
|
||||
|
||||
// NewToken creates token from raw string.
|
||||
func NewToken(auth string, enforceAuthPrefix bool) (*Token, error) {
|
||||
if enforceAuthPrefix && (len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix)) {
|
||||
return nil, fmt.Errorf("wrong format, prefix: %s is missing", prefix)
|
||||
}
|
||||
// remove prefix if it is present
|
||||
auth = strings.TrimPrefix(auth, prefix)
|
||||
jwt := strings.SplitN(auth, ".", 3)
|
||||
if len(jwt) != 3 {
|
||||
return nil, ErrBadTokenFormat
|
||||
}
|
||||
var t Token
|
||||
return t.parse(jwt[0], jwt[1], jwt[2])
|
||||
}
|
||||
|
||||
// NewTokenFromRequestWithCustomHeader return new jwt token from request by provided header
|
||||
func NewTokenFromRequestWithCustomHeader(r *http.Request, headerName string, enforceAuthPrefix bool) (*Token, error) {
|
||||
auth := r.Header.Get(headerName)
|
||||
if len(auth) == 0 {
|
||||
return nil, ErrHeaderMissing
|
||||
}
|
||||
return NewToken(auth, enforceAuthPrefix)
|
||||
}
|
||||
|
||||
func (t *Token) parse(header, body, signature string) (*Token, error) {
|
||||
b, err := parseJWTBody(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.VMAccess == nil {
|
||||
return nil, ErrVMAccessFieldMissing
|
||||
}
|
||||
t.body = b
|
||||
h, err := parseJWTHeader(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.header = h
|
||||
|
||||
t.payload = []byte(header + "." + body)
|
||||
t.signature, err = decodeB64([]byte(signature))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode signature as b64: %w", err)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// IsExpired checks if jwt token is expired.
|
||||
func (t *Token) IsExpired(currentTime time.Time) bool {
|
||||
return currentTime.Unix() > t.body.Exp
|
||||
}
|
||||
|
||||
// CanWrite checks if token has write permissions.
|
||||
func (t *Token) CanWrite() bool {
|
||||
// unconfigured
|
||||
if t.body.VMAccess.Mode == 0 {
|
||||
return true
|
||||
}
|
||||
if write&t.body.VMAccess.Mode > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CanRead check if token has read permissions.
|
||||
func (t *Token) CanRead() bool {
|
||||
// unconfigured
|
||||
if t.body.VMAccess.Mode == 0 {
|
||||
return true
|
||||
}
|
||||
if read&t.body.VMAccess.Mode > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AccessLabels returns access labels for given JWT token,
|
||||
// in key=value format.
|
||||
func (t *Token) AccessLabels() []string {
|
||||
return t.body.VMAccess.Labels.AsExtraLabels()
|
||||
}
|
||||
|
||||
// Tenant returns tenantID for token.
|
||||
func (t *Token) Tenant() TenantID {
|
||||
return t.body.VMAccess.Tenant
|
||||
}
|
||||
|
||||
// ExtraFilters metricsql filters for select queries
|
||||
func (t *Token) ExtraFilters() []string {
|
||||
return t.body.VMAccess.ExtraFilters
|
||||
}
|
||||
|
||||
func parseJWTHeader(data string) (*header, error) {
|
||||
var jh header
|
||||
decoded, err := decodeB64([]byte(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decode jwt header as b64: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(decoded, &jh); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse jwt header: %w", err)
|
||||
}
|
||||
return &jh, nil
|
||||
}
|
||||
|
||||
func parseJWTBody(data string) (*body, error) {
|
||||
type tbody struct {
|
||||
// expired at time unix_ts
|
||||
Exp int64 `json:"exp"`
|
||||
// issued at time unix_ts
|
||||
Iat int64 `json:"iat"`
|
||||
Jti string `json:"jti,omitempty"`
|
||||
Scope *json.RawMessage `json:"scope,omitempty"`
|
||||
// store as raw message to support different types
|
||||
VMAccess *json.RawMessage `json:"vm_access"`
|
||||
}
|
||||
var tb tbody
|
||||
|
||||
decoded, err := decodeB64([]byte(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decode jwt body as b64: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(decoded, &tb); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse jwt body: %w", err)
|
||||
}
|
||||
|
||||
if tb.VMAccess == nil {
|
||||
return nil, ErrVMAccessFieldMissing
|
||||
}
|
||||
|
||||
// some IDPs encode custom claims as a string
|
||||
// try parsing as an object and fallback to a string
|
||||
var a access
|
||||
if err := json.Unmarshal(*tb.VMAccess, &a); err != nil {
|
||||
var s string
|
||||
if err := json.Unmarshal(*tb.VMAccess, &s); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse jwt body vm_access: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(s), &a); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse jwt body vm_access: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// some IDPs encode scope as a string and some as an array
|
||||
var scope string
|
||||
if tb.Scope != nil {
|
||||
if err := json.Unmarshal(*tb.Scope, &scope); err != nil {
|
||||
var scopeSlice []string
|
||||
if err := json.Unmarshal(*tb.Scope, &scopeSlice); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse jwt body scope: %w", err)
|
||||
}
|
||||
scope = strings.Join(scopeSlice, " ")
|
||||
}
|
||||
}
|
||||
|
||||
parsedBody := &body{
|
||||
Exp: tb.Exp,
|
||||
Iat: tb.Iat,
|
||||
Jti: tb.Jti,
|
||||
Scope: scope,
|
||||
VMAccess: &a,
|
||||
}
|
||||
return parsedBody, nil
|
||||
}
|
||||
|
||||
func decodeB64(data []byte) ([]byte, error) {
|
||||
idx := bytes.IndexAny(data, "+/")
|
||||
// slow path, std base64 encoding convert it to url encoding
|
||||
if idx > 0 {
|
||||
for idx, c := range data {
|
||||
switch c {
|
||||
case '+':
|
||||
data[idx] = '-'
|
||||
case '/':
|
||||
data[idx] = '_'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
dst := make([]byte, base64.RawURLEncoding.DecodedLen(len(data)))
|
||||
_, err := base64.RawURLEncoding.Decode(dst, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decode jwt body as b64: %w", err)
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
626
lib/jwt/jwt_test.go
Normal file
626
lib/jwt/jwt_test.go
Normal file
@@ -0,0 +1,626 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseJWTHeader_Failure(t *testing.T) {
|
||||
f := func(data, expectedErr string, encode bool) {
|
||||
t.Helper()
|
||||
if encode {
|
||||
encodedLen := base64.RawURLEncoding.EncodedLen(len(data))
|
||||
encoded := make([]byte, encodedLen)
|
||||
base64.RawURLEncoding.Encode(encoded, []byte(data))
|
||||
data = string(encoded)
|
||||
}
|
||||
if _, err := parseJWTHeader(data); err != nil {
|
||||
if err.Error() != expectedErr {
|
||||
t.Errorf("unexpected error message: \ngot\n%s\nwant\n%s", err.Error(), expectedErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// invalid input
|
||||
f(
|
||||
`bad input`,
|
||||
`cannot decode jwt header as b64: cannot decode jwt body as b64: illegal base64 data at input byte 3`,
|
||||
false,
|
||||
)
|
||||
|
||||
// invalid b644
|
||||
f(
|
||||
`YmFk`,
|
||||
`cannot parse jwt header: invalid character 'b' looking for beginning of value`,
|
||||
false,
|
||||
)
|
||||
|
||||
// invalid header json
|
||||
f(`{]`,
|
||||
`cannot parse jwt header: invalid character ']' looking for beginning of object key string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid header type json
|
||||
f(`[]`,
|
||||
`cannot parse jwt header: json: cannot unmarshal array into Go value of type jwt.header`,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func TestParseJWTHeader_Success(t *testing.T) {
|
||||
f := func(data string, expected *header) {
|
||||
t.Helper()
|
||||
encodedLen := base64.RawURLEncoding.EncodedLen(len(data))
|
||||
encoded := make([]byte, encodedLen)
|
||||
base64.RawURLEncoding.Encode(encoded, []byte(data))
|
||||
header, err := parseJWTHeader(string(encoded))
|
||||
if err != nil {
|
||||
t.Fatalf("parseJWTHeader() error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(header, expected) {
|
||||
t.Fatalf("unexpected token header;\ngot\n%v\nwant\n%v", header, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// parse ok supported algorithms
|
||||
supportedAlgorithms := []string{
|
||||
"RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512",
|
||||
}
|
||||
for i := range supportedAlgorithms {
|
||||
f(fmt.Sprintf(`{
|
||||
"alg": %q,
|
||||
"kid": "test"
|
||||
}`, supportedAlgorithms[i]),
|
||||
&header{
|
||||
Alg: supportedAlgorithms[i],
|
||||
Kid: "test",
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseJWTBody_Failure(t *testing.T) {
|
||||
f := func(data, expectedErr string, encode bool) {
|
||||
t.Helper()
|
||||
if encode {
|
||||
encodedLen := base64.RawURLEncoding.EncodedLen(len(data))
|
||||
encoded := make([]byte, encodedLen)
|
||||
base64.RawURLEncoding.Encode(encoded, []byte(data))
|
||||
data = string(encoded)
|
||||
}
|
||||
if _, err := parseJWTBody(data); err != nil {
|
||||
if err.Error() != expectedErr {
|
||||
t.Errorf("unexpected error message: \ngot\n%s\nwant\n%s", err.Error(), expectedErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// invalid input
|
||||
f(
|
||||
`bad input`,
|
||||
`cannot decode jwt body as b64: cannot decode jwt body as b64: illegal base64 data at input byte 3`,
|
||||
false,
|
||||
)
|
||||
|
||||
// invalid b644
|
||||
f(
|
||||
`YmFk`,
|
||||
`cannot parse jwt body: invalid character 'b' looking for beginning of value`,
|
||||
false,
|
||||
)
|
||||
|
||||
// invalid body json
|
||||
f(
|
||||
`{]`,
|
||||
`cannot parse jwt body: invalid character ']' looking for beginning of object key string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid body type json
|
||||
f(
|
||||
`[]`,
|
||||
`cannot parse jwt body: json: cannot unmarshal array into Go value of type jwt.tbody`,
|
||||
true,
|
||||
)
|
||||
|
||||
// missing vm_access claim
|
||||
f(
|
||||
`{}`,
|
||||
"missing `vm_access` claim",
|
||||
true,
|
||||
)
|
||||
|
||||
// vm_access claim invalid type
|
||||
f(
|
||||
`{"vm_access": 123}`,
|
||||
"cannot parse jwt body vm_access: json: cannot unmarshal number into Go value of type string",
|
||||
true,
|
||||
)
|
||||
|
||||
// vm_access claim null
|
||||
f(
|
||||
`{"vm_access": null}`,
|
||||
"missing `vm_access` claim",
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid vm_access: account_id type mismatch
|
||||
f(
|
||||
`{"vm_access": {"tenant_id": {"account_id": "1", "project_id": 5}}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid vm_access: project_id type mismatch
|
||||
f(
|
||||
`{"vm_access": {"tenant_id": {"account_id": 1, "project_id": "5"}}}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid vm_access: extra_label type mismatch
|
||||
f(`
|
||||
{
|
||||
"vm_access": {
|
||||
"extra_labels": [{
|
||||
"project": "dev",
|
||||
"team": "mobile"
|
||||
}],
|
||||
"tenant_id": {
|
||||
"account_id": 1,
|
||||
"project_id": 5
|
||||
}
|
||||
}
|
||||
}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid vm_access: extra_filters type mismatch
|
||||
f(`
|
||||
{
|
||||
"vm_access": {
|
||||
"extra_filters": [{}],
|
||||
"tenant_id": {
|
||||
"account_id": 1,
|
||||
"project_id": 5
|
||||
}
|
||||
}
|
||||
}`,
|
||||
`cannot parse jwt body vm_access: json: cannot unmarshal object into Go value of type string`,
|
||||
true,
|
||||
)
|
||||
|
||||
// invalid exp claim value type
|
||||
f(
|
||||
`{"exp": "1610976189", "vm_access": {}}`,
|
||||
`cannot parse jwt body: json: cannot unmarshal string into Go struct field tbody.exp of type int64`,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func TestParseJWTBody_Success(t *testing.T) {
|
||||
f := func(data string, resultExpected *body) {
|
||||
t.Helper()
|
||||
|
||||
encodedLen := base64.RawURLEncoding.EncodedLen(len(data))
|
||||
encoded := make([]byte, encodedLen)
|
||||
base64.RawURLEncoding.Encode(encoded, []byte(data))
|
||||
|
||||
result, err := parseJWTBody(string(encoded))
|
||||
if err != nil {
|
||||
t.Fatalf("parseJWTBody() error: %s", err)
|
||||
}
|
||||
if result.Exp != resultExpected.Exp {
|
||||
t.Fatalf("unexpected Exp; got %d; want %d", result.Exp, resultExpected.Exp)
|
||||
}
|
||||
if result.Iat != resultExpected.Iat {
|
||||
t.Fatalf("unexpected Iat; got %d; want %d", result.Iat, resultExpected.Iat)
|
||||
}
|
||||
if result.Scope != resultExpected.Scope {
|
||||
t.Fatalf("unexpected scope; got %q; want %q", result.Scope, resultExpected.Scope)
|
||||
}
|
||||
if result.Jti != resultExpected.Jti {
|
||||
t.Fatalf("unexpected jti; got %q; want %q", result.Jti, resultExpected.Jti)
|
||||
}
|
||||
if !reflect.DeepEqual(result.VMAccess.Tenant, resultExpected.VMAccess.Tenant) {
|
||||
t.Fatalf("unexpected tenant; got %v; want %v", result.VMAccess.Tenant, resultExpected.VMAccess.Tenant)
|
||||
}
|
||||
if !reflect.DeepEqual(result.VMAccess.Labels, resultExpected.VMAccess.Labels) {
|
||||
t.Fatalf("unexpected labels; got %v; want %v", result.VMAccess.Labels, resultExpected.VMAccess.Labels)
|
||||
}
|
||||
if !reflect.DeepEqual(result.VMAccess.ExtraFilters, resultExpected.VMAccess.ExtraFilters) {
|
||||
t.Fatalf("unexpected extra_filters; got %v; want %v", result.VMAccess.ExtraFilters, resultExpected.VMAccess.ExtraFilters)
|
||||
}
|
||||
}
|
||||
|
||||
f(`{"vm_access": {}}`, &body{
|
||||
VMAccess: &access{},
|
||||
})
|
||||
f(`{"vm_access": {"tenant_id": {}}}`, &body{
|
||||
VMAccess: &access{},
|
||||
})
|
||||
|
||||
f(
|
||||
`
|
||||
{
|
||||
"vm_access": {
|
||||
"tenant_id": {
|
||||
"project_id": 5,
|
||||
"account_id": 1
|
||||
}
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
f(
|
||||
`
|
||||
{
|
||||
"vm_access": {
|
||||
"extra_labels": {
|
||||
"project": "dev",
|
||||
"team": "mobile"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &access{
|
||||
Labels: Labels{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
f(
|
||||
`
|
||||
{
|
||||
"vm_access": {
|
||||
"extra_filters": [
|
||||
"{project=\"dev\"}",
|
||||
"{team=~\"mobile\"}"
|
||||
]
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &access{
|
||||
ExtraFilters: []string{
|
||||
`{project="dev"}`,
|
||||
`{team=~"mobile"}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
f(
|
||||
`
|
||||
{
|
||||
"vm_access": {
|
||||
"tenant_id": {
|
||||
"project_id": 5,
|
||||
"account_id": 1
|
||||
},
|
||||
"extra_labels": {
|
||||
"project": "dev",
|
||||
"team": "mobile"
|
||||
},
|
||||
"extra_filters": [
|
||||
"{project=\"dev\"}",
|
||||
"{team=~\"mobile\"}"
|
||||
]
|
||||
}
|
||||
}`,
|
||||
&body{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
Labels: Labels{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
},
|
||||
ExtraFilters: []string{
|
||||
`{project="dev"}`,
|
||||
`{team=~"mobile"}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
f(
|
||||
`
|
||||
{
|
||||
"exp": 1610976189,
|
||||
"iat": 1610975889,
|
||||
"jti": "9b194187-6bb7-4244-9d1b-559eab2ef7f3",
|
||||
"scope": "openid email profile",
|
||||
"vm_access": {}
|
||||
}`,
|
||||
&body{
|
||||
Exp: 1610976189,
|
||||
Iat: 1610975889,
|
||||
Jti: "9b194187-6bb7-4244-9d1b-559eab2ef7f3",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &access{},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestNewTokenFromRequest_Failure(t *testing.T) {
|
||||
f := func(r *http.Request) {
|
||||
t.Helper()
|
||||
|
||||
_, err := NewTokenFromRequestWithCustomHeader(r, "Authorization", false)
|
||||
if err == nil {
|
||||
t.Fatalf("expecting non-nil error")
|
||||
}
|
||||
}
|
||||
|
||||
// missing header
|
||||
f(&http.Request{})
|
||||
|
||||
// bad input
|
||||
f(&http.Request{
|
||||
Header: map[string][]string{
|
||||
"Authorization": {
|
||||
"Bearer fsfFSF",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// bad input malformed
|
||||
r := &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Authorization": {
|
||||
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhQVpvQ0d2dUdiRm9mdFdIeFFaeVJTUWVuM3lYNFUwR1BsUDVvWk9RU3djIn0.eyJleHAiOjE2MTA4ODkyNjYsImlhdCI6MTYxMDg4ODk2NiwiYXV0aF90aW1lIjoxNjEwODg4MDQ0LCJqdGkiOiIwOWEwNThhMi0wNzUyLTRlY2QtYTRlOS1iNjVlODVhZjQyM2YiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIzZGRjODc0OS1lZTI2LTQ2ODEtOWNlYy03M2U5YmIyZmRkOGUiLCJhY3IiOiIwIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bS1hY2Nlc3MiOnsibGFiZWxzIjp7InByb2plY3QiOiJkZXYiLCJ0ZWFtIjoibW9iaWxlIn0sInRlbmFudElEIjp7ImFjY291bnRJRCI6MSwicHJvamVjdElEIjo1fX0sImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRnIHRnIiwicHJvamVjdCI6Im1vYmlsZSIsInByZWZlcnJlZF91c2VybmFtZSI6InRnIiwidGVhbSI6ImRldiIsImdpdmVuX25hbWUiOiJ0ZyIsImZhbWlseV9uYW1lIjoidGciLCJlbWFpbCI6InRnQGZnaHQubmV0In0",
|
||||
},
|
||||
},
|
||||
}
|
||||
f(r)
|
||||
}
|
||||
|
||||
func TestNewTokenFromRequest_Success(t *testing.T) {
|
||||
f := func(r *http.Request, resultExpected *Token, enforcePrefix bool) {
|
||||
t.Helper()
|
||||
|
||||
result, err := NewTokenFromRequestWithCustomHeader(r, "Authorization", enforcePrefix)
|
||||
if err != nil {
|
||||
t.Fatalf("NewTokenFromRequest() error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(result.body.VMAccess, resultExpected.body.VMAccess) {
|
||||
t.Fatalf("unxpected token body VMAccess;\ngot\n%v\nwant\n%v", result.body.VMAccess, resultExpected.body.VMAccess)
|
||||
}
|
||||
if !reflect.DeepEqual(result.header, resultExpected.header) {
|
||||
t.Fatalf("unxpected token header\ngot\n%v\nwant\n%v", result.header, resultExpected.header)
|
||||
}
|
||||
}
|
||||
|
||||
// parse ok
|
||||
r := &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Authorization": {
|
||||
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhQVpvQ0d2dUdiRm9mdFdIeFFaeVJTUWVuM3lYNFUwR1BsUDVvWk9RU3djIn0.eyJleHAiOjE2MTA5NzYxODksImlhdCI6MTYxMDk3NTg4OSwiYXV0aF90aW1lIjoxNjEwOTc1ODg5LCJqdGkiOiI5YjE5NDE4Ny02YmI3LTQyNDQtOWQxYi01NTllYWIyZWY3ZjMiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIxMzc3ZDEwMi03NTJiLTQ0ODYtOTlkYS1jMjA4MjRiODJkMzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bV9hY2Nlc3MiOnsiZXh0cmFfbGFiZWxzIjp7InByb2plY3QiOiJkZXYiLCJ0ZWFtIjoibW9iaWxlIn0sInRlbmFudF9pZCI6eyJhY2NvdW50X2lkIjoxLCJwcm9qZWN0X2lkIjo1fX0sImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRnIHRnIiwicHJvamVjdCI6Im1vYmlsZSIsInByZWZlcnJlZF91c2VybmFtZSI6InRnIiwidGVhbSI6ImRldiIsImdpdmVuX25hbWUiOiJ0ZyIsImZhbWlseV9uYW1lIjoidGciLCJlbWFpbCI6InRnQGZnaHQubmV0In0.XErPkz-qL-EV8BBAR17MoFytc5ajYRz71f9_GOuG1AVcMnUsD6D3x4z5jL1dLyoGGm8OUW_RIVrjMpZf_xOfgQKRVHAMaJi64UtpwS8EF50mlOCDAdKl6wlzAS4laV3dW9W9QrTH7TMetG33WVsJGaD-MIwSYJ5peh6u__oniezsRavw8Qw3nLpZCQPb-NatT3Q1raj1ymLJErJPtUBSk3ieWCVpTMo4ZYKFIQt2wjHeOVOF_3suhPfhgEgXlN6aUq3xeYJ1aAtl_5Ao3pB2pto46kDSXIulQQuGdttsw7bSDOYqZ-tx3y7DBWNdIcghsO_iMvrA805j5hG4Nu84Sw",
|
||||
},
|
||||
},
|
||||
}
|
||||
resultExpected := &Token{
|
||||
body: &body{
|
||||
Exp: 1610889266,
|
||||
Iat: 1610888966,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
Alg: "RS256",
|
||||
Kid: "aAZoCGvuGbFoftWHxQZyRSQen3yX4U0GPlP5oZOQSwc",
|
||||
Typ: "JWT",
|
||||
},
|
||||
}
|
||||
f(r, resultExpected, true)
|
||||
|
||||
// go-jwt
|
||||
r = &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Authorization": {
|
||||
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDU1MzY3NTgsImlhdCI6MTY0NTUzNjYzOCwidm1fYWNjZXNzIjp7ImV4dHJhX2ZpbHRlcnMiOlsie25hbWVzcGFjZT1-XCJlaWZ0ZGkxLXRlc3RcIn0iXSwibW9kZSI6MSwidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjB9fX0.4r3zE487ochfj_GgYRpbjmid5ktlBH0bKfz3Ut45Foc",
|
||||
},
|
||||
},
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 0,
|
||||
AccountID: 1,
|
||||
},
|
||||
ExtraFilters: []string{
|
||||
`{namespace=~"eiftdi1-test"}`,
|
||||
},
|
||||
Mode: 1,
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
Alg: "HS256",
|
||||
Typ: "JWT",
|
||||
},
|
||||
}
|
||||
f(r, resultExpected, true)
|
||||
|
||||
// jwt-with-std-b64
|
||||
r = &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Authorization": {
|
||||
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDU2MDY5OTgsImlhdCI6MTY0NTYwNjg3OCwidm1fYWNjZXNzIjp7ImV4dHJhX2ZpbHRlcnMiOlsie25hbWVzcGFjZT1+XCJlaWZ0ZGkxLXRlc3RcIn0iXSwibW9kZSI6MSwidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjB9fX0.oAYJdff8DK4+P1oR6tBE1l2mq79p3eJ5crXlkO+CxcA",
|
||||
},
|
||||
},
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 0,
|
||||
AccountID: 1,
|
||||
},
|
||||
ExtraFilters: []string{
|
||||
`{namespace=~"eiftdi1-test"}`,
|
||||
},
|
||||
Mode: 1,
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
Alg: "HS256",
|
||||
Typ: "JWT",
|
||||
},
|
||||
}
|
||||
f(r, resultExpected, true)
|
||||
|
||||
// parse ok with filters
|
||||
r = &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Authorization": {
|
||||
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFBWm9DR3Z1R2JGb2Z0V0h4UVp5UlNRZW4zeVg0VTBHUGxQNW9aT1FTd2MifQ.eyJleHAiOjE2MTA5NzYxODksImlhdCI6MTYxMDk3NTg4OSwiYXV0aF90aW1lIjoxNjEwOTc1ODg5LCJqdGkiOiI5YjE5NDE4Ny02YmI3LTQyNDQtOWQxYi01NTllYWIyZWY3ZjMiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIxMzc3ZDEwMi03NTJiLTQ0ODYtOTlkYS1jMjA4MjRiODJkMzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bV9hY2Nlc3MiOnsiZXh0cmFfbGFiZWxzIjp7InByb2plY3QiOiJkZXYiLCJ0ZWFtIjoibW9iaWxlIn0sImV4dHJhX2ZpbHRlcnMiOlsie2Vudj1+XCJwcm9kfGRldlwifSIsInt0ZWFtIT1cInRlc3RcIn0iXSwidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjV9fSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGcgdGciLCJwcm9qZWN0IjoibW9iaWxlIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGciLCJ0ZWFtIjoiZGV2IiwiZ2l2ZW5fbmFtZSI6InRnIiwiZmFtaWx5X25hbWUiOiJ0ZyJ9.Nx9An-sqto8ClmNah8Mi6y16mjB6jk-I1kxQdtP0j0c",
|
||||
},
|
||||
},
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
Exp: 1610889266,
|
||||
Iat: 1610888966,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
},
|
||||
ExtraFilters: []string{`{env=~"prod|dev"}`, `{team!="test"}`},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
Alg: "HS256",
|
||||
Kid: "aAZoCGvuGbFoftWHxQZyRSQen3yX4U0GPlP5oZOQSwc",
|
||||
Typ: "JWT",
|
||||
},
|
||||
}
|
||||
f(r, resultExpected, true)
|
||||
|
||||
// parse ok without prefix
|
||||
r = &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Authorization": {
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFBWm9DR3Z1R2JGb2Z0V0h4UVp5UlNRZW4zeVg0VTBHUGxQNW9aT1FTd2MifQ.eyJleHAiOjE2MTA5NzYxODksImlhdCI6MTYxMDk3NTg4OSwiYXV0aF90aW1lIjoxNjEwOTc1ODg5LCJqdGkiOiI5YjE5NDE4Ny02YmI3LTQyNDQtOWQxYi01NTllYWIyZWY3ZjMiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIxMzc3ZDEwMi03NTJiLTQ0ODYtOTlkYS1jMjA4MjRiODJkMzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bV9hY2Nlc3MiOnsiZXh0cmFfbGFiZWxzIjp7InByb2plY3QiOiJkZXYiLCJ0ZWFtIjoibW9iaWxlIn0sImV4dHJhX2ZpbHRlcnMiOlsie2Vudj1+XCJwcm9kfGRldlwifSIsInt0ZWFtIT1cInRlc3RcIn0iXSwidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjV9fSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGcgdGciLCJwcm9qZWN0IjoibW9iaWxlIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGciLCJ0ZWFtIjoiZGV2IiwiZ2l2ZW5fbmFtZSI6InRnIiwiZmFtaWx5X25hbWUiOiJ0ZyJ9.Nx9An-sqto8ClmNah8Mi6y16mjB6jk-I1kxQdtP0j0c",
|
||||
},
|
||||
},
|
||||
}
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
Exp: 1610889266,
|
||||
Iat: 1610888966,
|
||||
Jti: "09a058a2-0752-4ecd-a4e9-b65e85af423f",
|
||||
Scope: "openid email profile",
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"project": "dev",
|
||||
"team": "mobile",
|
||||
},
|
||||
ExtraFilters: []string{`{env=~"prod|dev"}`, `{team!="test"}`},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
Alg: "HS256",
|
||||
Kid: "aAZoCGvuGbFoftWHxQZyRSQen3yX4U0GPlP5oZOQSwc",
|
||||
Typ: "JWT",
|
||||
},
|
||||
}
|
||||
f(r, resultExpected, false)
|
||||
|
||||
// parse ok with string vm_access
|
||||
r = &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Authorization": {
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ikg5bmo1QU9Tc3dNcGhnMVNGeDdqYVYtbEI5dyJ9.eyJhdWQiOiI3YTczMTFlNy1iYTdlLTQ5NWUtOTk1ZS1hZjUzNGU3M2MxMTAiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vMjVkYTFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzL3YyLjAiLCJpYXQiOjE3MjU2MjUzMzIsIm5iZiI6MTcyNTYyNTMzMiwiZXhwIjoxNzI1NjI5MjMyLCJuYW1lIjoiWmFraGFyIEJlc3NhcmFiIiwib2lkIjoiOGI5ZWY2YjMtMWMwMS00YjczLTg0ODItMjRkNmI2NTE1Y2U0IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiei5iZXNzYXJhYkB2aWN0b3JpYW1ldHJpY3MuY29tIiwicmgiOiIwLkFXTUJ6aDdhSlpGbWFFaW5leHNQbTc1ZlEtY1JjM3AtdWw1Sm1WNnZVMDV6d1JCakFaby4iLCJzdWIiOiJXRld3QTlYZjZpZXUxLUgwNDBuU0QxRVo3UWxOLTVHbWxob2p4czdMUFJRIiwidGlkIjoiMjVkYTFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzIiwidXRpIjoidlo1MjQySmhNVWFUUktaYVFCRjhBQSIsInZlciI6IjIuMCIsInZtX2FjY2VzcyI6IntcInRlbmFudF9pZFwiOntcInByb2plY3RfaWRcIjogNSwgXCJhY2NvdW50X2lkXCI6IDF9fSJ9.E0pEjbazG1QP5nT7fk3GZ9QjIchxOegBQGWnRN8-xFVSJ61v9-FZ-0fNHCYuMVpWvCLqlAHscITB1EYOt4ezvVdwNhO-TXTFAXGznXD4WRsK_G5KGk1QuV-kYwhvidZsPGQe39KlAJm5BPx1fnoHr4yakD647aspd4p9SAsM_H0l4agVZeAhfBqKHI0-cnLcbGb7mC-pZUB1fJBvwc9OT2gzjmA-2T2Vmv4C33I70oDt-wTYmMyHQ4uItTVkj6JXo6gc4V1APJvtA6fB8iq75J-NZ51MiptVIoocX3fYHuC-FwHpi9AFH-1o06tHN0N_A4Hjf8cyzsG8GBaLLGQblw",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
Exp: 1725629232,
|
||||
Iat: 1725625332,
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
Alg: "RS256",
|
||||
Kid: "H9nj5AOSswMphg1SFx7jaV-lB9w",
|
||||
Typ: "JWT",
|
||||
},
|
||||
}
|
||||
f(r, resultExpected, false)
|
||||
|
||||
// parse ok with scope being slice of strings
|
||||
r = &http.Request{
|
||||
Header: map[string][]string{
|
||||
"Authorization": {
|
||||
"Bearer ewogICJ0eXAiOiJKV1QiLAogICJhbGciOiJSUzI1NiIsCiAgImtpZCI6Ikg5bmo1QU9Tc3dNcGhnMVNGeDdqYVYtbEI5dyIKfQ.ewogICJhdWQiOiI3YTczMTFlNy1iYTdlLTQ5NWUtOTk1ZS1hZjUzNGU3M2MxMTAiLAogICJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vMjVkYTFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzL3YyLjAiLAogICJpYXQiOjE3MjU2MjUzMzIsCiAgIm5iZiI6MTcyNTYyNTMzMiwKICAiZXhwIjoxNzI1NjI5MjMyLAogICJuYW1lIjoiWmFraGFyIEJlc3NhcmFiIiwKICAib2lkIjoiOGI5ZWY2YjMtMWMwMS00YjczLTg0ODItMjRkNmI2NTE1Y2U0IiwKICAicHJlZmVycmVkX3VzZXJuYW1lIjoiei5iZXNzYXJhYkB2aWN0b3JpYW1ldHJpY3MuY29tIiwKICAicmgiOiIwLkFXTUJ6aDdhSlpGbWFFaW5leHNQbTc1ZlEtY1JjM3AtdWw1Sm1WNnZVMDV6d1JCakFaby4iLAogICJzdWIiOiJXRld3QTlYZjZpZXUxLUgwNDBuU0QxRVo3UWxOLTVHbWxob2p4czdMUFJRIiwKICAidGlkIjoiMjVkYTFlY2UtNjY5MS00ODY4LWE3N2ItMWIwZjliYmU1ZjQzIiwKICAidXRpIjoidlo1MjQySmhNVWFUUktaYVFCRjhBQSIsCiAgInZlciI6IjIuMCIsCiAgInZtX2FjY2VzcyI6IntcInRlbmFudF9pZFwiOntcInByb2plY3RfaWRcIjogNSwgXCJhY2NvdW50X2lkXCI6IDF9fSIsCiAgInNjb3BlIjogWyJvcGVuaWQiLCAidm0iXQp9.ZXdvZ0lDSjBlWEFpT2lKS1YxUWlMQW9nSUNKaGJHY2lPaUpTVXpJMU5pSXNDaUFnSW10cFpDSTZJa2c1Ym1vMVFVOVRjM2ROY0dobk1WTkdlRGRxWVZZdGJFSTVkeUlLZlEuLktrUG9qNWJoaDNWcnRyY3RVb0lHaE5vN2hNc2VGT3hESGVEQ2g3MFViV2l2LU5pb1Zia2duZk1CMkhacHN6WGU5WmNmX2FIaURJSVNTYkNTaDlvQnF1aS02OEJDcmplNFJWRkpGZFV6R3V1SmdOTS11YVpBcFJqSFNNZDUxb2RvbHFoUGFHS09URnJXVmlIWlpfVDdXaVNUcV84U3Y1a2x1Y2xMb0hEcU82MU5Na2w0TmRCVnQxM1hjRTBfM243U3VxTDdpaks2dGMwZ2NzcmJ5c3JNdl9jd2VRamZsLU5fV0N0SG40NnhadEhvX0RpZERabzc2TjV1NE52Uk1OZUxNcXZ0YTgzUzhPdzNyUUlhaUFjUUNHYjBqUU5hV2VEQlFzZUZ6SjRyR0h6RjAwZDlqVkNCSHVWRmI5eHNnSnJVUDZ0S05iT2hTeEY1RzBocElVYk5OUQ",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resultExpected = &Token{
|
||||
body: &body{
|
||||
Exp: 1725629232,
|
||||
Iat: 1725625332,
|
||||
VMAccess: &access{
|
||||
Tenant: TenantID{
|
||||
ProjectID: 5,
|
||||
AccountID: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
header: &header{
|
||||
Alg: "RS256",
|
||||
Kid: "H9nj5AOSswMphg1SFx7jaV-lB9w",
|
||||
Typ: "JWT",
|
||||
},
|
||||
}
|
||||
f(r, resultExpected, false)
|
||||
}
|
||||
23
lib/jwt/key.go
Normal file
23
lib/jwt/key.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ParseKey parses key in PEM format.
|
||||
// It returns a *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey, or ed25519.PublicKey.
|
||||
func ParseKey(key []byte) (any, error) {
|
||||
b, _ := pem.Decode(key)
|
||||
if b == nil {
|
||||
return nil, fmt.Errorf("failed to parse key %q: failed to decode PEM block containing public key", key)
|
||||
}
|
||||
|
||||
k, err := x509.ParsePKIXPublicKey(b.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse key %q: %v", key, err)
|
||||
}
|
||||
|
||||
return k, nil
|
||||
}
|
||||
31
lib/jwt/key_test.go
Normal file
31
lib/jwt/key_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package jwt
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseKey_Failure(t *testing.T) {
|
||||
f := func(key string) {
|
||||
t.Helper()
|
||||
|
||||
_, err := ParseKey([]byte(key))
|
||||
if err == nil {
|
||||
t.Fatalf("expecting non-nil error")
|
||||
}
|
||||
}
|
||||
|
||||
key := "invalid_key-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(key)
|
||||
}
|
||||
|
||||
func TestParseKey_Success(t *testing.T) {
|
||||
f := func(key string) {
|
||||
t.Helper()
|
||||
|
||||
_, err := ParseKey([]byte(key))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKey() error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
key := "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(key)
|
||||
}
|
||||
135
lib/jwt/verifier_pool.go
Normal file
135
lib/jwt/verifier_pool.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrSignatureVerificationFailed token signature verification failed
|
||||
ErrSignatureVerificationFailed = fmt.Errorf("failed to verify token signature")
|
||||
// ErrSignatureAlgorithmNotSupported signature algorithm not supported
|
||||
ErrSignatureAlgorithmNotSupported = fmt.Errorf("signature algorithm verification not supported, supported algorithms: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512")
|
||||
)
|
||||
|
||||
// VerifierPool is a pool of verifiers for different algorithms
|
||||
type VerifierPool struct {
|
||||
rsaVerifiers map[string][]Verifier
|
||||
psVerifiers map[string][]Verifier
|
||||
esVerifiers map[string][]Verifier
|
||||
}
|
||||
|
||||
// NewVerifierPool creates a new verifier pool for a set of keys
|
||||
func NewVerifierPool(keys []any) (*VerifierPool, error) {
|
||||
rsaVerifiers := make(map[string][]Verifier)
|
||||
psVerifiers := make(map[string][]Verifier)
|
||||
esVerifiers := make(map[string][]Verifier)
|
||||
|
||||
rsaAlgs := []string{"RS256", "RS384", "RS512"}
|
||||
psAlgs := []string{"PS256", "PS384", "PS512"}
|
||||
|
||||
for _, key := range keys {
|
||||
switch k := key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
for _, alg := range rsaAlgs {
|
||||
verifier, err := newVerifierRS(Algorithm(alg), k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create RSA verifier for algorithm %s: %w", alg, err)
|
||||
}
|
||||
rsaVerifiers[alg] = append(rsaVerifiers[alg], verifier)
|
||||
}
|
||||
|
||||
for _, alg := range psAlgs {
|
||||
verifier, err := newVerifierPS(Algorithm(alg), k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create RSA-PSS verifier for algorithm %s: %w", alg, err)
|
||||
}
|
||||
psVerifiers[alg] = append(psVerifiers[alg], verifier)
|
||||
}
|
||||
|
||||
case *ecdsa.PublicKey:
|
||||
alg := getAlgorithmForKey(k)
|
||||
if alg == "" {
|
||||
return nil, fmt.Errorf("failed to create ECDSA verifier: unsupported key")
|
||||
}
|
||||
|
||||
verifier, err := newVerifierES(alg, k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create ES verifier for algorithm %s: %w", alg, err)
|
||||
}
|
||||
esVerifiers[string(alg)] = append(esVerifiers[string(alg)], verifier)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown key type: %T", key)
|
||||
}
|
||||
}
|
||||
|
||||
return &VerifierPool{
|
||||
rsaVerifiers: rsaVerifiers,
|
||||
psVerifiers: psVerifiers,
|
||||
esVerifiers: esVerifiers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (vp *VerifierPool) getVerifiers(alg string) []Verifier {
|
||||
if len(alg) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch alg[:2] {
|
||||
case "RS":
|
||||
return vp.getRSVerifiers(alg)
|
||||
case "PS":
|
||||
return vp.getPSVerifiers(alg)
|
||||
case "ES":
|
||||
return vp.getESVerifiers(alg)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Verify verifies a token signature by using keys provided to verifier pool
|
||||
func (vp *VerifierPool) Verify(token *Token) error {
|
||||
verifiers := vp.getVerifiers(token.header.Alg)
|
||||
if verifiers == nil {
|
||||
return ErrSignatureAlgorithmNotSupported
|
||||
}
|
||||
|
||||
for _, verifier := range verifiers {
|
||||
err := verifier.Verify(token)
|
||||
if err == nil {
|
||||
// Token verified, returning success immediately
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ErrSignatureVerificationFailed
|
||||
}
|
||||
|
||||
func (vp *VerifierPool) getRSVerifiers(alg string) []Verifier {
|
||||
v, ok := vp.rsaVerifiers[alg]
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vp *VerifierPool) getPSVerifiers(alg string) []Verifier {
|
||||
v, ok := vp.psVerifiers[alg]
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vp *VerifierPool) getESVerifiers(alg string) []Verifier {
|
||||
v, ok := vp.esVerifiers[alg]
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
148
lib/jwt/verifier_pool_test.go
Normal file
148
lib/jwt/verifier_pool_test.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTokenVerify_Failure(t *testing.T) {
|
||||
f := func(auth, key string) {
|
||||
t.Helper()
|
||||
|
||||
token, err := NewToken(auth, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
k, err := ParseKey([]byte(key))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
vp, err := NewVerifierPool([]any{k})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := vp.Verify(token); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.Fatalf("expecting some error")
|
||||
}
|
||||
|
||||
// RS256 - fail
|
||||
auth := "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.oa8LNeYBQz7HLvUEBvId2hDEbTZrHUBp1cnjqvJee45zAZ4Yf_UDMnpLIllJ5wsyyveDoIIJN1a6XbzZFbxCycBbmBycVwpow1k4y8JRvV8otjZTLSWeTChUFSGUnYWIuVrLEAX6DIA_N4QgTQj2rZ9Zjj9HgWhTvkCiuhFqg2Mvw9EjH8b2hEqwc50XubPvm34hkfy2ETK9sLvYyOwlMRwpAevikTVQ5TODgqhWp1NlagSHP7Dyw_Fx2zfrWWbS56k8fO_pcWvGxCaffYfclDLxNBfxypdl_CzZpXu3XoIlzSY7s2XZHlv6aqGom2ppyDGsdmnac5QCNzQzmhMvdA_fake"
|
||||
key := "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// RS384 - fail
|
||||
auth = "Bearer eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.fZPteCKEsuGKsrKQQZvXU7pAykIYCPW23TTXDZtellqc5JwtXwFNy7T6uwYXklqUmcAJouoFqePjET2MNUHug6PdA_4WdP2v287LrOS26Eto0ddWixdR-w69CbKC0VsMRpCFqvZOYBWUuBgZp6IiDyIocaJ8gfH3Cigsj9simd_aKGIPzd5bu_TNzZeLfisw_HYxP-v0qEIeU84HwfszTHzJszTFg_32FdvqyUxZwFXPoLMMpHZlkScDrgpl4ja9krLlmj8kWPMA0kJ0O_5FSWtly7wvHe_L31lHqGRAqeykGXDEsuMccgE-1sCfqiDu6EqeorlXBOXIkUiuIPan9w_fake"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// RS512 - fail
|
||||
auth = "Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.DXQYSz2jx63JROB7Fs9ZD2Sp7b95kzY-vj0VOZczyOSm_j4By42bwkWydxkyfc2jgE4Qmj2Mxq-mNm0BEIChP2Klh4TrmziXQZr1ZzD1KhzoZRqANg4Ngck0_Wm-qynFE8gzPiKhPlgH26PsdCn98-L8oqvqjL40AI92ZgGTDQAQavE0tfFjOamz_LHYXwAd7sJsjaJTCuLbAfzH4F-zKx1WH9fQLEp2cB1kZz6Zaa85xzDk0ndLOtLuVF9HbrMTsvx2xT-a-6sPQ-nwuBcIky4Q2A4_KxOzmfPBOqePiwVJa0Y-icq9xvAwb54WvEk8E_QECwKgrje4c2GjWG2CKA_fake"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// ES256 - fail
|
||||
auth = "Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.llgN8-tHQfakAfsTnPBFqmaPRYC5axXEclk_Ajzp6e_61ASwxOwZvgKDvV4po0HKYLqI3sy89M4ZV9_0SEGuKQ_fake"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// ES384 - fail
|
||||
auth = "Bearer eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.qbA54n3OJ7TZJ4XFq2AwdRFDLe2aZwfR04j0NvE1TbVGNipMCana5NY-yacgNRJKK3RjR2OsoWQGAHg9_R6VhTm3X7VNmwcR9u7L-BCAm1cFBU35X5a-PCuke9mbaY_fake"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+\nPk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii\n1D3jaW6pmGVJFhodzC31cy5sfOYotrzF\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// ES512 - fail
|
||||
auth = "Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.AVVk7ksNokv6xP0Q1JH6Hbj2AnH3z_o-r56fFASSUFw1Rc1YIYHH_UuMwJi9YkEACKZc0lM8VPfp7h3DXaPGuGJSAKfA7Bezou7Kf1LO5zX0uc2mK-GTyxTiZQysU1JRrQ1bX_Ul7ujtI6FxQjhZw7tPAAssJPUnsB2WpavZgXnsPX_fake"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\nPDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\n6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\nAl8G7CqwoJOsW7Kddns=\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// PS256 - fail
|
||||
auth = "Bearer eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.NXHoXwOduNa97qqsLsboHSuZFLT3WZ35A0opO9F_VZUCeLlViVxTwIgTmscaL8qjIqJvFqBn6Yb7PFaaZTfKHvd8q5aOUkN4or2aPajJv7kGADvH6emYmId6wqJU48oh0yhMQZBEB5trWyboUXM6yAsvdU3kFfyHgw4cNDmCY7oYv0LB2T3wiJ6Xi5q8ZjXt_vrilYTDzgwfI0t2Vu01fkXGuezXIyTzLia2saOFRbs6BedyrHXxGJyOvcXCPX2bqh5AQEegp9Q32_p7k6I1X67Dzd2J-2yUVsxQpRWS5ruOVZr4oE5yZBzRpLi4hr5Hwi9rHcSY7AANu-1h3eweqg_fake"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// PS384 - fail
|
||||
auth = "Bearer eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.Wj61agr500y4fB0lrd7AJuW8DXoXF09bMZ1yCiqZl8VKbh9me2RHzEAt0orbPV_rSDdqsgvRn4V2asaodyJjf9A6E8cfNFBRJNMqi5-lpdMP7fs2YF5IVGYzGAkalGVB4QPFGf6xDpNAGk-AsAGGu1Tqpo3jawy2r9IAzoVUs9q8PlNpjKhNK06Ugvy9sxeiEQmYO5gwOrT768mU8_au4InnzXkQdQW_BdYnEZ48udtBDe1r_QQ-dLbHUTW8WnApo6xlONuQErCB7HlpTeDLTcEyAcl_8HmOGupQpMUJCJW1p8D7PCjNSDQFAjr-OOHG7eqH0VGFcglRu95nPdJtHg_fake"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// PS512 - fail
|
||||
auth = "Bearer eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.JoPjCp97dGl4GGEvvaoVt1-SjQr9Qw29nGAl7NGXm9CCBt56dDTaoW_e42sF9XirSnzCztLT4841MbH-MOr7cWiEV-XHCLGNEpeSSZRcNAgnEmqTZSOJiMOHeqDjNfu5jE90qVvGB5lfpIsE3DRo8V5L4f0V4hMq47v6Jk1w1oV8snl-qbvjAfSXfKAfQNPkFwKowdwIpiZP9VDwxPaFDOg9Iczz1BeaB6kOZdvFnlr5YsnXPu_NZruAxZs4OnsE8XS1pnW3pJJVseLDMJt-Ao9zTTOsXI9P6FyKGgkquiyXQQkvBk4EcthUEu4mxkynYal53EIw3bHfsBb6sYUdKQ_fake"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// HS256 - not supported
|
||||
auth = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFBWm9DR3Z1R2JGb2Z0V0h4UVp5UlNRZW4zeVg0VTBHUGxQNW9aT1FTd2MifQ.eyJleHAiOjE2MTA5NzYxODksImlhdCI6MTYxMDk3NTg4OSwiYXV0aF90aW1lIjoxNjEwOTc1ODg5LCJqdGkiOiI5YjE5NDE4Ny02YmI3LTQyNDQtOWQxYi01NTllYWIyZWY3ZjMiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4NDQzL2F1dGgvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNDYwODU5NDEtYjkyYi00NzFhLWIwNWEtOTU5OWNhMjlkYTFlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZ3JhZmFuYSIsInNlc3Npb25fc3RhdGUiOiIxMzc3ZDEwMi03NTJiLTQ0ODYtOTlkYS1jMjA4MjRiODJkMzEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJ2bV9hY2Nlc3MiOnsiZXh0cmFfbGFiZWxzIjp7InByb2plY3QiOiJkZXYiLCJ0ZWFtIjoibW9iaWxlIn0sImV4dHJhX2ZpbHRlcnMiOlsie2Vudj1+XCJwcm9kfGRldlwifSIsInt0ZWFtIT1cInRlc3RcIn0iXSwidGVuYW50X2lkIjp7ImFjY291bnRfaWQiOjEsInByb2plY3RfaWQiOjV9fSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGcgdGciLCJwcm9qZWN0IjoibW9iaWxlIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGciLCJ0ZWFtIjoiZGV2IiwiZ2l2ZW5fbmFtZSI6InRnIiwiZmFtaWx5X25hbWUiOiJ0ZyJ9.Nx9An-sqto8ClmNah8Mi6y16mjB6jk-I1kxQdtP0j0c"
|
||||
key = "key"
|
||||
f(auth, key)
|
||||
}
|
||||
|
||||
func TestTokenVerify_Success(t *testing.T) {
|
||||
f := func(auth, key string) {
|
||||
t.Helper()
|
||||
|
||||
token, err := NewToken(auth, true)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot parse token: %s", err)
|
||||
}
|
||||
|
||||
k, err := ParseKey([]byte(key))
|
||||
if err != nil {
|
||||
t.Fatalf("cannot parse key: %s", err)
|
||||
}
|
||||
vp, err := NewVerifierPool([]any{k})
|
||||
if err != nil {
|
||||
t.Fatalf("NewVerifierPool() error: %s", err)
|
||||
}
|
||||
if err := vp.Verify(token); err != nil {
|
||||
t.Fatalf("Verify() error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// RS256
|
||||
auth := "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.oa8LNeYBQz7HLvUEBvId2hDEbTZrHUBp1cnjqvJee45zAZ4Yf_UDMnpLIllJ5wsyyveDoIIJN1a6XbzZFbxCycBbmBycVwpow1k4y8JRvV8otjZTLSWeTChUFSGUnYWIuVrLEAX6DIA_N4QgTQj2rZ9Zjj9HgWhTvkCiuhFqg2Mvw9EjH8b2hEqwc50XubPvm34hkfy2ETK9sLvYyOwlMRwpAevikTVQ5TODgqhWp1NlagSHP7Dyw_Fx2zfrWWbS56k8fO_pcWvGxCaffYfclDLxNBfxypdl_CzZpXu3XoIlzSY7s2XZHlv6aqGom2ppyDGsdmnac5QCNzQzmhMvdA"
|
||||
key := "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// RS384
|
||||
auth = "Bearer eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.fZPteCKEsuGKsrKQQZvXU7pAykIYCPW23TTXDZtellqc5JwtXwFNy7T6uwYXklqUmcAJouoFqePjET2MNUHug6PdA_4WdP2v287LrOS26Eto0ddWixdR-w69CbKC0VsMRpCFqvZOYBWUuBgZp6IiDyIocaJ8gfH3Cigsj9simd_aKGIPzd5bu_TNzZeLfisw_HYxP-v0qEIeU84HwfszTHzJszTFg_32FdvqyUxZwFXPoLMMpHZlkScDrgpl4ja9krLlmj8kWPMA0kJ0O_5FSWtly7wvHe_L31lHqGRAqeykGXDEsuMccgE-1sCfqiDu6EqeorlXBOXIkUiuIPan9w"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// RS512
|
||||
auth = "Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.DXQYSz2jx63JROB7Fs9ZD2Sp7b95kzY-vj0VOZczyOSm_j4By42bwkWydxkyfc2jgE4Qmj2Mxq-mNm0BEIChP2Klh4TrmziXQZr1ZzD1KhzoZRqANg4Ngck0_Wm-qynFE8gzPiKhPlgH26PsdCn98-L8oqvqjL40AI92ZgGTDQAQavE0tfFjOamz_LHYXwAd7sJsjaJTCuLbAfzH4F-zKx1WH9fQLEp2cB1kZz6Zaa85xzDk0ndLOtLuVF9HbrMTsvx2xT-a-6sPQ-nwuBcIky4Q2A4_KxOzmfPBOqePiwVJa0Y-icq9xvAwb54WvEk8E_QECwKgrje4c2GjWG2CKA"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// ES256
|
||||
auth = "Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.llgN8-tHQfakAfsTnPBFqmaPRYC5axXEclk_Ajzp6e_61ASwxOwZvgKDvV4po0HKYLqI3sy89M4ZV9_0SEGuKQ"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// ES384
|
||||
auth = "Bearer eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.qbA54n3OJ7TZJ4XFq2AwdRFDLe2aZwfR04j0NvE1TbVGNipMCana5NY-yacgNRJKK3RjR2OsoWQGAHg9_R6VhTm3X7VNmwcR9u7L-BCAm1cFBU35X5a-PCuke9mbaY7c"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+\nPk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii\n1D3jaW6pmGVJFhodzC31cy5sfOYotrzF\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// ES512
|
||||
auth = "Bearer eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.AVVk7ksNokv6xP0Q1JH6Hbj2AnH3z_o-r56fFASSUFw1Rc1YIYHH_UuMwJi9YkEACKZc0lM8VPfp7h3DXaPGuGJSAKfA7Bezou7Kf1LO5zX0uc2mK-GTyxTiZQysU1JRrQ1bX_Ul7ujtI6FxQjhZw7tPAAssJPUnsB2WpavZgXnsPXrW"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\nPDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\n6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\nAl8G7CqwoJOsW7Kddns=\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// PS256
|
||||
auth = "Bearer eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.NXHoXwOduNa97qqsLsboHSuZFLT3WZ35A0opO9F_VZUCeLlViVxTwIgTmscaL8qjIqJvFqBn6Yb7PFaaZTfKHvd8q5aOUkN4or2aPajJv7kGADvH6emYmId6wqJU48oh0yhMQZBEB5trWyboUXM6yAsvdU3kFfyHgw4cNDmCY7oYv0LB2T3wiJ6Xi5q8ZjXt_vrilYTDzgwfI0t2Vu01fkXGuezXIyTzLia2saOFRbs6BedyrHXxGJyOvcXCPX2bqh5AQEegp9Q32_p7k6I1X67Dzd2J-2yUVsxQpRWS5ruOVZr4oE5yZBzRpLi4hr5Hwi9rHcSY7AANu-1h3eweqg"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// PS384
|
||||
auth = "Bearer eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.Wj61agr500y4fB0lrd7AJuW8DXoXF09bMZ1yCiqZl8VKbh9me2RHzEAt0orbPV_rSDdqsgvRn4V2asaodyJjf9A6E8cfNFBRJNMqi5-lpdMP7fs2YF5IVGYzGAkalGVB4QPFGf6xDpNAGk-AsAGGu1Tqpo3jawy2r9IAzoVUs9q8PlNpjKhNK06Ugvy9sxeiEQmYO5gwOrT768mU8_au4InnzXkQdQW_BdYnEZ48udtBDe1r_QQ-dLbHUTW8WnApo6xlONuQErCB7HlpTeDLTcEyAcl_8HmOGupQpMUJCJW1p8D7PCjNSDQFAjr-OOHG7eqH0VGFcglRu95nPdJtHg"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
|
||||
// PS512
|
||||
auth = "Bearer eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidm1fYWNjZXNzIjp7fX0.JoPjCp97dGl4GGEvvaoVt1-SjQr9Qw29nGAl7NGXm9CCBt56dDTaoW_e42sF9XirSnzCztLT4841MbH-MOr7cWiEV-XHCLGNEpeSSZRcNAgnEmqTZSOJiMOHeqDjNfu5jE90qVvGB5lfpIsE3DRo8V5L4f0V4hMq47v6Jk1w1oV8snl-qbvjAfSXfKAfQNPkFwKowdwIpiZP9VDwxPaFDOg9Iczz1BeaB6kOZdvFnlr5YsnXPu_NZruAxZs4OnsE8XS1pnW3pJJVseLDMJt-Ao9zTTOsXI9P6FyKGgkquiyXQQkvBk4EcthUEu4mxkynYal53EIw3bHfsBb6sYUdKQ"
|
||||
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\nkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\ncKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\nmwIDAQAB\n-----END PUBLIC KEY-----"
|
||||
f(auth, key)
|
||||
}
|
||||
Reference in New Issue
Block a user