Skip to content

Commit

Permalink
ini
Browse files Browse the repository at this point in the history
  • Loading branch information
Stets Dmitriy committed Dec 25, 2023
1 parent 9368a5c commit 0b270ab
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
67 changes: 67 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
linters:
enable-all: true
disable:
- gochecknoglobals
- gofumpt
- exhaustivestruct
- exhaustruct
- wrapcheck
- goerr113
- gci
- godox
- varnamelen
- golint
- ireturn
- nosnakecase
linters-settings:
tagliatelle:
case:
use-field-name: false
rules:
json: snake
yaml: kebab
wsl:
# See https://github.com/bombsimon/wsl/blob/master/doc/configuration.md for
# documentation of available settings.
allow-assign-and-anything: false
allow-assign-and-call: true
allow-cuddle-declarations: false
allow-multiline-assign: true
allow-separated-leading-comment: false
allow-trailing-comment: false
force-case-trailing-whitespace: 0
force-err-cuddling: false
force-short-decl-cuddling: false
strict-append: true
godox:
keywords:
- BUG
- FIX
- FIXME
- TODO
- bug
- fixme
- todo
- fix
gomnd:
settings:
mnd:
checks: [ argument,case,condition,operation,return,assign ]
ignored-numbers: [ 1,2,3,10,64,512 ]
funlen:
# Checks the number of lines in a function.
# If lower than 0, disable the check.
# Default: 60
lines: 75
revive:
rules:
- name: var-naming
disabled: true
stylecheck:
checks: [ "all", "-ST1003" ]
run:
modules-download-mode: vendor
skip-dirs:
- "api"
skip-files:
- "_test.go"
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.PHONY: test
test:
go test -v -race -cover -bench=. ./...

lint-local:
golangci-lint run
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# jwks
# jwks
14 changes: 14 additions & 0 deletions erx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package jwks

import "errors"

var (
ErrJWKAlgNotSupported = errors.New("jwk algorithm type not supported")
ErrReqComponents = errors.New("required component not found to create public key")
ErrReqKid = errors.New("kid component not found in provided token")
ErrKidConvert = errors.New("kid has wrong type")
ErrReqAlg = errors.New("alg component not found in provided token")
ErrAlgConvert = errors.New("alg has wrong type")
ErrPKNotFound = errors.New("jwk not found for token kid")
ErrAlgNotSupported = errors.New("alg from token not supported")
)
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/stetsd/jwks

go 1.21.5

require github.com/golang-jwt/jwt/v5 v5.2.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
124 changes: 124 additions & 0 deletions jwks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package jwks

import (
"encoding/json"
"fmt"
"sync"

"github.com/golang-jwt/jwt/v5"
)

// JWKS - JSON web key set.
type JWKS struct {
rwm sync.RWMutex
keys map[string]parsedJWK
}

type BaseJWKS struct {
Keys []JWK `json:"keys"`
}

// JWK - JSON web key, for more information see there https://openid.net/specs/draft-jones-json-web-key-03.html.
type JWK struct {
// The alg member identifies the cryptographic algorithm family used with the key.
Alg string `json:"alg"`
// The exp member contains the exponent value for the RSA public key.
// It is represented as the base64url encoding of the value's big endian representation.
Exp string `json:"e"`
// The kid (Key ID) member can be used to match a specific key. This can be used, for instance,
// to choose among a set of keys within the JWK during key rollover.
Kid string `json:"kid"`
// The "kty" (key type) parameter identifies the cryptographic algorithm
// family used with the key, such as "RSA" or "EC".
Kty string `json:"kty"`
// The mod member contains the modulus value for the RSA public key.
Mod string `json:"n"`
// The use member identifies the intended use of the key.
// Values defined by this specification are sig (signature) and enc (encryption).
// Other values MAY be used. The use value is case sensitive. This member is OPTIONAL.
Use string `json:"use"`
}

type parsedJWK struct {
pk any
alg string
use string
}

func NewFromJSONString(jwksString string) (*JWKS, error) {
var rawJWKS BaseJWKS

err := json.Unmarshal([]byte(jwksString), &rawJWKS)
if err != nil {
return nil, err
}

result := &JWKS{
keys: make(map[string]parsedJWK, len(rawJWKS.Keys)),
}

for _, key := range rawJWKS.Keys {
var pk any

switch key.Kty {
case ktyRSA:
pk, err = key.RSA()
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("%w: %q", ErrJWKAlgNotSupported, key.Kty)
}

result.keys[key.Kid] = parsedJWK{
alg: key.Alg,
use: key.Use,
pk: pk,
}

}

return nil, nil
}

func (j *JWKS) KeyFunc(token *jwt.Token) (interface{}, error) {
kid, ok := token.Header["kid"]
if !ok {
return nil, ErrReqKid
}

kidRes, ok := kid.(string)
if !ok {
return nil, ErrKidConvert
}

alg, ok := token.Header["alg"]
if !ok {
return nil, ErrReqAlg
}

algRes, ok := alg.(string)
if !ok {
return nil, ErrAlgConvert
}

return j.getPublicKey(kidRes, algRes)
}

func (j *JWKS) getPublicKey(kid, alg string) (interface{}, error) {
j.rwm.RLock()

pk, ok := j.keys[kid]

j.rwm.RUnlock()

if !ok {
return nil, ErrPKNotFound
}

if pk.alg != "" && pk.alg != alg {
return nil, ErrAlgNotSupported
}

return pk.pk, nil
}
35 changes: 35 additions & 0 deletions rsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package jwks

import (
"crypto/rsa"
"math/big"
)

const (
// ktyRSA (key type) parameter identifies the cryptographic algorithm
// It located in JWT header
ktyRSA = "RSA"
)

// RSA try to convert JWK to RSA public key
func (t *JWK) RSA() (*rsa.PublicKey, error) {
if t.Exp == "" || t.Mod == "" {
return nil, ErrReqComponents
}

exp, err := componentToBase64(t.Exp)
if err != nil {
return nil, err
}
mod, err := componentToBase64(t.Mod)
if err != nil {
return nil, err
}

result := &rsa.PublicKey{}

result.N = big.NewInt(0).SetBytes(mod)
result.E = int(big.NewInt(0).SetBytes(exp).Uint64())

return result, nil
}
12 changes: 12 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package jwks

import (
"encoding/base64"
"strings"
)

func componentToBase64(component string) ([]byte, error) {
component = strings.TrimRight(component, "=")

return base64.RawURLEncoding.DecodeString(component)
}

0 comments on commit 0b270ab

Please sign in to comment.