Initial QSfera import
This commit is contained in:
+162
@@ -0,0 +1,162 @@
|
||||
package jwx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SignClaims signs the given claims using a given key and a method
|
||||
func SignClaims(claims jwt.Claims, key interface{}, method jwt.SigningMethod) (string, error) {
|
||||
token := jwt.NewWithClaims(method, claims)
|
||||
return token.SignedString(key)
|
||||
}
|
||||
|
||||
// DecodeAccessTokenHeader decodes the header of the accessToken
|
||||
func DecodeAccessTokenHeader(token string) (*DecodedAccessTokenHeader, error) {
|
||||
const errMessage = "could not decode access token header"
|
||||
token = strings.Replace(token, "Bearer ", "", 1)
|
||||
headerString := strings.Split(token, ".")
|
||||
decodedData, err := base64.RawStdEncoding.DecodeString(headerString[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
|
||||
result := &DecodedAccessTokenHeader{}
|
||||
err = json.Unmarshal(decodedData, result)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func toBigInt(v string) (*big.Int, error) {
|
||||
decRes, err := base64.RawURLEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := big.NewInt(0)
|
||||
res.SetBytes(decRes)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
var (
|
||||
curves = map[string]elliptic.Curve{
|
||||
"P-224": elliptic.P224(),
|
||||
"P-256": elliptic.P256(),
|
||||
"P-384": elliptic.P384(),
|
||||
"P-521": elliptic.P521(),
|
||||
}
|
||||
)
|
||||
|
||||
func decodeECDSAPublicKey(x, y, crv *string) (*ecdsa.PublicKey, error) {
|
||||
const errMessage = "could not decode public key"
|
||||
|
||||
xInt, err := toBigInt(*x)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
|
||||
yInt, err := toBigInt(*y)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
var c elliptic.Curve
|
||||
var ok bool
|
||||
if c, ok = curves[*crv]; !ok {
|
||||
return nil, errors.Wrap(fmt.Errorf("unknown curve alg: %s", *crv), errMessage)
|
||||
}
|
||||
return &ecdsa.PublicKey{X: xInt, Y: yInt, Curve: c}, nil
|
||||
}
|
||||
|
||||
func decodeRSAPublicKey(e, n *string) (*rsa.PublicKey, error) {
|
||||
const errMessage = "could not decode public key"
|
||||
|
||||
nInt, err := toBigInt(*n)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
|
||||
decE, err := base64.RawURLEncoding.DecodeString(*e)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
|
||||
var eBytes []byte
|
||||
if len(decE) < 8 {
|
||||
eBytes = make([]byte, 8-len(decE), 8)
|
||||
eBytes = append(eBytes, decE...)
|
||||
} else {
|
||||
eBytes = decE
|
||||
}
|
||||
|
||||
eReader := bytes.NewReader(eBytes)
|
||||
var eInt uint64
|
||||
err = binary.Read(eReader, binary.BigEndian, &eInt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
|
||||
pKey := rsa.PublicKey{N: nInt, E: int(eInt)}
|
||||
return &pKey, nil
|
||||
}
|
||||
|
||||
// DecodeAccessTokenRSACustomClaims decodes string access token into jwt.Token
|
||||
func DecodeAccessTokenRSACustomClaims(accessToken string, e, n *string, customClaims jwt.Claims) (*jwt.Token, error) {
|
||||
const errMessage = "could not decode accessToken with custom claims"
|
||||
accessToken = strings.Replace(accessToken, "Bearer ", "", 1)
|
||||
|
||||
rsaPublicKey, err := decodeRSAPublicKey(e, n)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
|
||||
token2, err := jwt.ParseWithClaims(accessToken, customClaims, func(token *jwt.Token) (interface{}, error) {
|
||||
// Don't forget to validate the alg is what you expect:
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return rsaPublicKey, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
return token2, nil
|
||||
}
|
||||
|
||||
// DecodeAccessTokenECDSACustomClaims decodes string access token into jwt.Token
|
||||
func DecodeAccessTokenECDSACustomClaims(accessToken string, x, y, crv *string, customClaims jwt.Claims) (*jwt.Token, error) {
|
||||
const errMessage = "could not decode accessToken"
|
||||
accessToken = strings.Replace(accessToken, "Bearer ", "", 1)
|
||||
|
||||
publicKey, err := decodeECDSAPublicKey(x, y, crv)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
|
||||
token2, err := jwt.ParseWithClaims(accessToken, customClaims, func(token *jwt.Token) (interface{}, error) {
|
||||
// Don't forget to validate the alg is what you expect:
|
||||
if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return publicKey, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, errMessage)
|
||||
}
|
||||
return token2, nil
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package jwx
|
||||
|
||||
import jwt "github.com/golang-jwt/jwt/v5"
|
||||
|
||||
// DecodedAccessTokenHeader is the decoded header from the access token
|
||||
type DecodedAccessTokenHeader struct {
|
||||
Alg string `json:"alg"`
|
||||
Typ string `json:"typ"`
|
||||
Kid string `json:"kid"`
|
||||
}
|
||||
|
||||
// Claims served by keycloak inside the accessToken
|
||||
type Claims struct {
|
||||
jwt.RegisteredClaims
|
||||
Typ string `json:"typ,omitempty"`
|
||||
Azp string `json:"azp,omitempty"`
|
||||
AuthTime int `json:"auth_time,omitempty"`
|
||||
SessionState string `json:"session_state,omitempty"`
|
||||
Acr string `json:"acr,omitempty"`
|
||||
AllowedOrigins []string `json:"allowed-origins,omitempty"`
|
||||
RealmAccess RealmAccess `json:"realm_access,omitempty"`
|
||||
ResourceAccess ResourceAccess `json:"resource_access,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
EmailVerified bool `json:"email_verified,omitempty"`
|
||||
Address Address `json:"address,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||
GivenName string `json:"given_name,omitempty"`
|
||||
FamilyName string `json:"family_name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
ClientHost string `json:"clientHost,omitempty"`
|
||||
ClientIP string `json:"clientAddress,omitempty"`
|
||||
}
|
||||
|
||||
// Address TODO what fields does any address have?
|
||||
type Address struct {
|
||||
}
|
||||
|
||||
// RealmAccess holds roles of the user
|
||||
type RealmAccess struct {
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
// ResourceAccess holds TODO: What does it hold?
|
||||
type ResourceAccess struct {
|
||||
RealmManagement RealmManagement `json:"realm-management,omitempty"`
|
||||
Account Account `json:"account,omitempty"`
|
||||
}
|
||||
|
||||
// RealmManagement holds TODO: What does it hold?
|
||||
type RealmManagement struct {
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
// Account holds TODO: What does it hold?
|
||||
type Account struct {
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user