github.com/nats-io/jwt/v2@v2.5.6/claims.go (about) 1 /* 2 * Copyright 2018-2022 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/sha512" 20 "encoding/base32" 21 "encoding/base64" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "time" 26 27 "github.com/nats-io/nkeys" 28 ) 29 30 // ClaimType is used to indicate the type of JWT being stored in a Claim 31 type ClaimType string 32 33 const ( 34 // OperatorClaim is the type of an operator JWT 35 OperatorClaim = "operator" 36 // AccountClaim is the type of an Account JWT 37 AccountClaim = "account" 38 // UserClaim is the type of an user JWT 39 UserClaim = "user" 40 // ActivationClaim is the type of an activation JWT 41 ActivationClaim = "activation" 42 // AuthorizationRequestClaim is the type of an auth request claim JWT 43 AuthorizationRequestClaim = "authorization_request" 44 // AuthorizationResponseClaim is the response for an auth request 45 AuthorizationResponseClaim = "authorization_response" 46 // GenericClaim is a type that doesn't match Operator/Account/User/ActionClaim 47 GenericClaim = "generic" 48 ) 49 50 func IsGenericClaimType(s string) bool { 51 switch s { 52 case OperatorClaim: 53 fallthrough 54 case AccountClaim: 55 fallthrough 56 case UserClaim: 57 fallthrough 58 case AuthorizationRequestClaim: 59 fallthrough 60 case AuthorizationResponseClaim: 61 fallthrough 62 case ActivationClaim: 63 return false 64 case GenericClaim: 65 return true 66 default: 67 return true 68 } 69 } 70 71 // Claims is a JWT claims 72 type Claims interface { 73 Claims() *ClaimsData 74 Encode(kp nkeys.KeyPair) (string, error) 75 ExpectedPrefixes() []nkeys.PrefixByte 76 Payload() interface{} 77 String() string 78 Validate(vr *ValidationResults) 79 ClaimType() ClaimType 80 81 verify(payload string, sig []byte) bool 82 updateVersion() 83 } 84 85 type GenericFields struct { 86 Tags TagList `json:"tags,omitempty"` 87 Type ClaimType `json:"type,omitempty"` 88 Version int `json:"version,omitempty"` 89 } 90 91 // ClaimsData is the base struct for all claims 92 type ClaimsData struct { 93 Audience string `json:"aud,omitempty"` 94 Expires int64 `json:"exp,omitempty"` 95 ID string `json:"jti,omitempty"` 96 IssuedAt int64 `json:"iat,omitempty"` 97 Issuer string `json:"iss,omitempty"` 98 Name string `json:"name,omitempty"` 99 NotBefore int64 `json:"nbf,omitempty"` 100 Subject string `json:"sub,omitempty"` 101 } 102 103 // Prefix holds the prefix byte for an NKey 104 type Prefix struct { 105 nkeys.PrefixByte 106 } 107 108 func encodeToString(d []byte) string { 109 return base64.RawURLEncoding.EncodeToString(d) 110 } 111 112 func decodeString(s string) ([]byte, error) { 113 return base64.RawURLEncoding.DecodeString(s) 114 } 115 116 func serialize(v interface{}) (string, error) { 117 j, err := json.Marshal(v) 118 if err != nil { 119 return "", err 120 } 121 return encodeToString(j), nil 122 } 123 124 func (c *ClaimsData) doEncode(header *Header, kp nkeys.KeyPair, claim Claims) (string, error) { 125 if header == nil { 126 return "", errors.New("header is required") 127 } 128 129 if kp == nil { 130 return "", errors.New("keypair is required") 131 } 132 133 if c != claim.Claims() { 134 return "", errors.New("claim and claim data do not match") 135 } 136 137 if c.Subject == "" { 138 return "", errors.New("subject is not set") 139 } 140 141 h, err := serialize(header) 142 if err != nil { 143 return "", err 144 } 145 146 issuerBytes, err := kp.PublicKey() 147 if err != nil { 148 return "", err 149 } 150 151 prefixes := claim.ExpectedPrefixes() 152 if prefixes != nil { 153 ok := false 154 for _, p := range prefixes { 155 switch p { 156 case nkeys.PrefixByteAccount: 157 if nkeys.IsValidPublicAccountKey(issuerBytes) { 158 ok = true 159 } 160 case nkeys.PrefixByteOperator: 161 if nkeys.IsValidPublicOperatorKey(issuerBytes) { 162 ok = true 163 } 164 case nkeys.PrefixByteServer: 165 if nkeys.IsValidPublicServerKey(issuerBytes) { 166 ok = true 167 } 168 case nkeys.PrefixByteCluster: 169 if nkeys.IsValidPublicClusterKey(issuerBytes) { 170 ok = true 171 } 172 case nkeys.PrefixByteUser: 173 if nkeys.IsValidPublicUserKey(issuerBytes) { 174 ok = true 175 } 176 } 177 } 178 if !ok { 179 return "", fmt.Errorf("unable to validate expected prefixes - %v", prefixes) 180 } 181 } 182 183 c.Issuer = issuerBytes 184 c.IssuedAt = time.Now().UTC().Unix() 185 c.ID = "" // to create a repeatable hash 186 c.ID, err = c.hash() 187 if err != nil { 188 return "", err 189 } 190 191 claim.updateVersion() 192 193 payload, err := serialize(claim) 194 if err != nil { 195 return "", err 196 } 197 198 toSign := fmt.Sprintf("%s.%s", h, payload) 199 eSig := "" 200 if header.Algorithm == AlgorithmNkeyOld { 201 return "", errors.New(AlgorithmNkeyOld + " not supported to write jwtV2") 202 } else if header.Algorithm == AlgorithmNkey { 203 sig, err := kp.Sign([]byte(toSign)) 204 if err != nil { 205 return "", err 206 } 207 eSig = encodeToString(sig) 208 } else { 209 return "", errors.New(header.Algorithm + " not supported to write jwtV2") 210 } 211 // hash need no padding 212 return fmt.Sprintf("%s.%s", toSign, eSig), nil 213 } 214 215 func (c *ClaimsData) hash() (string, error) { 216 j, err := json.Marshal(c) 217 if err != nil { 218 return "", err 219 } 220 h := sha512.New512_256() 221 h.Write(j) 222 return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(h.Sum(nil)), nil 223 } 224 225 // Encode encodes a claim into a JWT token. The claim is signed with the 226 // provided nkey's private key 227 func (c *ClaimsData) encode(kp nkeys.KeyPair, payload Claims) (string, error) { 228 return c.doEncode(&Header{TokenTypeJwt, AlgorithmNkey}, kp, payload) 229 } 230 231 // Returns a JSON representation of the claim 232 func (c *ClaimsData) String(claim interface{}) string { 233 j, err := json.MarshalIndent(claim, "", " ") 234 if err != nil { 235 return "" 236 } 237 return string(j) 238 } 239 240 func parseClaims(s string, target Claims) error { 241 h, err := decodeString(s) 242 if err != nil { 243 return err 244 } 245 return json.Unmarshal(h, &target) 246 } 247 248 // Verify verifies that the encoded payload was signed by the 249 // provided public key. Verify is called automatically with 250 // the claims portion of the token and the public key in the claim. 251 // Client code need to insure that the public key in the 252 // claim is trusted. 253 func (c *ClaimsData) verify(payload string, sig []byte) bool { 254 // decode the public key 255 kp, err := nkeys.FromPublicKey(c.Issuer) 256 if err != nil { 257 return false 258 } 259 if err := kp.Verify([]byte(payload), sig); err != nil { 260 return false 261 } 262 return true 263 } 264 265 // Validate checks a claim to make sure it is valid. Validity checks 266 // include expiration and not before constraints. 267 func (c *ClaimsData) Validate(vr *ValidationResults) { 268 now := time.Now().UTC().Unix() 269 if c.Expires > 0 && now > c.Expires { 270 vr.AddTimeCheck("claim is expired") 271 } 272 273 if c.NotBefore > 0 && c.NotBefore > now { 274 vr.AddTimeCheck("claim is not yet valid") 275 } 276 } 277 278 // IsSelfSigned returns true if the claims issuer is the subject 279 func (c *ClaimsData) IsSelfSigned() bool { 280 return c.Issuer == c.Subject 281 }