mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-08 04:20:59 -05:00
ddef292623
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.10.22 to 2.10.24. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.10.22...v2.10.24) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
300 lines
7.5 KiB
Go
300 lines
7.5 KiB
Go
/*
|
|
* Copyright 2018-2024 The NATS Authors
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package jwt
|
|
|
|
import (
|
|
"crypto/sha512"
|
|
"encoding/base32"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/nats-io/nkeys"
|
|
)
|
|
|
|
// ClaimType is used to indicate the type of JWT being stored in a Claim
|
|
type ClaimType string
|
|
|
|
const (
|
|
// OperatorClaim is the type of an operator JWT
|
|
OperatorClaim = "operator"
|
|
// AccountClaim is the type of an Account JWT
|
|
AccountClaim = "account"
|
|
// UserClaim is the type of an user JWT
|
|
UserClaim = "user"
|
|
// ActivationClaim is the type of an activation JWT
|
|
ActivationClaim = "activation"
|
|
// AuthorizationRequestClaim is the type of an auth request claim JWT
|
|
AuthorizationRequestClaim = "authorization_request"
|
|
// AuthorizationResponseClaim is the response for an auth request
|
|
AuthorizationResponseClaim = "authorization_response"
|
|
// GenericClaim is a type that doesn't match Operator/Account/User/ActionClaim
|
|
GenericClaim = "generic"
|
|
)
|
|
|
|
func IsGenericClaimType(s string) bool {
|
|
switch s {
|
|
case OperatorClaim:
|
|
fallthrough
|
|
case AccountClaim:
|
|
fallthrough
|
|
case UserClaim:
|
|
fallthrough
|
|
case AuthorizationRequestClaim:
|
|
fallthrough
|
|
case AuthorizationResponseClaim:
|
|
fallthrough
|
|
case ActivationClaim:
|
|
return false
|
|
case GenericClaim:
|
|
return true
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
// SignFn is used in an external sign environment. The function should be
|
|
// able to locate the private key for the specified pub key specified and sign the
|
|
// specified data returning the signature as generated.
|
|
type SignFn func(pub string, data []byte) ([]byte, error)
|
|
|
|
// Claims is a JWT claims
|
|
type Claims interface {
|
|
Claims() *ClaimsData
|
|
Encode(kp nkeys.KeyPair) (string, error)
|
|
EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error)
|
|
ExpectedPrefixes() []nkeys.PrefixByte
|
|
Payload() interface{}
|
|
String() string
|
|
Validate(vr *ValidationResults)
|
|
ClaimType() ClaimType
|
|
|
|
verify(payload string, sig []byte) bool
|
|
updateVersion()
|
|
}
|
|
|
|
type GenericFields struct {
|
|
Tags TagList `json:"tags,omitempty"`
|
|
Type ClaimType `json:"type,omitempty"`
|
|
Version int `json:"version,omitempty"`
|
|
}
|
|
|
|
// ClaimsData is the base struct for all claims
|
|
type ClaimsData struct {
|
|
Audience string `json:"aud,omitempty"`
|
|
Expires int64 `json:"exp,omitempty"`
|
|
ID string `json:"jti,omitempty"`
|
|
IssuedAt int64 `json:"iat,omitempty"`
|
|
Issuer string `json:"iss,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
NotBefore int64 `json:"nbf,omitempty"`
|
|
Subject string `json:"sub,omitempty"`
|
|
}
|
|
|
|
// Prefix holds the prefix byte for an NKey
|
|
type Prefix struct {
|
|
nkeys.PrefixByte
|
|
}
|
|
|
|
func encodeToString(d []byte) string {
|
|
return base64.RawURLEncoding.EncodeToString(d)
|
|
}
|
|
|
|
func decodeString(s string) ([]byte, error) {
|
|
return base64.RawURLEncoding.DecodeString(s)
|
|
}
|
|
|
|
func serialize(v interface{}) (string, error) {
|
|
j, err := json.Marshal(v)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return encodeToString(j), nil
|
|
}
|
|
|
|
func (c *ClaimsData) doEncode(header *Header, kp nkeys.KeyPair, claim Claims, fn SignFn) (string, error) {
|
|
if header == nil {
|
|
return "", errors.New("header is required")
|
|
}
|
|
|
|
if kp == nil {
|
|
return "", errors.New("keypair is required")
|
|
}
|
|
|
|
if c != claim.Claims() {
|
|
return "", errors.New("claim and claim data do not match")
|
|
}
|
|
|
|
if c.Subject == "" {
|
|
return "", errors.New("subject is not set")
|
|
}
|
|
|
|
h, err := serialize(header)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
issuerBytes, err := kp.PublicKey()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
prefixes := claim.ExpectedPrefixes()
|
|
if prefixes != nil {
|
|
ok := false
|
|
for _, p := range prefixes {
|
|
switch p {
|
|
case nkeys.PrefixByteAccount:
|
|
if nkeys.IsValidPublicAccountKey(issuerBytes) {
|
|
ok = true
|
|
}
|
|
case nkeys.PrefixByteOperator:
|
|
if nkeys.IsValidPublicOperatorKey(issuerBytes) {
|
|
ok = true
|
|
}
|
|
case nkeys.PrefixByteServer:
|
|
if nkeys.IsValidPublicServerKey(issuerBytes) {
|
|
ok = true
|
|
}
|
|
case nkeys.PrefixByteCluster:
|
|
if nkeys.IsValidPublicClusterKey(issuerBytes) {
|
|
ok = true
|
|
}
|
|
case nkeys.PrefixByteUser:
|
|
if nkeys.IsValidPublicUserKey(issuerBytes) {
|
|
ok = true
|
|
}
|
|
}
|
|
}
|
|
if !ok {
|
|
return "", fmt.Errorf("unable to validate expected prefixes - %v", prefixes)
|
|
}
|
|
}
|
|
|
|
c.Issuer = issuerBytes
|
|
c.IssuedAt = time.Now().UTC().Unix()
|
|
c.ID = "" // to create a repeatable hash
|
|
c.ID, err = c.hash()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
claim.updateVersion()
|
|
|
|
payload, err := serialize(claim)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
toSign := fmt.Sprintf("%s.%s", h, payload)
|
|
eSig := ""
|
|
if header.Algorithm == AlgorithmNkeyOld {
|
|
return "", errors.New(AlgorithmNkeyOld + " not supported to write jwtV2")
|
|
} else if header.Algorithm == AlgorithmNkey {
|
|
var sig []byte
|
|
if fn != nil {
|
|
pk, err := kp.PublicKey()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sig, err = fn(pk, []byte(toSign))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
} else {
|
|
sig, err = kp.Sign([]byte(toSign))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
eSig = encodeToString(sig)
|
|
} else {
|
|
return "", errors.New(header.Algorithm + " not supported to write jwtV2")
|
|
}
|
|
// hash need no padding
|
|
return fmt.Sprintf("%s.%s", toSign, eSig), nil
|
|
}
|
|
|
|
func (c *ClaimsData) hash() (string, error) {
|
|
j, err := json.Marshal(c)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
h := sha512.New512_256()
|
|
h.Write(j)
|
|
return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(h.Sum(nil)), nil
|
|
}
|
|
|
|
// Encode encodes a claim into a JWT token. The claim is signed with the
|
|
// provided nkey's private key
|
|
func (c *ClaimsData) encode(kp nkeys.KeyPair, payload Claims, fn SignFn) (string, error) {
|
|
return c.doEncode(&Header{TokenTypeJwt, AlgorithmNkey}, kp, payload, fn)
|
|
}
|
|
|
|
// Returns a JSON representation of the claim
|
|
func (c *ClaimsData) String(claim interface{}) string {
|
|
j, err := json.MarshalIndent(claim, "", " ")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(j)
|
|
}
|
|
|
|
func parseClaims(s string, target Claims) error {
|
|
h, err := decodeString(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(h, &target)
|
|
}
|
|
|
|
// Verify verifies that the encoded payload was signed by the
|
|
// provided public key. Verify is called automatically with
|
|
// the claims portion of the token and the public key in the claim.
|
|
// Client code need to insure that the public key in the
|
|
// claim is trusted.
|
|
func (c *ClaimsData) verify(payload string, sig []byte) bool {
|
|
// decode the public key
|
|
kp, err := nkeys.FromPublicKey(c.Issuer)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if err := kp.Verify([]byte(payload), sig); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Validate checks a claim to make sure it is valid. Validity checks
|
|
// include expiration and not before constraints.
|
|
func (c *ClaimsData) Validate(vr *ValidationResults) {
|
|
now := time.Now().UTC().Unix()
|
|
if c.Expires > 0 && now > c.Expires {
|
|
vr.AddTimeCheck("claim is expired")
|
|
}
|
|
|
|
if c.NotBefore > 0 && c.NotBefore > now {
|
|
vr.AddTimeCheck("claim is not yet valid")
|
|
}
|
|
}
|
|
|
|
// IsSelfSigned returns true if the claims issuer is the subject
|
|
func (c *ClaimsData) IsSelfSigned() bool {
|
|
return c.Issuer == c.Subject
|
|
}
|