github.com/blend/go-sdk@v1.20220411.3/envoyutil/xfcc_test.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package envoyutil_test
     9  
    10  import (
    11  	"crypto/x509"
    12  	"fmt"
    13  	"net/url"
    14  	"regexp"
    15  	"testing"
    16  
    17  	sdkAssert "github.com/blend/go-sdk/assert"
    18  	"github.com/blend/go-sdk/certutil"
    19  	"github.com/blend/go-sdk/ex"
    20  
    21  	"github.com/blend/go-sdk/envoyutil"
    22  )
    23  
    24  const (
    25  	fullXFCCTest                     = `By=spiffe://cluster.local/ns/blend/sa/yule;Hash=468ed33be74eee6556d90c0149c1309e9ba61d6425303443c0748a02dd8de688;Subject="/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=Test Client";URI=spiffe://cluster.local/ns/blend/sa/cheer`
    26  	xfccElementByTest                = `By=spiffe://cluster.local/ns/blend/sa/tide`
    27  	xfccElementHashTest              = `Hash=468ed33be74eee6556d90c0149c1309e9ba61d6425303443c0748a02dd8de688`
    28  	xfccElementCertTest              = `Cert=` + xfccElementTestCertEncoded
    29  	xfccElementChainTest             = `CHAIN=` + xfccElementTestCertEncoded
    30  	xfccElementSubjectTest           = `SUBJECT="/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=Test Client"`
    31  	xfccElementURITest               = `URI=spiffe://cluster.local/ns/blend/sa/quasar`
    32  	xfccElementDNSTest               = `dns=http://frontend.lyft.com`
    33  	xfccElementEndTest               = `dns=http://frontend.lyft.com;`
    34  	xfccElementNoneTest              = `key=value;dns=http://frontend.lyft.com`
    35  	xfccElementMultiTest             = `By=spiffe://cluster.local/ns/blend/sa/laser;Hash=468ed33be74eee6556d90c0149c1309e9ba61d6425303443c0748a02dd8de688`
    36  	xfccElementMalformedKeyTest      = `=value`
    37  	xfccElementMultiMalformedKeyTest = `=value;dns=http://frontend.lyft.com`
    38  	xfccElementMultiCertTest         = `cert=` + xfccElementTestCertEncoded + xfccElementTestCert
    39  
    40  	xfccElementMalformedEncoding = "%"
    41  
    42  	xfccElementTestCertEncoded = `-----BEGIN%20CERTIFICATE-----%0AMIIFKjCCAxICCQCA5%2FOCxg%2FqiDANBgkqhkiG9w0BAQsFADBXMQswCQYDVQQGEwJV%0AUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM%0ABEx5ZnQxFDASBgNVBAMMC1Rlc3QgQ2xpZW50MB4XDTIwMDYwNDE3NDkzNVoXDTIx%0AMDYwNDE3NDkzNVowVzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQH%0ADA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARMeWZ0MRQwEgYDVQQDDAtUZXN0IENs%0AaWVudDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKs6T8vcb8rIIkC4%0Aiz9h%2FOj6Iv%2BfazTLwNLK%2Fk58Ape5ZL0IdW6h8pWDlGnGz4X%2FTaJ5TwlamFo1h62v%0AsR8HPNOoLY0wmC2qHVquPF6eR9Lt5ejJiakr%2BYvf%2BU6LHXlOpOoot5rcTGoGBCf0%0AH3zmjdOE0o6hwJxMf54XQEVwNXqRrIDbY27mYS8eAVcSMrPUQVZ%2B3Vk1S56Imybz%0Adegi79IIoc6TzE5M7ChfJZBNNNZT08haJe6Oi%2FIgZhK3IexssY%2BQyD5uBSc7Mpas%0A6TstzeevIbeFy3Od2GhUy2Hz98qW%2FoO5iuerEArkNs4lB0J%2F0ARPHUDnmmH%2BqWYF%0APKealq2yEyXHHXrhDcSK%2FN5R64pp%2FVrxEas1qG20%2FCG4rixv36UJuEz5oUKNWyaR%0A268EI5Vecw%2BpK%2F0XC2%2Bhra9T%2FeP9JH0Fp43x7bdpQoxph8ZJZBsjbgCFMonf3ku1%0A9n74%2FxwvV6B0wp5C8jpwbGa85n%2BT8hogtO78mnpvxhTVJ7TOy596tI2apJ02edtD%0AJgsJV9MfZ%2FfGu3QZ6yN3rKVMPkZfC18cK04xy%2BroPo756CHkUHP5cz%2BKtJ7%2B8COR%0ArPDPxKBLOqwaSFcanQNONFIrffnZciiisCxjMHGoM4%2Fuix5gStlDC9%2FM5yyHt9He%0AldC8xL%2FyIalsa9Df7SL59Fd7T2JrAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAGTb%0AOTddb6Yr37JigDYGKxSiidWfPgxTFr3DWTTE2aBhTi8K1Gr8Br1FAGRg4P114kxm%0AQBx3TxuCZcssMX2Z3GbL20DrHNExCl7hM%2FZVA2OCVhwfXGOHRQ6lcpmeQISWDNsN%0Atanlap%2FAgqKN%2F6At6JEYmuTSJnKc4Bfgk2GP5LPa63yJOlyvFb8ovKsCgb1ppVyw%0ARE%2B7AmB2DfDdVql4nHsDh5UBZRgVxMZ6xGnkYKaAUDKl4slejvKwXuzu2Xf%2BAd74%0AgjdLHzP0WmHlAggR5LIv%2F9xlvrsKCrNDDxWwOGeYk2WZl%2Fybud0RFKhLIqbbeMy7%0ADcdy04cJcqa9qRHYySgaWtM6Ab%2Fx9CJqdzR2NQZNnLgk6Vc3%2BoDjXMUuyM17WJAS%0ArenwJvanXvF9P1yPMByJQlXxkUehkCa%2FPs7E1O%2F%2BE2FJnvrtGVdYVR8Otbec1osS%0AmtJC6k7rgMhgvk63sCqQqaZwRWwLl2R5XcDZknUiqDKjuVHHA01II7jtGB1oyEIH%0Asp%2FrQlLNeyYlyhAlc3MhF5hu6nUjH%2B2%2BDuIHJsM0mEF0rjlbnp4bKJ%2FgF1COAIAL%0APzu2qAC%2BaOFldCmRonqUluayv6fQaQCeeh8sW2IjNVjA2ynKn2ybGIXH4mrH0KVa%0AJmUY%2B1YGMn7qbeHTma33N28Ec7hK%2BWByul746Nro%0A-----END%20CERTIFICATE-----`
    43  	xfccElementTestCert        = `-----BEGIN CERTIFICATE-----
    44  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
    72  -----END CERTIFICATE-----`
    73  )
    74  
    75  func TestXFCCElementDecodeBy(t *testing.T) {
    76  	assert := sdkAssert.New(t)
    77  
    78  	type testCase struct {
    79  		By          string
    80  		Expected    *url.URL
    81  		ErrorRegexp *regexp.Regexp
    82  	}
    83  	testCases := []testCase{
    84  		{By: "", Expected: &url.URL{}},
    85  		// NOTE: Regex needed to support error format changes from go1.13 to go1.14
    86  		{By: "\n", ErrorRegexp: regexp.MustCompile(`(?m)^parse ("\\n"|\n): net/url: invalid control character in URL$`)},
    87  		{
    88  			By: "spiffe://cluster.local/ns/blend/sa/yule",
    89  			Expected: &url.URL{
    90  				Scheme: "spiffe",
    91  				Host:   "cluster.local",
    92  				Path:   "/ns/blend/sa/yule",
    93  			},
    94  		},
    95  	}
    96  	for _, tc := range testCases {
    97  		xe := envoyutil.XFCCElement{By: tc.By}
    98  		uri, err := xe.DecodeBy()
    99  		assert.Equal(tc.Expected, uri)
   100  		if tc.ErrorRegexp != nil {
   101  			asEx, ok := err.(*ex.Ex)
   102  			assert.True(ok)
   103  			assert.Equal(envoyutil.ErrXFCCParsing, asEx.Class)
   104  			assert.True(tc.ErrorRegexp.MatchString(asEx.Inner.Error()))
   105  		} else {
   106  			assert.Nil(err)
   107  		}
   108  	}
   109  }
   110  
   111  func TestXFCCElementDecodeHash(t *testing.T) {
   112  	assert := sdkAssert.New(t)
   113  
   114  	type testCase struct {
   115  		Hash     string
   116  		Expected []byte
   117  		Error    string
   118  	}
   119  	testCases := []testCase{
   120  		{Hash: "", Expected: []byte("")},
   121  		{Hash: "41434944", Expected: []byte("ACID")},
   122  		{Hash: "x", Expected: nil, Error: "Error Parsing X-Forwarded-Client-Cert\nencoding/hex: invalid byte: U+0078 'x'"},
   123  	}
   124  	for _, tc := range testCases {
   125  		xe := envoyutil.XFCCElement{Hash: tc.Hash}
   126  		decoded, err := xe.DecodeHash()
   127  		assert.Equal(tc.Expected, decoded)
   128  		if tc.Error != "" {
   129  			assert.Equal(tc.Error, fmt.Sprintf("%v", err))
   130  		} else {
   131  			assert.Nil(err)
   132  		}
   133  	}
   134  }
   135  
   136  func TestXFCCElementDecodeCert(t *testing.T) {
   137  	assert := sdkAssert.New(t)
   138  
   139  	parsedCert, err := certutil.ParseCertPEM([]byte(xfccElementTestCert))
   140  	assert.Nil(err)
   141  
   142  	type testCase struct {
   143  		Cert   string
   144  		Parsed *x509.Certificate
   145  		Error  string
   146  	}
   147  	testCases := []testCase{
   148  		{Cert: ""},
   149  		{Cert: xfccElementTestCertEncoded, Parsed: parsedCert[0]},
   150  		{Cert: "%", Error: "Error Parsing X-Forwarded-Client-Cert\ninvalid URL escape \"%\""},
   151  		{
   152  			Cert:  "-----BEGIN CERTIFICATE-----\nnope\n-----END CERTIFICATE-----\n",
   153  			Error: "Error Parsing X-Forwarded-Client-Cert\nx509: malformed certificate",
   154  		},
   155  		{
   156  			Cert:  url.QueryEscape(xfccElementTestCert + "\n" + xfccElementTestCert),
   157  			Error: "Error Parsing X-Forwarded-Client-Cert; Incorrect number of certificates; expected 1 got 2",
   158  		},
   159  		{
   160  			Cert:  xfccElementMultiCertTest,
   161  			Error: "Error Parsing X-Forwarded-Client-Cert; Incorrect number of certificates; expected 1 got 0",
   162  		},
   163  	}
   164  
   165  	for _, tc := range testCases {
   166  		xe := envoyutil.XFCCElement{Cert: tc.Cert}
   167  		cert, err := xe.DecodeCert()
   168  		if tc.Error != "" {
   169  			assert.Equal(tc.Error, fmt.Sprintf("%v", err))
   170  		} else {
   171  			assert.Nil(err)
   172  		}
   173  		assert.Equal(tc.Parsed, cert)
   174  	}
   175  }
   176  
   177  func TestXFCCElementDecodeChain(t *testing.T) {
   178  	assert := sdkAssert.New(t)
   179  
   180  	parsedCerts, err := certutil.ParseCertPEM([]byte(xfccElementTestCert + "\n" + xfccElementTestCert))
   181  	assert.Nil(err)
   182  
   183  	type testCase struct {
   184  		Chain  string
   185  		Parsed []*x509.Certificate
   186  		Error  string
   187  	}
   188  	testCases := []testCase{
   189  		{Chain: ""},
   190  		{
   191  			Chain:  url.QueryEscape(xfccElementTestCert + "\n" + xfccElementTestCert),
   192  			Parsed: parsedCerts,
   193  		},
   194  		{Chain: "%", Error: "Error Parsing X-Forwarded-Client-Cert\ninvalid URL escape \"%\""},
   195  		{
   196  			Chain: "-----BEGIN CERTIFICATE-----\nnope\n-----END CERTIFICATE-----\n",
   197  			Error: "Error Parsing X-Forwarded-Client-Cert\nx509: malformed certificate",
   198  		},
   199  	}
   200  
   201  	for _, tc := range testCases {
   202  		xe := envoyutil.XFCCElement{Chain: tc.Chain}
   203  		chain, err := xe.DecodeChain()
   204  		if tc.Error != "" {
   205  			assert.Equal(tc.Error, fmt.Sprintf("%v", err))
   206  		} else {
   207  			assert.Nil(err)
   208  		}
   209  		assert.Equal(tc.Parsed, chain)
   210  	}
   211  }
   212  
   213  func TestXFCCElementDecodeURI(t *testing.T) {
   214  	assert := sdkAssert.New(t)
   215  
   216  	type testCase struct {
   217  		URI         string
   218  		Expected    *url.URL
   219  		ErrorRegexp *regexp.Regexp
   220  	}
   221  	testCases := []testCase{
   222  		{URI: "", Expected: &url.URL{}},
   223  		// NOTE: Regex needed to support error format changes from go1.13 to go1.14
   224  		{URI: "\r", ErrorRegexp: regexp.MustCompile(`(?m)^parse ("\\r"|\r): net/url: invalid control character in URL$`)},
   225  		{
   226  			URI: "spiffe://cluster.local/ns/first/sa/furst",
   227  			Expected: &url.URL{
   228  				Scheme: "spiffe",
   229  				Host:   "cluster.local",
   230  				Path:   "/ns/first/sa/furst",
   231  			},
   232  		},
   233  	}
   234  	for _, tc := range testCases {
   235  		xe := envoyutil.XFCCElement{URI: tc.URI}
   236  		uri, err := xe.DecodeURI()
   237  		assert.Equal(tc.Expected, uri)
   238  		if tc.ErrorRegexp != nil {
   239  			asEx, ok := err.(*ex.Ex)
   240  			assert.True(ok)
   241  			assert.Equal(envoyutil.ErrXFCCParsing, asEx.Class)
   242  			assert.True(tc.ErrorRegexp.MatchString(asEx.Inner.Error()))
   243  		} else {
   244  			assert.Nil(err)
   245  		}
   246  	}
   247  }
   248  
   249  func TestXFCCElementString(t *testing.T) {
   250  	assert := sdkAssert.New(t)
   251  
   252  	type testCase struct {
   253  		Element  envoyutil.XFCCElement
   254  		Expected string
   255  	}
   256  	testCases := []testCase{
   257  		{Element: envoyutil.XFCCElement{By: "hi"}, Expected: "By=hi"},
   258  		{Element: envoyutil.XFCCElement{Hash: "1389ab1"}, Expected: "Hash=1389ab1"},
   259  		{Element: envoyutil.XFCCElement{Cert: "anything-goes"}, Expected: "Cert=anything-goes"},
   260  		{Element: envoyutil.XFCCElement{Chain: "anything-goes"}, Expected: "Chain=anything-goes"},
   261  		{
   262  			Element:  envoyutil.XFCCElement{Subject: "OU=Blent/CN=Test Client"},
   263  			Expected: `Subject="OU=Blent/CN=Test Client"`,
   264  		},
   265  		{Element: envoyutil.XFCCElement{URI: "bye"}, Expected: "URI=bye"},
   266  		{
   267  			Element:  envoyutil.XFCCElement{DNS: []string{"web.invalid", "bye.invalid"}},
   268  			Expected: "DNS=web.invalid;DNS=bye.invalid",
   269  		},
   270  		{
   271  			Element:  envoyutil.XFCCElement{By: "a,b=10", URI: `c; "then" again`},
   272  			Expected: "By=\"a,b=10\";URI=\"c; \\\"then\\\" again\"",
   273  		},
   274  	}
   275  	for _, tc := range testCases {
   276  		asString := tc.Element.String()
   277  		assert.Equal(tc.Expected, asString)
   278  		parsed, err := envoyutil.ParseXFCC(asString)
   279  		assert.Nil(err)
   280  		assert.Equal(envoyutil.XFCC{tc.Element}, parsed)
   281  	}
   282  
   283  	element := envoyutil.XFCCElement{}
   284  	asString := element.String()
   285  	assert.Equal("", asString)
   286  }
   287  
   288  func TestParseXFCC(t *testing.T) {
   289  	assert := sdkAssert.New(t)
   290  
   291  	ele, err := envoyutil.ParseXFCC(xfccElementByTest)
   292  	assert.Nil(err)
   293  	expected := envoyutil.XFCC{
   294  		envoyutil.XFCCElement{By: "spiffe://cluster.local/ns/blend/sa/tide"},
   295  	}
   296  	assert.Equal(expected, ele)
   297  
   298  	ele, err = envoyutil.ParseXFCC(xfccElementByTest + ";" + xfccElementByTest)
   299  	except, ok := err.(*ex.Ex)
   300  	assert.True(ok)
   301  	assert.NotNil(except)
   302  	expectedErr := &ex.Ex{
   303  		Class:      envoyutil.ErrXFCCParsing,
   304  		Message:    `Key already encountered "by"`,
   305  		StackTrace: except.StackTrace,
   306  	}
   307  	assert.Equal(expectedErr, except)
   308  	assert.Equal(envoyutil.XFCC{}, ele)
   309  
   310  	ele, err = envoyutil.ParseXFCC(xfccElementHashTest)
   311  	assert.Nil(err)
   312  	expected = envoyutil.XFCC{
   313  		envoyutil.XFCCElement{
   314  			Hash: "468ed33be74eee6556d90c0149c1309e9ba61d6425303443c0748a02dd8de688",
   315  		},
   316  	}
   317  	assert.Equal(expected, ele)
   318  
   319  	ele, err = envoyutil.ParseXFCC(xfccElementHashTest + ";" + xfccElementHashTest)
   320  	except, ok = err.(*ex.Ex)
   321  	assert.True(ok)
   322  	assert.NotNil(except)
   323  	expectedErr = &ex.Ex{
   324  		Class:      envoyutil.ErrXFCCParsing,
   325  		Message:    `Key already encountered "hash"`,
   326  		StackTrace: except.StackTrace,
   327  	}
   328  	assert.Equal(expectedErr, except)
   329  	assert.Equal(envoyutil.XFCC{}, ele)
   330  
   331  	ele, err = envoyutil.ParseXFCC(xfccElementCertTest)
   332  	assert.Nil(err)
   333  	expected = envoyutil.XFCC{
   334  		envoyutil.XFCCElement{
   335  			Cert: xfccElementTestCertEncoded,
   336  		},
   337  	}
   338  	assert.Equal(expected, ele)
   339  
   340  	ele, err = envoyutil.ParseXFCC(xfccElementCertTest + ";" + xfccElementCertTest)
   341  	except, ok = err.(*ex.Ex)
   342  	assert.True(ok)
   343  	assert.NotNil(except)
   344  	expectedErr = &ex.Ex{
   345  		Class:      envoyutil.ErrXFCCParsing,
   346  		Message:    `Key already encountered "cert"`,
   347  		StackTrace: except.StackTrace,
   348  	}
   349  	assert.Equal(expectedErr, except)
   350  	assert.Equal(envoyutil.XFCC{}, ele)
   351  
   352  	ele, err = envoyutil.ParseXFCC(xfccElementChainTest)
   353  	assert.Nil(err)
   354  	expected = envoyutil.XFCC{
   355  		envoyutil.XFCCElement{
   356  			Chain: xfccElementTestCertEncoded,
   357  		},
   358  	}
   359  	assert.Equal(expected, ele)
   360  
   361  	ele, err = envoyutil.ParseXFCC(xfccElementChainTest + ";" + xfccElementChainTest)
   362  	except, ok = err.(*ex.Ex)
   363  	assert.True(ok)
   364  	assert.NotNil(except)
   365  	expectedErr = &ex.Ex{
   366  		Class:      envoyutil.ErrXFCCParsing,
   367  		Message:    `Key already encountered "chain"`,
   368  		StackTrace: except.StackTrace,
   369  	}
   370  	assert.Equal(expectedErr, except)
   371  	assert.Equal(envoyutil.XFCC{}, ele)
   372  
   373  	ele, err = envoyutil.ParseXFCC(xfccElementSubjectTest)
   374  	assert.Nil(err)
   375  	expected = envoyutil.XFCC{
   376  		envoyutil.XFCCElement{
   377  			Subject: "/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=Test Client",
   378  		},
   379  	}
   380  	assert.Equal(expected, ele)
   381  
   382  	ele, err = envoyutil.ParseXFCC(xfccElementSubjectTest + ";" + xfccElementSubjectTest)
   383  	except, ok = err.(*ex.Ex)
   384  	assert.True(ok)
   385  	assert.NotNil(except)
   386  	expectedErr = &ex.Ex{
   387  		Class:      envoyutil.ErrXFCCParsing,
   388  		Message:    `Key already encountered "subject"`,
   389  		StackTrace: except.StackTrace,
   390  	}
   391  	assert.Equal(expectedErr, except)
   392  	assert.Equal(envoyutil.XFCC{}, ele)
   393  
   394  	ele, err = envoyutil.ParseXFCC(xfccElementURITest)
   395  	assert.Nil(err)
   396  	expected = envoyutil.XFCC{
   397  		envoyutil.XFCCElement{
   398  			URI: "spiffe://cluster.local/ns/blend/sa/quasar",
   399  		},
   400  	}
   401  	assert.Equal(expected, ele)
   402  
   403  	ele, err = envoyutil.ParseXFCC(xfccElementURITest + ";" + xfccElementURITest)
   404  	except, ok = err.(*ex.Ex)
   405  	assert.True(ok)
   406  	assert.NotNil(except)
   407  	expectedErr = &ex.Ex{
   408  		Class:      envoyutil.ErrXFCCParsing,
   409  		Message:    `Key already encountered "uri"`,
   410  		StackTrace: except.StackTrace,
   411  	}
   412  	assert.Equal(expectedErr, except)
   413  	assert.Equal(envoyutil.XFCC{}, ele)
   414  
   415  	ele, err = envoyutil.ParseXFCC(xfccElementDNSTest)
   416  	assert.Nil(err)
   417  	expected = envoyutil.XFCC{
   418  		envoyutil.XFCCElement{
   419  			DNS: []string{"http://frontend.lyft.com"},
   420  		},
   421  	}
   422  	assert.Equal(expected, ele)
   423  
   424  	ele, err = envoyutil.ParseXFCC("dns=web.invalid;dns=blend.local.invalid")
   425  	assert.Nil(err)
   426  	expected = envoyutil.XFCC{
   427  		envoyutil.XFCCElement{
   428  			DNS: []string{"web.invalid", "blend.local.invalid"},
   429  		},
   430  	}
   431  	assert.Equal(expected, ele)
   432  
   433  	_, err = envoyutil.ParseXFCC(xfccElementNoneTest)
   434  	assert.NotNil(err)
   435  	except, ok = err.(*ex.Ex)
   436  	assert.True(ok)
   437  	assert.NotNil(except)
   438  	assert.Equal(envoyutil.ErrXFCCParsing, except.Class)
   439  
   440  	ele, err = envoyutil.ParseXFCC(xfccElementMultiTest)
   441  	assert.Nil(err)
   442  	expected = envoyutil.XFCC{
   443  		envoyutil.XFCCElement{
   444  			By:   "spiffe://cluster.local/ns/blend/sa/laser",
   445  			Hash: "468ed33be74eee6556d90c0149c1309e9ba61d6425303443c0748a02dd8de688",
   446  		},
   447  	}
   448  	assert.Equal(expected, ele)
   449  
   450  	_, err = envoyutil.ParseXFCC(xfccElementMalformedKeyTest)
   451  	assert.NotNil(err)
   452  	except, ok = err.(*ex.Ex)
   453  	assert.True(ok)
   454  	assert.NotNil(except)
   455  	assert.Equal(envoyutil.ErrXFCCParsing, except.Class)
   456  
   457  	_, err = envoyutil.ParseXFCC(xfccElementMultiMalformedKeyTest)
   458  	assert.NotNil(err)
   459  	except, ok = err.(*ex.Ex)
   460  	assert.True(ok)
   461  	assert.NotNil(except)
   462  	assert.Equal(envoyutil.ErrXFCCParsing, except.Class)
   463  
   464  	ele, err = envoyutil.ParseXFCC(xfccElementEndTest)
   465  	except, ok = err.(*ex.Ex)
   466  	assert.True(ok)
   467  	assert.NotNil(except)
   468  	expectedErr = &ex.Ex{
   469  		Class:      envoyutil.ErrXFCCParsing,
   470  		Message:    "Ends with separator character",
   471  		StackTrace: except.StackTrace,
   472  	}
   473  	assert.Equal(expectedErr, except)
   474  	assert.Equal(envoyutil.XFCC{}, ele)
   475  
   476  	ele, err = envoyutil.ParseXFCC("cert=" + xfccElementMalformedEncoding)
   477  	assert.Nil(err)
   478  	expected = envoyutil.XFCC{
   479  		envoyutil.XFCCElement{
   480  			Cert: "%",
   481  		},
   482  	}
   483  	assert.Equal(expected, ele)
   484  
   485  	ele, err = envoyutil.ParseXFCC("chain=" + xfccElementMalformedEncoding)
   486  	assert.Nil(err)
   487  	expected = envoyutil.XFCC{
   488  		envoyutil.XFCCElement{
   489  			Chain: "%",
   490  		},
   491  	}
   492  	assert.Equal(expected, ele)
   493  
   494  	ele, err = envoyutil.ParseXFCC("=;")
   495  	assert.NotNil(err)
   496  	except, ok = err.(*ex.Ex)
   497  	assert.True(ok)
   498  	assert.NotNil(except)
   499  	assert.Equal(envoyutil.ErrXFCCParsing, except.Class)
   500  	assert.Equal(envoyutil.XFCC{}, ele)
   501  
   502  	// Test empty subject
   503  	ele, err = envoyutil.ParseXFCC(`By=spiffe://cluster.local/ns/blend/sa/protocol;Hash=52114972613efb0820c5e32bfee0f0ee2a84859f7169da6c222300ef852a1129;Subject="";URI=spiffe://cluster.local/ns/blend/sa/world`)
   504  	assert.Nil(err)
   505  	expected = envoyutil.XFCC{
   506  		envoyutil.XFCCElement{
   507  			By:      "spiffe://cluster.local/ns/blend/sa/protocol",
   508  			Hash:    "52114972613efb0820c5e32bfee0f0ee2a84859f7169da6c222300ef852a1129",
   509  			Subject: "",
   510  			URI:     "spiffe://cluster.local/ns/blend/sa/world",
   511  		},
   512  	}
   513  	assert.Equal(expected, ele)
   514  
   515  	// Quoted value with empty key.
   516  	ele, err = envoyutil.ParseXFCC(`="a";b=20`)
   517  	assert.Equal(envoyutil.XFCC{}, ele)
   518  	except, ok = err.(*ex.Ex)
   519  	assert.True(ok)
   520  	assert.NotNil(except)
   521  	expectedErr = &ex.Ex{
   522  		Class:      envoyutil.ErrXFCCParsing,
   523  		Message:    "Key missing",
   524  		StackTrace: except.StackTrace,
   525  	}
   526  	assert.Equal(expectedErr, except)
   527  
   528  	// Quoted value with invalid key.
   529  	ele, err = envoyutil.ParseXFCC(`wrong="quoted";by=next`)
   530  	assert.Equal(envoyutil.XFCC{}, ele)
   531  	except, ok = err.(*ex.Ex)
   532  	assert.True(ok)
   533  	assert.NotNil(except)
   534  	expectedErr = &ex.Ex{
   535  		Class:      envoyutil.ErrXFCCParsing,
   536  		Message:    `Unknown key "wrong"`,
   537  		StackTrace: except.StackTrace,
   538  	}
   539  	assert.Equal(expectedErr, except)
   540  
   541  	// Closing quoted not following by `;`
   542  	ele, err = envoyutil.ParseXFCC(`a="b"---`)
   543  	assert.Equal(envoyutil.XFCC{}, ele)
   544  	except, ok = err.(*ex.Ex)
   545  	assert.True(ok)
   546  	assert.NotNil(except)
   547  	expectedErr = &ex.Ex{
   548  		Class:      envoyutil.ErrXFCCParsing,
   549  		Message:    "Closing quote not followed by `;`.",
   550  		StackTrace: except.StackTrace,
   551  	}
   552  	assert.Equal(expectedErr, except)
   553  
   554  	// Escaped quotes and other characters work as expected.
   555  	ele, err = envoyutil.ParseXFCC(`By="a,b=10";URI="c; \"then\" again"`)
   556  	assert.Nil(err)
   557  	expected = envoyutil.XFCC{
   558  		envoyutil.XFCCElement{
   559  			By:  "a,b=10",
   560  			URI: `c; "then" again`,
   561  		},
   562  	}
   563  	assert.Equal(expected, ele)
   564  
   565  	// Bare escape character works fine (when not followed by a quote).
   566  	ele, err = envoyutil.ParseXFCC(`By="first\tsecond"`)
   567  	assert.Nil(err)
   568  	expected = envoyutil.XFCC{
   569  		envoyutil.XFCCElement{
   570  			By: "first\\tsecond",
   571  		},
   572  	}
   573  	assert.Equal(expected, ele)
   574  
   575  	xfcc, err := envoyutil.ParseXFCC(fullXFCCTest + "," + fullXFCCTest)
   576  	assert.Nil(err)
   577  	expected = envoyutil.XFCC{
   578  		envoyutil.XFCCElement{
   579  			By:      "spiffe://cluster.local/ns/blend/sa/yule",
   580  			Hash:    "468ed33be74eee6556d90c0149c1309e9ba61d6425303443c0748a02dd8de688",
   581  			Subject: "/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=Test Client",
   582  			URI:     "spiffe://cluster.local/ns/blend/sa/cheer",
   583  		},
   584  		envoyutil.XFCCElement{
   585  			By:      "spiffe://cluster.local/ns/blend/sa/yule",
   586  			Hash:    "468ed33be74eee6556d90c0149c1309e9ba61d6425303443c0748a02dd8de688",
   587  			Subject: "/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=Test Client",
   588  			URI:     "spiffe://cluster.local/ns/blend/sa/cheer",
   589  		},
   590  	}
   591  	assert.Equal(expected, xfcc)
   592  
   593  	ele, err = envoyutil.ParseXFCC(xfccElementMalformedKeyTest)
   594  	except, ok = err.(*ex.Ex)
   595  	assert.True(ok)
   596  	assert.NotNil(except)
   597  	expectedErr = &ex.Ex{
   598  		Class:      envoyutil.ErrXFCCParsing,
   599  		Message:    "Key or value found but not both",
   600  		StackTrace: except.StackTrace,
   601  	}
   602  	assert.Equal(expectedErr, except)
   603  	assert.Equal(envoyutil.XFCC{}, ele)
   604  
   605  	// Quoted value at element boundary.
   606  	xfcc, err = envoyutil.ParseXFCC(`by="me",uri=you`)
   607  	assert.Nil(err)
   608  	expected = envoyutil.XFCC{
   609  		envoyutil.XFCCElement{By: "me"},
   610  		envoyutil.XFCCElement{URI: "you"},
   611  	}
   612  	assert.Equal(expected, xfcc)
   613  
   614  	// KV separator is the last character
   615  	xfcc, err = envoyutil.ParseXFCC("by=cliffhanger;")
   616  	assert.Equal(envoyutil.XFCC{}, xfcc)
   617  	except, ok = err.(*ex.Ex)
   618  	assert.True(ok)
   619  	assert.NotNil(except)
   620  	expectedErr = &ex.Ex{
   621  		Class:      envoyutil.ErrXFCCParsing,
   622  		Message:    "Ends with separator character",
   623  		StackTrace: except.StackTrace,
   624  	}
   625  	assert.Equal(expectedErr, except)
   626  	assert.Equal(envoyutil.XFCC{}, ele)
   627  
   628  	// Element separator is the last character
   629  	xfcc, err = envoyutil.ParseXFCC("uri=cliffhanger,")
   630  	assert.Equal(envoyutil.XFCC{}, xfcc)
   631  	except, ok = err.(*ex.Ex)
   632  	assert.True(ok)
   633  	assert.NotNil(except)
   634  	expectedErr = &ex.Ex{
   635  		Class:      envoyutil.ErrXFCCParsing,
   636  		Message:    "Ends with separator character",
   637  		StackTrace: except.StackTrace,
   638  	}
   639  	assert.Equal(expectedErr, except)
   640  	assert.Equal(envoyutil.XFCC{}, ele)
   641  
   642  	// Empty header
   643  	xfcc, err = envoyutil.ParseXFCC("")
   644  	assert.Equal(envoyutil.XFCC{}, xfcc)
   645  	assert.Nil(err)
   646  	assert.Equal(envoyutil.XFCC{}, ele)
   647  }