github.com/nats-io/jwt/v2@v2.5.6/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:"kind,omitempty"` 32 // IssuerAccount stores the public key for the account the issuer represents. 33 // When set, the claim was issued by a signing key. 34 IssuerAccount string `json:"issuer_account,omitempty"` 35 GenericFields 36 } 37 38 // IsService returns true if an Activation is for a service 39 func (a *Activation) IsService() bool { 40 return a.ImportType == Service 41 } 42 43 // IsStream returns true if an Activation is for a stream 44 func (a *Activation) IsStream() bool { 45 return a.ImportType == Stream 46 } 47 48 // Validate checks the exports and limits in an activation JWT 49 func (a *Activation) Validate(vr *ValidationResults) { 50 if !a.IsService() && !a.IsStream() { 51 vr.AddError("invalid import type: %q", a.ImportType) 52 } 53 54 a.ImportSubject.Validate(vr) 55 } 56 57 // ActivationClaims holds the data specific to an activation JWT 58 type ActivationClaims struct { 59 ClaimsData 60 Activation `json:"nats,omitempty"` 61 } 62 63 // NewActivationClaims creates a new activation claim with the provided sub 64 func NewActivationClaims(subject string) *ActivationClaims { 65 if subject == "" { 66 return nil 67 } 68 ac := &ActivationClaims{} 69 ac.Subject = subject 70 return ac 71 } 72 73 // Encode turns an activation claim into a JWT strimg 74 func (a *ActivationClaims) Encode(pair nkeys.KeyPair) (string, error) { 75 if !nkeys.IsValidPublicAccountKey(a.ClaimsData.Subject) { 76 return "", errors.New("expected subject to be an account") 77 } 78 a.Type = ActivationClaim 79 return a.ClaimsData.encode(pair, a) 80 } 81 82 // DecodeActivationClaims tries to create an activation claim from a JWT string 83 func DecodeActivationClaims(token string) (*ActivationClaims, error) { 84 claims, err := Decode(token) 85 if err != nil { 86 return nil, err 87 } 88 ac, ok := claims.(*ActivationClaims) 89 if !ok { 90 return nil, errors.New("not activation claim") 91 } 92 return ac, nil 93 } 94 95 // Payload returns the activation specific part of the JWT 96 func (a *ActivationClaims) Payload() interface{} { 97 return a.Activation 98 } 99 100 // Validate checks the claims 101 func (a *ActivationClaims) Validate(vr *ValidationResults) { 102 a.validateWithTimeChecks(vr, true) 103 } 104 105 // Validate checks the claims 106 func (a *ActivationClaims) validateWithTimeChecks(vr *ValidationResults, timeChecks bool) { 107 if timeChecks { 108 a.ClaimsData.Validate(vr) 109 } 110 a.Activation.Validate(vr) 111 if a.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(a.IssuerAccount) { 112 vr.AddError("account_id is not an account public key") 113 } 114 } 115 116 func (a *ActivationClaims) ClaimType() ClaimType { 117 return a.Type 118 } 119 120 func (a *ActivationClaims) updateVersion() { 121 a.GenericFields.Version = libVersion 122 } 123 124 // ExpectedPrefixes defines the types that can sign an activation jwt, account and oeprator 125 func (a *ActivationClaims) ExpectedPrefixes() []nkeys.PrefixByte { 126 return []nkeys.PrefixByte{nkeys.PrefixByteAccount, nkeys.PrefixByteOperator} 127 } 128 129 // Claims returns the generic part of the JWT 130 func (a *ActivationClaims) Claims() *ClaimsData { 131 return &a.ClaimsData 132 } 133 134 func (a *ActivationClaims) String() string { 135 return a.ClaimsData.String(a) 136 } 137 138 // HashID returns a hash of the claims that can be used to identify it. 139 // The hash is calculated by creating a string with 140 // issuerPubKey.subjectPubKey.<subject> and constructing the sha-256 hash and base32 encoding that. 141 // <subject> is the exported subject, minus any wildcards, so foo.* becomes foo. 142 // the one special case is that if the export start with "*" or is ">" the <subject> "_" 143 func (a *ActivationClaims) HashID() (string, error) { 144 145 if a.Issuer == "" || a.Subject == "" || a.ImportSubject == "" { 146 return "", fmt.Errorf("not enough data in the activaion claims to create a hash") 147 } 148 149 subject := cleanSubject(string(a.ImportSubject)) 150 base := fmt.Sprintf("%s.%s.%s", a.Issuer, a.Subject, subject) 151 h := sha256.New() 152 h.Write([]byte(base)) 153 sha := h.Sum(nil) 154 hash := base32.StdEncoding.EncodeToString(sha) 155 156 return hash, nil 157 } 158 159 func cleanSubject(subject string) string { 160 split := strings.Split(subject, ".") 161 cleaned := "" 162 163 for i, tok := range split { 164 if tok == "*" || tok == ">" { 165 if i == 0 { 166 cleaned = "_" 167 break 168 } 169 170 cleaned = strings.Join(split[:i], ".") 171 break 172 } 173 } 174 if cleaned == "" { 175 cleaned = subject 176 } 177 return cleaned 178 }