github.com/nats-io/jwt/v2@v2.5.6/creds_utils.go (about) 1 /* 2 * Copyright 2019-2020 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 "bytes" 20 "errors" 21 "fmt" 22 "regexp" 23 "strings" 24 "time" 25 26 "github.com/nats-io/nkeys" 27 ) 28 29 // DecorateJWT returns a decorated JWT that describes the kind of JWT 30 func DecorateJWT(jwtString string) ([]byte, error) { 31 gc, err := Decode(jwtString) 32 if err != nil { 33 return nil, err 34 } 35 return formatJwt(string(gc.ClaimType()), jwtString) 36 } 37 38 func formatJwt(kind string, jwtString string) ([]byte, error) { 39 templ := `-----BEGIN NATS %s JWT----- 40 %s 41 ------END NATS %s JWT------ 42 43 ` 44 w := bytes.NewBuffer(nil) 45 kind = strings.ToUpper(kind) 46 _, err := fmt.Fprintf(w, templ, kind, jwtString, kind) 47 if err != nil { 48 return nil, err 49 } 50 return w.Bytes(), nil 51 } 52 53 // DecorateSeed takes a seed and returns a string that wraps 54 // the seed in the form: 55 // 56 // ************************* IMPORTANT ************************* 57 // NKEY Seed printed below can be used sign and prove identity. 58 // NKEYs are sensitive and should be treated as secrets. 59 // 60 // -----BEGIN USER NKEY SEED----- 61 // SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM 62 // ------END USER NKEY SEED------ 63 func DecorateSeed(seed []byte) ([]byte, error) { 64 w := bytes.NewBuffer(nil) 65 ts := bytes.TrimSpace(seed) 66 pre := string(ts[0:2]) 67 kind := "" 68 switch pre { 69 case "SU": 70 kind = "USER" 71 case "SA": 72 kind = "ACCOUNT" 73 case "SO": 74 kind = "OPERATOR" 75 default: 76 return nil, errors.New("seed is not an operator, account or user seed") 77 } 78 header := `************************* IMPORTANT ************************* 79 NKEY Seed printed below can be used to sign and prove identity. 80 NKEYs are sensitive and should be treated as secrets. 81 82 -----BEGIN %s NKEY SEED----- 83 ` 84 _, err := fmt.Fprintf(w, header, kind) 85 if err != nil { 86 return nil, err 87 } 88 w.Write(ts) 89 90 footer := ` 91 ------END %s NKEY SEED------ 92 93 ************************************************************* 94 ` 95 _, err = fmt.Fprintf(w, footer, kind) 96 if err != nil { 97 return nil, err 98 } 99 return w.Bytes(), nil 100 } 101 102 var userConfigRE = regexp.MustCompile(`\s*(?:(?:[-]{3,}.*[-]{3,}\r?\n)([\w\-.=]+)(?:\r?\n[-]{3,}.*[-]{3,}(\r?\n|\z)))`) 103 104 // An user config file looks like this: 105 // -----BEGIN NATS USER JWT----- 106 // eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5... 107 // ------END NATS USER JWT------ 108 // 109 // ************************* IMPORTANT ************************* 110 // NKEY Seed printed below can be used sign and prove identity. 111 // NKEYs are sensitive and should be treated as secrets. 112 // 113 // -----BEGIN USER NKEY SEED----- 114 // SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM 115 // ------END USER NKEY SEED------ 116 117 // FormatUserConfig returns a decorated file with a decorated JWT and decorated seed 118 func FormatUserConfig(jwtString string, seed []byte) ([]byte, error) { 119 gc, err := Decode(jwtString) 120 if err != nil { 121 return nil, err 122 } 123 if gc.ClaimType() != UserClaim { 124 return nil, fmt.Errorf("%q cannot be serialized as a user config", string(gc.ClaimType())) 125 } 126 127 w := bytes.NewBuffer(nil) 128 129 jd, err := formatJwt(string(gc.ClaimType()), jwtString) 130 if err != nil { 131 return nil, err 132 } 133 _, err = w.Write(jd) 134 if err != nil { 135 return nil, err 136 } 137 if !bytes.HasPrefix(bytes.TrimSpace(seed), []byte("SU")) { 138 return nil, fmt.Errorf("nkey seed is not an user seed") 139 } 140 141 d, err := DecorateSeed(seed) 142 if err != nil { 143 return nil, err 144 } 145 _, err = w.Write(d) 146 if err != nil { 147 return nil, err 148 } 149 150 return w.Bytes(), nil 151 } 152 153 // ParseDecoratedJWT takes a creds file and returns the JWT portion. 154 func ParseDecoratedJWT(contents []byte) (string, error) { 155 items := userConfigRE.FindAllSubmatch(contents, -1) 156 if len(items) == 0 { 157 return string(contents), nil 158 } 159 // First result should be the user JWT. 160 // We copy here so that if the file contained a seed file too we wipe appropriately. 161 raw := items[0][1] 162 tmp := make([]byte, len(raw)) 163 copy(tmp, raw) 164 return string(tmp), nil 165 } 166 167 // ParseDecoratedNKey takes a creds file, finds the NKey portion and creates a 168 // key pair from it. 169 func ParseDecoratedNKey(contents []byte) (nkeys.KeyPair, error) { 170 var seed []byte 171 172 items := userConfigRE.FindAllSubmatch(contents, -1) 173 if len(items) > 1 { 174 seed = items[1][1] 175 } else { 176 lines := bytes.Split(contents, []byte("\n")) 177 for _, line := range lines { 178 if bytes.HasPrefix(bytes.TrimSpace(line), []byte("SO")) || 179 bytes.HasPrefix(bytes.TrimSpace(line), []byte("SA")) || 180 bytes.HasPrefix(bytes.TrimSpace(line), []byte("SU")) { 181 seed = line 182 break 183 } 184 } 185 } 186 if seed == nil { 187 return nil, errors.New("no nkey seed found") 188 } 189 if !bytes.HasPrefix(seed, []byte("SO")) && 190 !bytes.HasPrefix(seed, []byte("SA")) && 191 !bytes.HasPrefix(seed, []byte("SU")) { 192 return nil, errors.New("doesn't contain a seed nkey") 193 } 194 kp, err := nkeys.FromSeed(seed) 195 if err != nil { 196 return nil, err 197 } 198 return kp, nil 199 } 200 201 // ParseDecoratedUserNKey takes a creds file, finds the NKey portion and creates a 202 // key pair from it. Similar to ParseDecoratedNKey but fails for non-user keys. 203 func ParseDecoratedUserNKey(contents []byte) (nkeys.KeyPair, error) { 204 nk, err := ParseDecoratedNKey(contents) 205 if err != nil { 206 return nil, err 207 } 208 seed, err := nk.Seed() 209 if err != nil { 210 return nil, err 211 } 212 if !bytes.HasPrefix(seed, []byte("SU")) { 213 return nil, errors.New("doesn't contain an user seed nkey") 214 } 215 kp, err := nkeys.FromSeed(seed) 216 if err != nil { 217 return nil, err 218 } 219 return kp, nil 220 } 221 222 // IssueUserJWT takes an account scoped signing key, account id, and use public key (and optionally a user's name, an expiration duration and tags) and returns a valid signed JWT. 223 // The scopedSigningKey, is a mandatory account scoped signing nkey pair to sign the generated jwt (note that it _must_ be a signing key attached to the account (and a _scoped_ signing key), not the account's private (seed) key). 224 // The accountId, is a mandatory public account nkey. Will return error when not set or not account nkey. 225 // The publicUserKey, is a mandatory public user nkey. Will return error when not set or not user nkey. 226 // The name, is an optional human-readable name. When absent, default to publicUserKey. 227 // The expirationDuration, is an optional but recommended duration, when the generated jwt needs to expire. If not set, JWT will not expire. 228 // The tags, is an optional list of tags to be included in the JWT. 229 // 230 // Returns: 231 // string, resulting jwt. 232 // error, when issues arose. 233 func IssueUserJWT(scopedSigningKey nkeys.KeyPair, accountId string, publicUserKey string, name string, expirationDuration time.Duration, tags ...string) (string, error) { 234 235 if !nkeys.IsValidPublicAccountKey(accountId) { 236 return "", errors.New("issueUserJWT requires an account key for the accountId parameter, but got " + nkeys.Prefix(accountId).String()) 237 } 238 239 if !nkeys.IsValidPublicUserKey(publicUserKey) { 240 return "", errors.New("issueUserJWT requires an account key for the publicUserKey parameter, but got " + nkeys.Prefix(publicUserKey).String()) 241 } 242 243 claim := NewUserClaims(publicUserKey) 244 claim.SetScoped(true) 245 246 if expirationDuration != 0 { 247 claim.Expires = time.Now().Add(expirationDuration).UTC().Unix() 248 } 249 250 claim.IssuerAccount = accountId 251 if name != "" { 252 claim.Name = name 253 } else { 254 claim.Name = publicUserKey 255 } 256 257 claim.Subject = publicUserKey 258 claim.Tags = tags 259 260 encoded, err := claim.Encode(scopedSigningKey) 261 if err != nil { 262 return "", errors.New("err encoding claim " + err.Error()) 263 } 264 265 return encoded, nil 266 }