github.com/nats-io/jwt/v2@v2.5.6/v1compat/activation_claims.go (about)

     1  /*
     2   * Copyright 2018 The NATS Authors
     3   * Licensed under the Apache License, Version 2.0 (the "License");
     4   * you may not use this file except in compliance with the License.
     5   * You may obtain a copy of the License at
     6   *
     7   * http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15  
    16  package jwt
    17  
    18  import (
    19  	"crypto/sha256"
    20  	"encoding/base32"
    21  	"errors"
    22  	"fmt"
    23  	"strings"
    24  
    25  	"github.com/nats-io/nkeys"
    26  )
    27  
    28  // Activation defines the custom parts of an activation claim
    29  type Activation struct {
    30  	ImportSubject Subject    `json:"subject,omitempty"`
    31  	ImportType    ExportType `json:"type,omitempty"`
    32  	Limits
    33  }
    34  
    35  // IsService returns true if an Activation is for a service
    36  func (a *Activation) IsService() bool {
    37  	return a.ImportType == Service
    38  }
    39  
    40  // IsStream returns true if an Activation is for a stream
    41  func (a *Activation) IsStream() bool {
    42  	return a.ImportType == Stream
    43  }
    44  
    45  // Validate checks the exports and limits in an activation JWT
    46  func (a *Activation) Validate(vr *ValidationResults) {
    47  	if !a.IsService() && !a.IsStream() {
    48  		vr.AddError("invalid export type: %q", a.ImportType)
    49  	}
    50  
    51  	if a.IsService() {
    52  		if a.ImportSubject.HasWildCards() {
    53  			vr.AddError("services cannot have wildcard subject: %q", a.ImportSubject)
    54  		}
    55  	}
    56  
    57  	a.ImportSubject.Validate(vr)
    58  	a.Limits.Validate(vr)
    59  }
    60  
    61  // ActivationClaims holds the data specific to an activation JWT
    62  type ActivationClaims struct {
    63  	ClaimsData
    64  	Activation `json:"nats,omitempty"`
    65  	// IssuerAccount stores the public key for the account the issuer represents.
    66  	// When set, the claim was issued by a signing key.
    67  	IssuerAccount string `json:"issuer_account,omitempty"`
    68  }
    69  
    70  // NewActivationClaims creates a new activation claim with the provided sub
    71  func NewActivationClaims(subject string) *ActivationClaims {
    72  	if subject == "" {
    73  		return nil
    74  	}
    75  	ac := &ActivationClaims{}
    76  	ac.Subject = subject
    77  	return ac
    78  }
    79  
    80  // Encode turns an activation claim into a JWT strimg
    81  func (a *ActivationClaims) Encode(pair nkeys.KeyPair) (string, error) {
    82  	if !nkeys.IsValidPublicAccountKey(a.ClaimsData.Subject) {
    83  		return "", errors.New("expected subject to be an account")
    84  	}
    85  	a.ClaimsData.Type = ActivationClaim
    86  	return a.ClaimsData.Encode(pair, a)
    87  }
    88  
    89  // DecodeActivationClaims tries to create an activation claim from a JWT string
    90  func DecodeActivationClaims(token string) (*ActivationClaims, error) {
    91  	v := ActivationClaims{}
    92  	if err := Decode(token, &v); err != nil {
    93  		return nil, err
    94  	}
    95  	return &v, nil
    96  }
    97  
    98  // Payload returns the activation specific part of the JWT
    99  func (a *ActivationClaims) Payload() interface{} {
   100  	return a.Activation
   101  }
   102  
   103  // Validate checks the claims
   104  func (a *ActivationClaims) Validate(vr *ValidationResults) {
   105  	a.validateWithTimeChecks(vr, true)
   106  }
   107  
   108  // Validate checks the claims
   109  func (a *ActivationClaims) validateWithTimeChecks(vr *ValidationResults, timeChecks bool) {
   110  	if timeChecks {
   111  		a.ClaimsData.Validate(vr)
   112  	}
   113  	a.Activation.Validate(vr)
   114  	if a.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(a.IssuerAccount) {
   115  		vr.AddError("account_id is not an account public key")
   116  	}
   117  }
   118  
   119  // ExpectedPrefixes defines the types that can sign an activation jwt, account and oeprator
   120  func (a *ActivationClaims) ExpectedPrefixes() []nkeys.PrefixByte {
   121  	return []nkeys.PrefixByte{nkeys.PrefixByteAccount, nkeys.PrefixByteOperator}
   122  }
   123  
   124  // Claims returns the generic part of the JWT
   125  func (a *ActivationClaims) Claims() *ClaimsData {
   126  	return &a.ClaimsData
   127  }
   128  
   129  func (a *ActivationClaims) String() string {
   130  	return a.ClaimsData.String(a)
   131  }
   132  
   133  // HashID returns a hash of the claims that can be used to identify it.
   134  // The hash is calculated by creating a string with
   135  // issuerPubKey.subjectPubKey.<subject> and constructing the sha-256 hash and base32 encoding that.
   136  // <subject> is the exported subject, minus any wildcards, so foo.* becomes foo.
   137  // the one special case is that if the export start with "*" or is ">" the <subject> "_"
   138  func (a *ActivationClaims) HashID() (string, error) {
   139  
   140  	if a.Issuer == "" || a.Subject == "" || a.ImportSubject == "" {
   141  		return "", fmt.Errorf("not enough data in the activaion claims to create a hash")
   142  	}
   143  
   144  	subject := cleanSubject(string(a.ImportSubject))
   145  	base := fmt.Sprintf("%s.%s.%s", a.Issuer, a.Subject, subject)
   146  	h := sha256.New()
   147  	h.Write([]byte(base))
   148  	sha := h.Sum(nil)
   149  	hash := base32.StdEncoding.EncodeToString(sha)
   150  
   151  	return hash, nil
   152  }
   153  
   154  func cleanSubject(subject string) string {
   155  	split := strings.Split(subject, ".")
   156  	cleaned := ""
   157  
   158  	for i, tok := range split {
   159  		if tok == "*" || tok == ">" {
   160  			if i == 0 {
   161  				cleaned = "_"
   162  				break
   163  			}
   164  
   165  			cleaned = strings.Join(split[:i], ".")
   166  			break
   167  		}
   168  	}
   169  	if cleaned == "" {
   170  		cleaned = subject
   171  	}
   172  	return cleaned
   173  }