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  }