github.com/nats-io/jwt/v2@v2.5.6/creds_utils_test.go (about) 1 /* 2 * Copyright 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 "fmt" 21 "strings" 22 "testing" 23 24 "github.com/nats-io/nkeys" 25 ) 26 27 func makeJWT(t *testing.T) (string, nkeys.KeyPair) { 28 akp := createAccountNKey(t) 29 kp := createUserNKey(t) 30 pk := publicKey(kp, t) 31 oc := NewUserClaims(pk) 32 token, err := oc.Encode(akp) 33 if err != nil { 34 t.Fatal(err) 35 } 36 return token, kp 37 } 38 39 func Test_DecorateJwt(t *testing.T) { 40 token, _ := makeJWT(t) 41 d, err := DecorateJWT(token) 42 if err != nil { 43 t.Fatal(err) 44 } 45 s := string(d) 46 if !strings.Contains(s, "-BEGIN NATS USER JWT-") { 47 t.Fatal("doesn't contain expected header") 48 } 49 if !strings.Contains(s, "eyJ0") { 50 t.Fatal("doesn't contain public key") 51 } 52 if !strings.Contains(s, "-END NATS USER JWT------\n\n") { 53 t.Fatal("doesn't contain expected footer") 54 } 55 } 56 57 func Test_FormatUserConfig(t *testing.T) { 58 token, kp := makeJWT(t) 59 d, err := FormatUserConfig(token, seedKey(kp, t)) 60 if err != nil { 61 t.Fatal(err) 62 } 63 s := string(d) 64 if !strings.Contains(s, "-BEGIN NATS USER JWT-") { 65 t.Fatal("doesn't contain expected header") 66 } 67 if !strings.Contains(s, "eyJ0") { 68 t.Fatal("doesn't contain public key") 69 } 70 if !strings.Contains(s, "-END NATS USER JWT-") { 71 t.Fatal("doesn't contain expected footer") 72 } 73 74 validateSeed(t, d, kp) 75 } 76 77 func validateSeed(t *testing.T, decorated []byte, nk nkeys.KeyPair) { 78 kind := "" 79 seed := seedKey(nk, t) 80 switch string(seed[0:2]) { 81 case "SO": 82 kind = "operator" 83 case "SA": 84 kind = "account" 85 case "SU": 86 kind = "user" 87 default: 88 kind = "not supported" 89 } 90 kind = strings.ToUpper(kind) 91 92 s := string(decorated) 93 if !strings.Contains(s, fmt.Sprintf("\n\n-----BEGIN %s NKEY SEED-", kind)) { 94 t.Fatal("doesn't contain expected seed header") 95 } 96 if !strings.Contains(s, string(seed)) { 97 t.Fatal("doesn't contain the seed") 98 } 99 if !strings.Contains(s, fmt.Sprintf("-END %s NKEY SEED------\n\n", kind)) { 100 t.Fatal("doesn't contain expected seed footer") 101 } 102 } 103 104 func Test_ParseDecoratedJWT(t *testing.T) { 105 token, _ := makeJWT(t) 106 107 t2, err := ParseDecoratedJWT([]byte(token)) 108 if err != nil { 109 t.Fatal(err) 110 } 111 if token != t2 { 112 t.Fatal("jwt didn't match expected") 113 } 114 115 decorated, err := DecorateJWT(token) 116 if err != nil { 117 t.Fatal(err) 118 } 119 120 t3, err := ParseDecoratedJWT(decorated) 121 if err != nil { 122 t.Fatal(err) 123 } 124 if token != t3 { 125 t.Fatal("parse decorated jwt didn't match expected") 126 } 127 } 128 129 func Test_ParseDecoratedJWTBad(t *testing.T) { 130 v, err := ParseDecoratedJWT([]byte("foo")) 131 if err != nil { 132 t.Fatal(err) 133 } 134 if v != "foo" { 135 t.Fatal("unexpected input was not returned") 136 } 137 } 138 139 func Test_ParseDecoratedOPJWT(t *testing.T) { 140 content := []string{ 141 `-----BEGIN TEST OPERATOR JWT----- 142 eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJhdWQiOiJURVNUUyIsImV4cCI6MTg1OTEyMTI3NSwianRpIjoiWE5MWjZYWVBIVE1ESlFSTlFPSFVPSlFHV0NVN01JNVc1SlhDWk5YQllVS0VRVzY3STI1USIsImlhdCI6MTU0Mzc2MTI3NSwiaXNzIjoiT0NBVDMzTVRWVTJWVU9JTUdOR1VOWEo2NkFIMlJMU0RBRjNNVUJDWUFZNVFNSUw2NU5RTTZYUUciLCJuYW1lIjoiU3luYWRpYSBDb21tdW5pY2F0aW9ucyBJbmMuIiwibmJmIjoxNTQzNzYxMjc1LCJzdWIiOiJPQ0FUMzNNVFZVMlZVT0lNR05HVU5YSjY2QUgyUkxTREFGM01VQkNZQVk1UU1JTDY1TlFNNlhRRyIsInR5cGUiOiJvcGVyYXRvciIsIm5hdHMiOnsic2lnbmluZ19rZXlzIjpbIk9EU0tSN01ZRlFaNU1NQUo2RlBNRUVUQ1RFM1JJSE9GTFRZUEpSTUFWVk40T0xWMllZQU1IQ0FDIiwiT0RTS0FDU1JCV1A1MzdEWkRSVko2NTdKT0lHT1BPUTZLRzdUNEhONk9LNEY2SUVDR1hEQUhOUDIiLCJPRFNLSTM2TFpCNDRPWTVJVkNSNlA1MkZaSlpZTVlXWlZXTlVEVExFWjVUSzJQTjNPRU1SVEFCUiJdfX0.hyfz6E39BMUh0GLzovFfk3wT4OfualftjdJ_eYkLfPvu5tZubYQ_Pn9oFYGCV_6yKy3KMGhWGUCyCdHaPhalBw 143 ------END TEST OPERATOR JWT------`, 144 `-----BEGIN TEST OPERATOR JWT----- 145 eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJKV01TUzNRUFpDS0lHSE1BWko3RUpQSlVHN01DTFNQUkJaTEpSUUlRQkRVTkFaUE5MQVVBIiwiaWF0IjoxNTY1ODg5NzEyLCJpc3MiOiJPQU01VlNINDJXRlZWTkpXNFNMRTZRVkpCREpVRTJGUVNYWkxRTk1SRDdBMlBaTTIzTDIyWFlVWSIsIm5hbWUiOiJzeW5hZGlhIiwic3ViIjoiT0FNNVZTSDQyV0ZWVk5KVzRTTEU2UVZKQkRKVUUyRlFTWFpMUU5NUkQ3QTJQWk0yM0wyMlhZVVkiLCJ0eXBlIjoib3BlcmF0b3IiLCJuYXRzIjp7ImFjY291bnRfc2VydmVyX3VybCI6Imh0dHA6Ly9sb2NhbGhvc3Q6NjA2MC9qd3QvdjEiLCJvcGVyYXRvcl9zZXJ2aWNlX3VybHMiOlsibmF0czovL2xvY2FsaG9zdDo0MTQxIl19fQ.XPvAezQj3AxwEvYLVBq-EIssP4OhjoMGLbIaripzBKv1oCtHdPNKz96YwB2vUoY-4OrN9ZOPo9TKR3jVxq0uBQ 146 ------END TEST OPERATOR JWT------`} 147 test := func(content string) { 148 t.Helper() 149 v, err := ParseDecoratedJWT([]byte(content)) 150 if err != nil { 151 t.Fatal(err) 152 } 153 if !strings.HasPrefix(v, "eyJ") { 154 t.Fatal("unexpected input was not returned") 155 } 156 } 157 for i, cont := range content { 158 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 159 test(cont) 160 }) 161 t.Run(fmt.Sprintf("%d-win", i), func(t *testing.T) { 162 test(strings.ReplaceAll(cont, "\n", "\r\n")) 163 }) 164 cont = cont + "\n" 165 t.Run(fmt.Sprintf("%d-trail-nl", i), func(t *testing.T) { 166 test(cont) 167 }) 168 t.Run(fmt.Sprintf("%d-trail-nl-win", i), func(t *testing.T) { 169 test(strings.ReplaceAll(cont, "\n", "\r\n")) 170 }) 171 } 172 } 173 174 func Test_ParseDecoratedSeed(t *testing.T) { 175 token, ukp := makeJWT(t) 176 us := seedKey(ukp, t) 177 decorated, err := FormatUserConfig(token, us) 178 if err != nil { 179 t.Fatal(err) 180 } 181 kp, err := ParseDecoratedUserNKey(decorated) 182 if err != nil { 183 t.Fatal(err) 184 } 185 pu := seedKey(kp, t) 186 if !bytes.Equal(us, pu) { 187 t.Fatal("seeds don't match") 188 } 189 } 190 191 func Test_ParseDecoratedBadKey(t *testing.T) { 192 token, ukp := makeJWT(t) 193 us, err := ukp.Seed() 194 if err != nil { 195 t.Fatal(err) 196 } 197 akp := createAccountNKey(t) 198 as := seedKey(akp, t) 199 200 _, err = FormatUserConfig(token, as) 201 if err == nil { 202 t.Fatal("should have failed to encode with bad seed") 203 } 204 205 sc, err := FormatUserConfig(token, us) 206 if err != nil { 207 t.Fatal(err) 208 } 209 bad := strings.Replace(string(sc), string(us), string(as), -1) 210 _, err = ParseDecoratedUserNKey([]byte(bad)) 211 if err == nil { 212 t.Fatal("parse should have failed for non user nkey") 213 } 214 } 215 216 func Test_FailsOnNonUserJWT(t *testing.T) { 217 akp := createAccountNKey(t) 218 pk := publicKey(akp, t) 219 220 ac := NewAccountClaims(pk) 221 token, err := ac.Encode(akp) 222 if err != nil { 223 t.Fatal(err) 224 } 225 ukp := createUserNKey(t) 226 us := seedKey(ukp, t) 227 _, err = FormatUserConfig(token, us) 228 if err == nil { 229 t.Fatal("should have failed with account claims") 230 } 231 } 232 233 func Test_DecorateNKeys(t *testing.T) { 234 var kps []nkeys.KeyPair 235 kps = append(kps, createOperatorNKey(t)) 236 kps = append(kps, createAccountNKey(t)) 237 kps = append(kps, createUserNKey(t)) 238 239 for _, kp := range kps { 240 seed := seedKey(kp, t) 241 d, err := DecorateSeed(seed) 242 if err != nil { 243 t.Fatal(err, string(seed)) 244 } 245 validateSeed(t, d, kp) 246 247 kp2, err := ParseDecoratedNKey(d) 248 if err != nil { 249 t.Fatal(string(seed), err) 250 } 251 seed2 := seedKey(kp2, t) 252 if !bytes.Equal(seed, seed2) { 253 t.Fatalf("seeds dont match %q != %q", string(seed), string(seed2)) 254 } 255 } 256 257 _, err := ParseDecoratedNKey([]byte("bad")) 258 if err == nil { 259 t.Fatal("required error parsing bad nkey") 260 } 261 } 262 263 func Test_ParseCreds(t *testing.T) { 264 token, kp := makeJWT(t) 265 d, err := FormatUserConfig(token, seedKey(kp, t)) 266 if err != nil { 267 t.Fatal(err) 268 } 269 pk, err := kp.PublicKey() 270 if err != nil { 271 t.Fatal(err) 272 } 273 274 token2, err := ParseDecoratedJWT(d) 275 if err != nil { 276 t.Fatal(err) 277 } 278 if token != token2 { 279 t.Fatal("expected jwts to match") 280 } 281 kp2, err := ParseDecoratedUserNKey(d) 282 if err != nil { 283 t.Fatal(err) 284 } 285 pk2, err := kp2.PublicKey() 286 if err != nil { 287 t.Fatal(err) 288 } 289 if pk != pk2 { 290 t.Fatal("expected keys to match") 291 } 292 } 293 294 func Test_ParseCredsWithCrLfs(t *testing.T) { 295 token, kp := makeJWT(t) 296 d, err := FormatUserConfig(token, seedKey(kp, t)) 297 if err != nil { 298 t.Fatal(err) 299 } 300 pk, err := kp.PublicKey() 301 if err != nil { 302 t.Fatal(err) 303 } 304 d = bytes.ReplaceAll(d, []byte{'\n'}, []byte{'\r', '\n'}) 305 306 token2, err := ParseDecoratedJWT(d) 307 if err != nil { 308 t.Fatal(err) 309 } 310 if token != token2 { 311 t.Fatal("expected jwts to match") 312 } 313 kp2, err := ParseDecoratedUserNKey(d) 314 if err != nil { 315 t.Fatal(err) 316 } 317 pk2, err := kp2.PublicKey() 318 if err != nil { 319 t.Fatal(err) 320 } 321 if pk != pk2 { 322 t.Fatal("expected keys to match") 323 } 324 }