...

Source file src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper_test.go

Documentation: k8s.io/apimachinery/pkg/util/httpstream/spdy

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package spdy
    18  
    19  import (
    20  	"context"
    21  	"crypto/tls"
    22  	"crypto/x509"
    23  	"io"
    24  	"net"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"net/url"
    28  	"reflect"
    29  	"strconv"
    30  	"strings"
    31  	"testing"
    32  
    33  	"github.com/armon/go-socks5"
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  
    37  	"k8s.io/apimachinery/pkg/util/httpstream"
    38  	utilnettesting "k8s.io/apimachinery/pkg/util/net/testing"
    39  )
    40  
    41  type serverHandlerConfig struct {
    42  	shouldError      bool
    43  	statusCode       int
    44  	connectionHeader string
    45  	upgradeHeader    string
    46  }
    47  
    48  func serverHandler(t *testing.T, config serverHandlerConfig) http.HandlerFunc {
    49  	return func(w http.ResponseWriter, req *http.Request) {
    50  		if config.shouldError {
    51  			if e, a := httpstream.HeaderUpgrade, req.Header.Get(httpstream.HeaderConnection); e != a {
    52  				t.Fatalf("expected connection=upgrade header, got '%s", a)
    53  			}
    54  
    55  			w.Header().Set(httpstream.HeaderConnection, config.connectionHeader)
    56  			w.Header().Set(httpstream.HeaderUpgrade, config.upgradeHeader)
    57  			w.WriteHeader(config.statusCode)
    58  
    59  			return
    60  		}
    61  
    62  		streamCh := make(chan httpstream.Stream)
    63  
    64  		responseUpgrader := NewResponseUpgrader()
    65  		spdyConn := responseUpgrader.UpgradeResponse(w, req, func(s httpstream.Stream, replySent <-chan struct{}) error {
    66  			streamCh <- s
    67  			return nil
    68  		})
    69  		if spdyConn == nil {
    70  			t.Fatal("unexpected nil spdyConn")
    71  		}
    72  		defer spdyConn.Close()
    73  
    74  		stream := <-streamCh
    75  		io.Copy(stream, stream)
    76  	}
    77  }
    78  
    79  type serverFunc func(http.Handler) *httptest.Server
    80  
    81  func httpsServerInvalidHostname(t *testing.T) serverFunc {
    82  	return func(h http.Handler) *httptest.Server {
    83  		cert, err := tls.X509KeyPair(exampleCert, exampleKey)
    84  		if err != nil {
    85  			t.Errorf("https (invalid hostname): proxy_test: %v", err)
    86  		}
    87  		ts := httptest.NewUnstartedServer(h)
    88  		ts.TLS = &tls.Config{
    89  			Certificates: []tls.Certificate{cert},
    90  		}
    91  		ts.StartTLS()
    92  		return ts
    93  	}
    94  }
    95  
    96  func httpsServerValidHostname(t *testing.T) serverFunc {
    97  	return func(h http.Handler) *httptest.Server {
    98  		cert, err := tls.X509KeyPair(localhostCert, localhostKey)
    99  		if err != nil {
   100  			t.Errorf("https (valid hostname): proxy_test: %v", err)
   101  		}
   102  		ts := httptest.NewUnstartedServer(h)
   103  		ts.TLS = &tls.Config{
   104  			Certificates: []tls.Certificate{cert},
   105  		}
   106  		ts.StartTLS()
   107  		return ts
   108  	}
   109  }
   110  
   111  func localhostCertPool(t *testing.T) *x509.CertPool {
   112  	localhostPool := x509.NewCertPool()
   113  
   114  	if !localhostPool.AppendCertsFromPEM(localhostCert) {
   115  		t.Errorf("error setting up localhostCert pool")
   116  	}
   117  	return localhostPool
   118  }
   119  
   120  // be sure to unset environment variable https_proxy (if exported) before testing, otherwise the testing will fail unexpectedly.
   121  func TestRoundTripAndNewConnection(t *testing.T) {
   122  	localhostPool := localhostCertPool(t)
   123  
   124  	testCases := map[string]struct {
   125  		serverFunc             func(http.Handler) *httptest.Server
   126  		proxyServerFunc        func(http.Handler) *httptest.Server
   127  		proxyAuth              *url.Userinfo
   128  		clientTLS              *tls.Config
   129  		serverConnectionHeader string
   130  		serverUpgradeHeader    string
   131  		serverStatusCode       int
   132  		shouldError            bool
   133  	}{
   134  		"no headers": {
   135  			serverFunc:             httptest.NewServer,
   136  			serverConnectionHeader: "",
   137  			serverUpgradeHeader:    "",
   138  			serverStatusCode:       http.StatusSwitchingProtocols,
   139  			shouldError:            true,
   140  		},
   141  		"no upgrade header": {
   142  			serverFunc:             httptest.NewServer,
   143  			serverConnectionHeader: "Upgrade",
   144  			serverUpgradeHeader:    "",
   145  			serverStatusCode:       http.StatusSwitchingProtocols,
   146  			shouldError:            true,
   147  		},
   148  		"no connection header": {
   149  			serverFunc:             httptest.NewServer,
   150  			serverConnectionHeader: "",
   151  			serverUpgradeHeader:    "SPDY/3.1",
   152  			serverStatusCode:       http.StatusSwitchingProtocols,
   153  			shouldError:            true,
   154  		},
   155  		"no switching protocol status code": {
   156  			serverFunc:             httptest.NewServer,
   157  			serverConnectionHeader: "Upgrade",
   158  			serverUpgradeHeader:    "SPDY/3.1",
   159  			serverStatusCode:       http.StatusForbidden,
   160  			shouldError:            true,
   161  		},
   162  		"http": {
   163  			serverFunc:             httptest.NewServer,
   164  			serverConnectionHeader: "Upgrade",
   165  			serverUpgradeHeader:    "SPDY/3.1",
   166  			serverStatusCode:       http.StatusSwitchingProtocols,
   167  			shouldError:            false,
   168  		},
   169  		"https (invalid hostname + InsecureSkipVerify)": {
   170  			serverFunc:             httpsServerInvalidHostname(t),
   171  			clientTLS:              &tls.Config{InsecureSkipVerify: true},
   172  			serverConnectionHeader: "Upgrade",
   173  			serverUpgradeHeader:    "SPDY/3.1",
   174  			serverStatusCode:       http.StatusSwitchingProtocols,
   175  			shouldError:            false,
   176  		},
   177  		"https (invalid hostname + hostname verification)": {
   178  			serverFunc:             httpsServerInvalidHostname(t),
   179  			clientTLS:              &tls.Config{InsecureSkipVerify: false},
   180  			serverConnectionHeader: "Upgrade",
   181  			serverUpgradeHeader:    "SPDY/3.1",
   182  			serverStatusCode:       http.StatusSwitchingProtocols,
   183  			shouldError:            true,
   184  		},
   185  		"https (valid hostname + RootCAs)": {
   186  			serverFunc:             httpsServerValidHostname(t),
   187  			clientTLS:              &tls.Config{RootCAs: localhostPool},
   188  			serverConnectionHeader: "Upgrade",
   189  			serverUpgradeHeader:    "SPDY/3.1",
   190  			serverStatusCode:       http.StatusSwitchingProtocols,
   191  			shouldError:            false,
   192  		},
   193  		"proxied http->http": {
   194  			serverFunc:             httptest.NewServer,
   195  			proxyServerFunc:        httptest.NewServer,
   196  			serverConnectionHeader: "Upgrade",
   197  			serverUpgradeHeader:    "SPDY/3.1",
   198  			serverStatusCode:       http.StatusSwitchingProtocols,
   199  			shouldError:            false,
   200  		},
   201  		"proxied https (invalid hostname + InsecureSkipVerify) -> http": {
   202  			serverFunc:             httptest.NewServer,
   203  			proxyServerFunc:        httpsServerInvalidHostname(t),
   204  			clientTLS:              &tls.Config{InsecureSkipVerify: true},
   205  			serverConnectionHeader: "Upgrade",
   206  			serverUpgradeHeader:    "SPDY/3.1",
   207  			serverStatusCode:       http.StatusSwitchingProtocols,
   208  			shouldError:            false,
   209  		},
   210  		"proxied https with auth (invalid hostname + InsecureSkipVerify) -> http": {
   211  			serverFunc:             httptest.NewServer,
   212  			proxyServerFunc:        httpsServerInvalidHostname(t),
   213  			proxyAuth:              url.UserPassword("proxyuser", "proxypasswd"),
   214  			clientTLS:              &tls.Config{InsecureSkipVerify: true},
   215  			serverConnectionHeader: "Upgrade",
   216  			serverUpgradeHeader:    "SPDY/3.1",
   217  			serverStatusCode:       http.StatusSwitchingProtocols,
   218  			shouldError:            false,
   219  		},
   220  		"proxied https (invalid hostname + hostname verification) -> http": {
   221  			serverFunc:             httptest.NewServer,
   222  			proxyServerFunc:        httpsServerInvalidHostname(t),
   223  			clientTLS:              &tls.Config{InsecureSkipVerify: false},
   224  			serverConnectionHeader: "Upgrade",
   225  			serverUpgradeHeader:    "SPDY/3.1",
   226  			serverStatusCode:       http.StatusSwitchingProtocols,
   227  			shouldError:            true, // fails because the client doesn't trust the proxy
   228  		},
   229  		"proxied https (valid hostname + RootCAs) -> http": {
   230  			serverFunc:             httptest.NewServer,
   231  			proxyServerFunc:        httpsServerValidHostname(t),
   232  			clientTLS:              &tls.Config{RootCAs: localhostPool},
   233  			serverConnectionHeader: "Upgrade",
   234  			serverUpgradeHeader:    "SPDY/3.1",
   235  			serverStatusCode:       http.StatusSwitchingProtocols,
   236  			shouldError:            false,
   237  		},
   238  		"proxied https with auth (valid hostname + RootCAs) -> http": {
   239  			serverFunc:             httptest.NewServer,
   240  			proxyServerFunc:        httpsServerValidHostname(t),
   241  			proxyAuth:              url.UserPassword("proxyuser", "proxypasswd"),
   242  			clientTLS:              &tls.Config{RootCAs: localhostPool},
   243  			serverConnectionHeader: "Upgrade",
   244  			serverUpgradeHeader:    "SPDY/3.1",
   245  			serverStatusCode:       http.StatusSwitchingProtocols,
   246  			shouldError:            false,
   247  		},
   248  		"proxied https (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": {
   249  			serverFunc:             httpsServerInvalidHostname(t),
   250  			proxyServerFunc:        httpsServerInvalidHostname(t),
   251  			clientTLS:              &tls.Config{InsecureSkipVerify: true},
   252  			serverConnectionHeader: "Upgrade",
   253  			serverUpgradeHeader:    "SPDY/3.1",
   254  			serverStatusCode:       http.StatusSwitchingProtocols,
   255  			shouldError:            false, // works because the test proxy ignores TLS errors
   256  		},
   257  		"proxied https with auth (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": {
   258  			serverFunc:             httpsServerInvalidHostname(t),
   259  			proxyServerFunc:        httpsServerInvalidHostname(t),
   260  			proxyAuth:              url.UserPassword("proxyuser", "proxypasswd"),
   261  			clientTLS:              &tls.Config{InsecureSkipVerify: true},
   262  			serverConnectionHeader: "Upgrade",
   263  			serverUpgradeHeader:    "SPDY/3.1",
   264  			serverStatusCode:       http.StatusSwitchingProtocols,
   265  			shouldError:            false, // works because the test proxy ignores TLS errors
   266  		},
   267  		"proxied https (invalid hostname + hostname verification) -> https (invalid hostname)": {
   268  			serverFunc:             httpsServerInvalidHostname(t),
   269  			proxyServerFunc:        httpsServerInvalidHostname(t),
   270  			clientTLS:              &tls.Config{InsecureSkipVerify: false},
   271  			serverConnectionHeader: "Upgrade",
   272  			serverUpgradeHeader:    "SPDY/3.1",
   273  			serverStatusCode:       http.StatusSwitchingProtocols,
   274  			shouldError:            true, // fails because the client doesn't trust the proxy
   275  		},
   276  		"proxied https (valid hostname + RootCAs) -> https (valid hostname + RootCAs)": {
   277  			serverFunc:             httpsServerValidHostname(t),
   278  			proxyServerFunc:        httpsServerValidHostname(t),
   279  			clientTLS:              &tls.Config{RootCAs: localhostPool},
   280  			serverConnectionHeader: "Upgrade",
   281  			serverUpgradeHeader:    "SPDY/3.1",
   282  			serverStatusCode:       http.StatusSwitchingProtocols,
   283  			shouldError:            false,
   284  		},
   285  		"proxied https with auth (valid hostname + RootCAs) -> https (valid hostname + RootCAs)": {
   286  			serverFunc:             httpsServerValidHostname(t),
   287  			proxyServerFunc:        httpsServerValidHostname(t),
   288  			proxyAuth:              url.UserPassword("proxyuser", "proxypasswd"),
   289  			clientTLS:              &tls.Config{RootCAs: localhostPool},
   290  			serverConnectionHeader: "Upgrade",
   291  			serverUpgradeHeader:    "SPDY/3.1",
   292  			serverStatusCode:       http.StatusSwitchingProtocols,
   293  			shouldError:            false,
   294  		},
   295  		"proxied valid https, proxy auth with chars that percent escape -> valid https": {
   296  			serverFunc:             httpsServerValidHostname(t),
   297  			proxyServerFunc:        httpsServerValidHostname(t),
   298  			proxyAuth:              url.UserPassword("proxy user", "proxypasswd%"),
   299  			clientTLS:              &tls.Config{RootCAs: localhostPool},
   300  			serverConnectionHeader: "Upgrade",
   301  			serverUpgradeHeader:    "SPDY/3.1",
   302  			serverStatusCode:       http.StatusSwitchingProtocols,
   303  			shouldError:            false,
   304  		},
   305  	}
   306  
   307  	for k, testCase := range testCases {
   308  		t.Run(k, func(t *testing.T) {
   309  			server := testCase.serverFunc(serverHandler(
   310  				t, serverHandlerConfig{
   311  					shouldError:      testCase.shouldError,
   312  					statusCode:       testCase.serverStatusCode,
   313  					connectionHeader: testCase.serverConnectionHeader,
   314  					upgradeHeader:    testCase.serverUpgradeHeader,
   315  				},
   316  			))
   317  			defer server.Close()
   318  			t.Logf("Server URL: %v", server.URL)
   319  
   320  			serverURL, err := url.Parse(server.URL)
   321  			if err != nil {
   322  				t.Fatalf("error creating request: %s", err)
   323  			}
   324  			req, err := http.NewRequest("GET", server.URL, nil)
   325  			if err != nil {
   326  				t.Fatalf("error creating request: %s", err)
   327  			}
   328  
   329  			spdyTransport, err := NewRoundTripper(testCase.clientTLS)
   330  			if err != nil {
   331  				t.Fatalf("error creating SpdyRoundTripper: %v", err)
   332  			}
   333  
   334  			var proxierCalled bool
   335  			var proxyCalledWithHost string
   336  			var proxyCalledWithAuth bool
   337  			var proxyCalledWithAuthHeader string
   338  			if testCase.proxyServerFunc != nil {
   339  				proxyHandler := utilnettesting.NewHTTPProxyHandler(t, func(req *http.Request) bool {
   340  					proxyCalledWithHost = req.Host
   341  
   342  					proxyAuthHeaderName := "Proxy-Authorization"
   343  					_, proxyCalledWithAuth = req.Header[proxyAuthHeaderName]
   344  					proxyCalledWithAuthHeader = req.Header.Get(proxyAuthHeaderName)
   345  					return true
   346  				})
   347  				defer proxyHandler.Wait()
   348  
   349  				proxy := testCase.proxyServerFunc(proxyHandler)
   350  				defer proxy.Close()
   351  
   352  				t.Logf("Proxy URL: %v", proxy.URL)
   353  
   354  				spdyTransport.proxier = func(proxierReq *http.Request) (*url.URL, error) {
   355  					proxierCalled = true
   356  					proxyURL, err := url.Parse(proxy.URL)
   357  					if err != nil {
   358  						return nil, err
   359  					}
   360  					proxyURL.User = testCase.proxyAuth
   361  					return proxyURL, nil
   362  				}
   363  			}
   364  
   365  			client := &http.Client{Transport: spdyTransport}
   366  
   367  			resp, err := client.Do(req)
   368  			var conn httpstream.Connection
   369  			if err == nil {
   370  				conn, err = spdyTransport.NewConnection(resp)
   371  			}
   372  			haveErr := err != nil
   373  			if e, a := testCase.shouldError, haveErr; e != a {
   374  				t.Fatalf("shouldError=%t, got %t: %v", e, a, err)
   375  			}
   376  			if testCase.shouldError {
   377  				return
   378  			}
   379  			defer conn.Close()
   380  
   381  			if resp.StatusCode != http.StatusSwitchingProtocols {
   382  				t.Fatalf("expected http 101 switching protocols, got %d", resp.StatusCode)
   383  			}
   384  
   385  			stream, err := conn.CreateStream(http.Header{})
   386  			if err != nil {
   387  				t.Fatalf("error creating client stream: %s", err)
   388  			}
   389  
   390  			n, err := stream.Write([]byte("hello"))
   391  			if err != nil {
   392  				t.Fatalf("error writing to stream: %s", err)
   393  			}
   394  			if n != 5 {
   395  				t.Fatalf("expected to write 5 bytes, but actually wrote %d", n)
   396  			}
   397  
   398  			b := make([]byte, 5)
   399  			n, err = stream.Read(b)
   400  			if err != nil {
   401  				t.Fatalf("error reading from stream: %s", err)
   402  			}
   403  			if n != 5 {
   404  				t.Fatalf("expected to read 5 bytes, but actually read %d", n)
   405  			}
   406  			if e, a := "hello", string(b[0:n]); e != a {
   407  				t.Fatalf("expected '%s', got '%s'", e, a)
   408  			}
   409  
   410  			if testCase.proxyServerFunc != nil {
   411  				if !proxierCalled {
   412  					t.Fatal("expected to use a proxy but proxier in SpdyRoundTripper wasn't called")
   413  				}
   414  				if proxyCalledWithHost != serverURL.Host {
   415  					t.Fatalf("expected to see a call to the proxy for backend %q, got %q", serverURL.Host, proxyCalledWithHost)
   416  				}
   417  			}
   418  
   419  			if testCase.proxyAuth != nil {
   420  				expectedUsername := testCase.proxyAuth.Username()
   421  				expectedPassword, _ := testCase.proxyAuth.Password()
   422  				username, password, ok := (&http.Request{Header: http.Header{"Authorization": []string{proxyCalledWithAuthHeader}}}).BasicAuth()
   423  				if !ok {
   424  					t.Fatalf("invalid proxy auth header %s", proxyCalledWithAuthHeader)
   425  				}
   426  				if username != expectedUsername || password != expectedPassword {
   427  					t.Fatalf("expected proxy auth \"%s:%s\", got \"%s:%s\"", expectedUsername, expectedPassword, username, password)
   428  				}
   429  			} else if proxyCalledWithAuth {
   430  				t.Fatalf("proxy authorization unexpected, got %q", proxyCalledWithAuthHeader)
   431  			}
   432  		})
   433  	}
   434  }
   435  
   436  // Tests SpdyRoundTripper constructors
   437  func TestRoundTripConstuctor(t *testing.T) {
   438  	testCases := map[string]struct {
   439  		tlsConfig         *tls.Config
   440  		proxier           func(req *http.Request) (*url.URL, error)
   441  		upgradeTransport  http.RoundTripper
   442  		expectedTLSConfig *tls.Config
   443  		errMsg            string
   444  	}{
   445  		"Basic TLSConfig; no error": {
   446  			tlsConfig:         &tls.Config{InsecureSkipVerify: true},
   447  			expectedTLSConfig: &tls.Config{InsecureSkipVerify: true},
   448  			upgradeTransport:  nil,
   449  		},
   450  		"Basic TLSConfig and Proxier: no error": {
   451  			tlsConfig:         &tls.Config{InsecureSkipVerify: true},
   452  			proxier:           func(req *http.Request) (*url.URL, error) { return nil, nil },
   453  			expectedTLSConfig: &tls.Config{InsecureSkipVerify: true},
   454  			upgradeTransport:  nil,
   455  		},
   456  		"TLSConfig with UpgradeTransport: error": {
   457  			tlsConfig:         &tls.Config{InsecureSkipVerify: true},
   458  			upgradeTransport:  &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
   459  			expectedTLSConfig: &tls.Config{InsecureSkipVerify: true},
   460  			errMsg:            "SpdyRoundTripper: UpgradeTransport is mutually exclusive to TLSConfig or Proxier",
   461  		},
   462  		"Proxier with UpgradeTransport: error": {
   463  			proxier:           func(req *http.Request) (*url.URL, error) { return nil, nil },
   464  			upgradeTransport:  &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
   465  			expectedTLSConfig: &tls.Config{InsecureSkipVerify: true},
   466  			errMsg:            "SpdyRoundTripper: UpgradeTransport is mutually exclusive to TLSConfig or Proxier",
   467  		},
   468  		"Only UpgradeTransport: no error": {
   469  			upgradeTransport:  &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
   470  			expectedTLSConfig: &tls.Config{InsecureSkipVerify: true},
   471  		},
   472  	}
   473  	for name, testCase := range testCases {
   474  		t.Run(name, func(t *testing.T) {
   475  			spdyRoundTripper, err := NewRoundTripperWithConfig(
   476  				RoundTripperConfig{
   477  					TLS:              testCase.tlsConfig,
   478  					Proxier:          testCase.proxier,
   479  					UpgradeTransport: testCase.upgradeTransport,
   480  				},
   481  			)
   482  			if testCase.errMsg != "" {
   483  				if err == nil {
   484  					t.Fatalf("expected error but received none")
   485  				}
   486  				if !strings.Contains(err.Error(), testCase.errMsg) {
   487  					t.Fatalf("expected error message (%s), got (%s)", err.Error(), testCase.errMsg)
   488  				}
   489  			}
   490  			if testCase.errMsg == "" {
   491  				if err != nil {
   492  					t.Fatalf("unexpected error received: %v", err)
   493  				}
   494  				actualTLSConfig := spdyRoundTripper.TLSClientConfig()
   495  				if !reflect.DeepEqual(testCase.expectedTLSConfig, actualTLSConfig) {
   496  					t.Errorf("expected TLSConfig (%v), got (%v)",
   497  						testCase.expectedTLSConfig, actualTLSConfig)
   498  				}
   499  			}
   500  		})
   501  	}
   502  }
   503  
   504  type Interceptor struct {
   505  	Authorization       socks5.AuthContext
   506  	proxyCalledWithHost *string
   507  }
   508  
   509  func (i *Interceptor) GetAuthContext() (int, map[string]string) {
   510  	return int(i.Authorization.Method), i.Authorization.Payload
   511  }
   512  
   513  func (i *Interceptor) Rewrite(ctx context.Context, req *socks5.Request) (context.Context, *socks5.AddrSpec) {
   514  	*i.proxyCalledWithHost = req.DestAddr.Address()
   515  	i.Authorization = socks5.AuthContext(*req.AuthContext)
   516  	return ctx, req.DestAddr
   517  }
   518  
   519  // be sure to unset environment variable https_proxy (if exported) before testing, otherwise the testing will fail unexpectedly.
   520  func TestRoundTripSocks5AndNewConnection(t *testing.T) {
   521  	localhostPool := localhostCertPool(t)
   522  
   523  	socks5Server := func(creds *socks5.StaticCredentials, interceptor *Interceptor) *socks5.Server {
   524  		var conf *socks5.Config
   525  		if creds != nil {
   526  			authenticator := socks5.UserPassAuthenticator{Credentials: creds}
   527  			conf = &socks5.Config{
   528  				AuthMethods: []socks5.Authenticator{authenticator},
   529  				Rewriter:    interceptor,
   530  			}
   531  		} else {
   532  			conf = &socks5.Config{Rewriter: interceptor}
   533  		}
   534  
   535  		ts, err := socks5.New(conf)
   536  		if err != nil {
   537  			t.Errorf("failed to create sock5 server: %v", err)
   538  		}
   539  		return ts
   540  	}
   541  
   542  	testCases := map[string]struct {
   543  		clientTLS              *tls.Config
   544  		proxyAuth              *url.Userinfo
   545  		serverConnectionHeader string
   546  		serverFunc             serverFunc
   547  		serverStatusCode       int
   548  		serverUpgradeHeader    string
   549  		shouldError            bool
   550  	}{
   551  		"proxied without auth -> http": {
   552  			serverFunc:             httptest.NewServer,
   553  			serverConnectionHeader: "Upgrade",
   554  			serverStatusCode:       http.StatusSwitchingProtocols,
   555  			serverUpgradeHeader:    "SPDY/3.1",
   556  			shouldError:            false,
   557  		},
   558  		"proxied with invalid auth -> http": {
   559  			serverFunc:             httptest.NewServer,
   560  			proxyAuth:              url.UserPassword("invalid", "auth"),
   561  			serverConnectionHeader: "Upgrade",
   562  			serverStatusCode:       http.StatusSwitchingProtocols,
   563  			serverUpgradeHeader:    "SPDY/3.1",
   564  			shouldError:            true,
   565  		},
   566  		"proxied with valid auth -> http": {
   567  			serverFunc:             httptest.NewServer,
   568  			proxyAuth:              url.UserPassword("proxyuser", "proxypasswd"),
   569  			serverConnectionHeader: "Upgrade",
   570  			serverStatusCode:       http.StatusSwitchingProtocols,
   571  			serverUpgradeHeader:    "SPDY/3.1",
   572  			shouldError:            false,
   573  		},
   574  		"proxied with valid auth -> https (invalid hostname + InsecureSkipVerify)": {
   575  			serverFunc:             httpsServerInvalidHostname(t),
   576  			proxyAuth:              url.UserPassword("proxyuser", "proxypasswd"),
   577  			clientTLS:              &tls.Config{InsecureSkipVerify: true},
   578  			serverConnectionHeader: "Upgrade",
   579  			serverUpgradeHeader:    "SPDY/3.1",
   580  			serverStatusCode:       http.StatusSwitchingProtocols,
   581  			shouldError:            false,
   582  		},
   583  		"proxied with valid auth -> https (invalid hostname + hostname verification)": {
   584  			serverFunc:             httpsServerInvalidHostname(t),
   585  			proxyAuth:              url.UserPassword("proxyuser", "proxypasswd"),
   586  			clientTLS:              &tls.Config{InsecureSkipVerify: false},
   587  			serverConnectionHeader: "Upgrade",
   588  			serverUpgradeHeader:    "SPDY/3.1",
   589  			serverStatusCode:       http.StatusSwitchingProtocols,
   590  			shouldError:            true,
   591  		},
   592  		"proxied with valid auth -> https (valid hostname + RootCAs)": {
   593  			serverFunc:             httpsServerValidHostname(t),
   594  			proxyAuth:              url.UserPassword("proxyuser", "proxypasswd"),
   595  			clientTLS:              &tls.Config{RootCAs: localhostPool},
   596  			serverConnectionHeader: "Upgrade",
   597  			serverUpgradeHeader:    "SPDY/3.1",
   598  			serverStatusCode:       http.StatusSwitchingProtocols,
   599  			shouldError:            false,
   600  		},
   601  	}
   602  
   603  	for name, testCase := range testCases {
   604  		t.Run(name, func(t *testing.T) {
   605  			server := testCase.serverFunc(serverHandler(
   606  				t, serverHandlerConfig{
   607  					shouldError:      testCase.shouldError,
   608  					statusCode:       testCase.serverStatusCode,
   609  					connectionHeader: testCase.serverConnectionHeader,
   610  					upgradeHeader:    testCase.serverUpgradeHeader,
   611  				},
   612  			))
   613  			defer server.Close()
   614  
   615  			req, err := http.NewRequest("GET", server.URL, nil)
   616  			if err != nil {
   617  				t.Fatalf("error creating request: %s", err)
   618  			}
   619  
   620  			spdyTransport, err := NewRoundTripper(testCase.clientTLS)
   621  			if err != nil {
   622  				t.Fatalf("error creating SpdyRoundTripper: %v", err)
   623  			}
   624  			var proxierCalled bool
   625  			var proxyCalledWithHost string
   626  
   627  			interceptor := &Interceptor{proxyCalledWithHost: &proxyCalledWithHost}
   628  
   629  			proxyHandler := socks5Server(nil, interceptor)
   630  
   631  			if testCase.proxyAuth != nil {
   632  				proxyHandler = socks5Server(&socks5.StaticCredentials{
   633  					"proxyuser": "proxypasswd", // Socks5 server static credentials when client authentication is expected
   634  				}, interceptor)
   635  			}
   636  
   637  			closed := make(chan struct{})
   638  			isClosed := func() bool {
   639  				select {
   640  				case <-closed:
   641  					return true
   642  				default:
   643  					return false
   644  				}
   645  			}
   646  
   647  			l, err := net.Listen("tcp", "127.0.0.1:0")
   648  			if err != nil {
   649  				t.Fatalf("socks5Server: proxy_test: Listen: %v", err)
   650  			}
   651  			defer l.Close()
   652  
   653  			go func(shoulderror bool) {
   654  				conn, err := l.Accept()
   655  				if err != nil {
   656  					if isClosed() {
   657  						return
   658  					}
   659  
   660  					t.Errorf("error accepting connection: %s", err)
   661  				}
   662  
   663  				if err := proxyHandler.ServeConn(conn); err != nil && !shoulderror {
   664  					// If the connection request is closed before the channel is closed
   665  					// the test will fail with a ServeConn error. Since the test only return
   666  					// early if expects shouldError=true, the channel is closed at the end of
   667  					// the test, just before all the deferred connections Close() are executed.
   668  					if isClosed() {
   669  						return
   670  					}
   671  
   672  					t.Errorf("ServeConn error: %s", err)
   673  				}
   674  			}(testCase.shouldError)
   675  			spdyTransport.proxier = func(proxierReq *http.Request) (*url.URL, error) {
   676  				proxierCalled = true
   677  				return &url.URL{
   678  					Scheme: "socks5",
   679  					Host:   net.JoinHostPort("127.0.0.1", strconv.Itoa(l.Addr().(*net.TCPAddr).Port)),
   680  					User:   testCase.proxyAuth,
   681  				}, nil
   682  			}
   683  
   684  			client := &http.Client{Transport: spdyTransport}
   685  
   686  			resp, err := client.Do(req)
   687  			haveErr := err != nil
   688  			if e, a := testCase.shouldError, haveErr; e != a {
   689  				t.Fatalf("shouldError=%t, got %t: %v", e, a, err)
   690  			}
   691  			if testCase.shouldError {
   692  				return
   693  			}
   694  
   695  			conn, err := spdyTransport.NewConnection(resp)
   696  			haveErr = err != nil
   697  			if e, a := testCase.shouldError, haveErr; e != a {
   698  				t.Fatalf("shouldError=%t, got %t: %v", e, a, err)
   699  			}
   700  			if testCase.shouldError {
   701  				return
   702  			}
   703  
   704  			defer conn.Close()
   705  
   706  			if resp.StatusCode != http.StatusSwitchingProtocols {
   707  				t.Fatalf("expected http 101 switching protocols, got %d", resp.StatusCode)
   708  			}
   709  
   710  			stream, err := conn.CreateStream(http.Header{})
   711  			if err != nil {
   712  				t.Fatalf("error creating client stream: %s", err)
   713  			}
   714  
   715  			n, err := stream.Write([]byte("hello"))
   716  			if err != nil {
   717  				t.Fatalf("error writing to stream: %s", err)
   718  			}
   719  			if n != 5 {
   720  				t.Fatalf("expected to write 5 bytes, but actually wrote %d", n)
   721  			}
   722  
   723  			b := make([]byte, 5)
   724  			n, err = stream.Read(b)
   725  			if err != nil {
   726  				t.Fatalf("error reading from stream: %s", err)
   727  			}
   728  			if n != 5 {
   729  				t.Fatalf("expected to read 5 bytes, but actually read %d", n)
   730  			}
   731  			if e, a := "hello", string(b[0:n]); e != a {
   732  				t.Fatalf("expected '%s', got '%s'", e, a)
   733  			}
   734  
   735  			if !proxierCalled {
   736  				t.Fatal("xpected to use a proxy but proxier in SpdyRoundTripper wasn't called")
   737  			}
   738  
   739  			serverURL, err := url.Parse(server.URL)
   740  			if err != nil {
   741  				t.Fatalf("error creating request: %s", err)
   742  			}
   743  			if proxyCalledWithHost != serverURL.Host {
   744  				t.Fatalf("expected to see a call to the proxy for backend %q, got %q", serverURL.Host, proxyCalledWithHost)
   745  			}
   746  
   747  			authMethod, authUser := interceptor.GetAuthContext()
   748  
   749  			if testCase.proxyAuth != nil {
   750  				expectedSocks5AuthMethod := 2
   751  				expectedSocks5AuthUser := "proxyuser"
   752  
   753  				if expectedSocks5AuthMethod != authMethod {
   754  					t.Fatalf("socks5 Proxy authorization unexpected, got %d, expected %d", authMethod, expectedSocks5AuthMethod)
   755  				}
   756  
   757  				if expectedSocks5AuthUser != authUser["Username"] {
   758  					t.Fatalf("socks5 Proxy authorization user unexpected, got %q, expected %q", authUser["Username"], expectedSocks5AuthUser)
   759  				}
   760  			} else {
   761  				if authMethod != 0 {
   762  					t.Fatalf("proxy authentication method unexpected, got %d", authMethod)
   763  				}
   764  				if len(authUser) != 0 {
   765  					t.Fatalf("unexpected proxy user: %v", authUser)
   766  				}
   767  			}
   768  
   769  			// The channel must be closed before any of the connections are closed
   770  			close(closed)
   771  		})
   772  	}
   773  }
   774  
   775  func TestRoundTripPassesContextToDialer(t *testing.T) {
   776  	urls := []string{"http://127.0.0.1:1233/", "https://127.0.0.1:1233/"}
   777  	for _, u := range urls {
   778  		t.Run(u, func(t *testing.T) {
   779  			ctx, cancel := context.WithCancel(context.Background())
   780  			cancel()
   781  			req, err := http.NewRequestWithContext(ctx, "GET", u, nil)
   782  			require.NoError(t, err)
   783  			spdyTransport, err := NewRoundTripper(&tls.Config{})
   784  			if err != nil {
   785  				t.Fatalf("error creating SpdyRoundTripper: %v", err)
   786  			}
   787  			_, err = spdyTransport.Dial(req)
   788  			assert.EqualError(t, err, "dial tcp 127.0.0.1:1233: operation was canceled")
   789  		})
   790  	}
   791  }
   792  
   793  // exampleCert was generated from crypto/tls/generate_cert.go with the following command:
   794  //
   795  //	go run generate_cert.go  --rsa-bits 2048 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
   796  var exampleCert = []byte(`-----BEGIN CERTIFICATE-----
   797  MIIDADCCAeigAwIBAgIQVHG3Fn9SdWayyLOZKCW1vzANBgkqhkiG9w0BAQsFADAS
   798  MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
   799  MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
   800  MIIBCgKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9p+tn
   801  zm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/JlNy
   802  +i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj7aMi
   803  vXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJOoUG
   804  f8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb+9he
   805  7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABo1AwTjAOBgNVHQ8BAf8EBAMC
   806  AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREE
   807  DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAig4AIi9xWs1+pLES
   808  eeGGdSDoclplFpcbXANnsYYFyLf+8pcWgVi2bOmb2gXMbHFkB07MA82wRJAUTaA+
   809  2iNXVQMhPCoA7J6ADUbww9doJX2S9HGyArhiV/MhHtE8txzMn2EKNLdhhk3N9rmV
   810  x/qRbWAY1U2z4BpdrAR87Fe81Nlj7h45csW9K+eS+NgXipiNTIfEShKgCFM8EdxL
   811  1WXg7r9AvYV3TNDPWTjLsm1rQzzZQ7Uvcf6deWiNodZd8MOT/BFLclDPTK6cF2Hr
   812  UU4dq6G4kCwMSxWE4cM3HlZ4u1dyIt47VbkP0rtvkBCXx36y+NXYA5lzntchNFZP
   813  uvEQdw==
   814  -----END CERTIFICATE-----`)
   815  
   816  var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
   817  MIIEpQIBAAKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9
   818  p+tnzm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/
   819  JlNy+i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj
   820  7aMivXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJ
   821  OoUGf8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb
   822  +9he7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABAoIBAQClt4CiYaaF5ltx
   823  wVDjz6TNcJUBUs3CKE+uWAYFnF5Ii1nyU876Pxj8Aaz9fHZ6Kde0GkwiXY7gFOj1
   824  YHo2tzcELSKS/SEDZcYbYFTGCjq13g1AH74R+SV6WZLn+5m8kPvVrM1ZWap188H5
   825  bmuCkRDqVmIvShkbRW7EwhC35J9fiuW3majC/sjmsxtxyP6geWmu4f5/Ttqahcdb
   826  osPZIgIIPzqAkNtkLTi7+meHYI9wlrGhL7XZTwnJ1Oc/Y67zzmbthLYB5YFSLUew
   827  rXT58jtSjX4gbiQyheBSrWxW08QE4qYg6jJlAdffHhWv72hJW2MCXhuXp8gJs/Do
   828  XLRHGwSBAoGBAMdNtsbe4yae/QeHUPGxNW0ipa0yoTF6i+VYoxvqiRMzDM3+3L8k
   829  dgI1rr4330SivqDahMA/odWtM/9rVwJI2B2QhZLMHA0n9ytH007OO9TghgVB12nN
   830  xosRYBpKdHXyyvV/MUZl7Jux6zKIzRDWOkF95VVYPcAaxJqd1E5/jJ6JAoGBAN51
   831  QrebA1w/jfydeqQTz1sK01sbO4HYj4qGfo/JarVqGEkm1azeBBPPRnHz3jNKnCkM
   832  S4PpqRDased3NIcViXlAgoqPqivZ8mQa/Rb146l7WaTErASHsZ023OGrxsr/Ed6N
   833  P3GrmvxVJjebaFNaQ9sP80dLkpgeas0t2TY8iQNRAoGATOcnx8TpUVW3vNfx29DN
   834  FLdxxkrq9/SZVn3FMlhlXAsuva3B799ZybB9JNjaRdmmRNsMrkHfaFvU3JHGmRMS
   835  kRXa9LHdgRYSwZiNaLMbUyDvlce6HxFPswmZU4u3NGvi9KeHk+pwSgN1BaLTvdNr
   836  1ymE/FF4QlAR3LdZ3JBK6kECgYEA0wW4/CJ31ZIURoW8SNjh4iMqy0nR8SJVR7q9
   837  Y/hU2TKDRyEnoIwaohAFayNCrLUh3W5kVAXa8roB+OgDVAECH5sqOfZ+HorofD19
   838  x8II7ESujLZj1whBXDkm3ovsT7QWZ17lyBZZNvQvBKDPHgKKS8udowv1S4fPGENd
   839  wS07a4ECgYEAwLSbmMIVJme0jFjsp5d1wOGA2Qi2ZwGIAVlsbnJtygrU/hSBfnu8
   840  VfyJSCgg3fPe7kChWKlfcOebVKSb68LKRsz1Lz1KdbY0HOJFp/cT4lKmDAlRY9gq
   841  LB4rdf46lV0mUkvd2/oofIbTrzukjQSnyfLawb/2uJGV1IkTcZcn9CI=
   842  -----END RSA PRIVATE KEY-----`)
   843  
   844  // localhostCert was generated from crypto/tls/generate_cert.go with the following command:
   845  //
   846  //	go run generate_cert.go  --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
   847  var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
   848  MIIDGTCCAgGgAwIBAgIRALL5AZcefF4kkYV1SEG6YrMwDQYJKoZIhvcNAQELBQAw
   849  EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
   850  MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP
   851  ADCCAQoCggEBALQ/FHcyVwdFHxARbbD2KBtDUT7Eni+8ioNdjtGcmtXqBv45EC1C
   852  JOqqGJTroFGJ6Q9kQIZ9FqH5IJR2fOOJD9kOTueG4Vt1JY1rj1Kbpjefu8XleZ5L
   853  SBwIWVnN/lEsEbuKmj7N2gLt5AH3zMZiBI1mg1u9Z5ZZHYbCiTpBrwsq6cTlvR9g
   854  dyo1YkM5hRESCzsrL0aUByoo0qRMD8ZsgANJwgsiO0/M6idbxDwv1BnGwGmRYvOE
   855  Hxpy3v0Jg7GJYrvnpnifJTs4nw91N5X9pXxR7FFzi/6HTYDWRljvTb0w6XciKYAz
   856  bWZ0+cJr5F7wB7ovlbm7HrQIR7z7EIIu2d8CAwEAAaNoMGYwDgYDVR0PAQH/BAQD
   857  AgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0R
   858  BCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI
   859  hvcNAQELBQADggEBAFPPWopNEJtIA2VFAQcqN6uJK+JVFOnjGRoCrM6Xgzdm0wxY
   860  XCGjsxY5dl+V7KzdGqu858rCaq5osEBqypBpYAnS9C38VyCDA1vPS1PsN8SYv48z
   861  DyBwj+7R2qar0ADBhnhWxvYO9M72lN/wuCqFKYMeFSnJdQLv3AsrrHe9lYqOa36s
   862  8wxSwVTFTYXBzljPEnSaaJMPqFD8JXaZK1ryJPkO5OsCNQNGtatNiWAf3DcmwHAT
   863  MGYMzP0u4nw47aRz9shB8w+taPKHx2BVwE1m/yp3nHVioOjXqA1fwRQVGclCJSH1
   864  D2iq3hWVHRENgjTjANBPICLo9AZ4JfN6PH19mnU=
   865  -----END CERTIFICATE-----`)
   866  
   867  // localhostKey is the private key for localhostCert.
   868  var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
   869  MIIEogIBAAKCAQEAtD8UdzJXB0UfEBFtsPYoG0NRPsSeL7yKg12O0Zya1eoG/jkQ
   870  LUIk6qoYlOugUYnpD2RAhn0WofkglHZ844kP2Q5O54bhW3UljWuPUpumN5+7xeV5
   871  nktIHAhZWc3+USwRu4qaPs3aAu3kAffMxmIEjWaDW71nllkdhsKJOkGvCyrpxOW9
   872  H2B3KjViQzmFERILOysvRpQHKijSpEwPxmyAA0nCCyI7T8zqJ1vEPC/UGcbAaZFi
   873  84QfGnLe/QmDsYliu+emeJ8lOzifD3U3lf2lfFHsUXOL/odNgNZGWO9NvTDpdyIp
   874  gDNtZnT5wmvkXvAHui+VubsetAhHvPsQgi7Z3wIDAQABAoIBAGmw93IxjYCQ0ncc
   875  kSKMJNZfsdtJdaxuNRZ0nNNirhQzR2h403iGaZlEpmdkhzxozsWcto1l+gh+SdFk
   876  bTUK4MUZM8FlgO2dEqkLYh5BcMT7ICMZvSfJ4v21E5eqR68XVUqQKoQbNvQyxFk3
   877  EddeEGdNrkb0GDK8DKlBlzAW5ep4gjG85wSTjR+J+muUv3R0BgLBFSuQnIDM/IMB
   878  LWqsja/QbtB7yppe7jL5u8UCFdZG8BBKT9fcvFIu5PRLO3MO0uOI7LTc8+W1Xm23
   879  uv+j3SY0+v+6POjK0UlJFFi/wkSPTFIfrQO1qFBkTDQHhQ6q/7GnILYYOiGbIRg2
   880  NNuP52ECgYEAzXEoy50wSYh8xfFaBuxbm3ruuG2W49jgop7ZfoFrPWwOQKAZS441
   881  VIwV4+e5IcA6KkuYbtGSdTYqK1SMkgnUyD/VevwAqH5TJoEIGu0pDuKGwVuwqioZ
   882  frCIAV5GllKyUJ55VZNbRr2vY2fCsWbaCSCHETn6C16DNuTCe5C0JBECgYEA4JqY
   883  5GpNbMG8fOt4H7hU0Fbm2yd6SHJcQ3/9iimef7xG6ajxsYrIhg1ft+3IPHMjVI0+
   884  9brwHDnWg4bOOx/VO4VJBt6Dm/F33bndnZRkuIjfSNpLM51P+EnRdaFVHOJHwKqx
   885  uF69kihifCAG7YATgCveeXImzBUSyZUz9UrETu8CgYARNBimdFNG1RcdvEg9rC0/
   886  p9u1tfecvNySwZqU7WF9kz7eSonTueTdX521qAHowaAdSpdJMGODTTXaywm6cPhQ
   887  jIfj9JZZhbqQzt1O4+08Qdvm9TamCUB5S28YLjza+bHU7nBaqixKkDfPqzCyilpX
   888  yVGGL8SwjwmN3zop/sQXAQKBgC0JMsESQ6YcDsRpnrOVjYQc+LtW5iEitTdfsaID
   889  iGGKihmOI7B66IxgoCHMTws39wycKdSyADVYr5e97xpR3rrJlgQHmBIrz+Iow7Q2
   890  LiAGaec8xjl6QK/DdXmFuQBKqyKJ14rljFODP4QuE9WJid94bGqjpf3j99ltznZP
   891  4J8HAoGAJb4eb4lu4UGwifDzqfAPzLGCoi0fE1/hSx34lfuLcc1G+LEu9YDKoOVJ
   892  9suOh0b5K/bfEy9KrVMBBriduvdaERSD8S3pkIQaitIz0B029AbE4FLFf9lKQpP2
   893  KR8NJEkK99Vh/tew6jAMll70xFrE7aF8VLXJVE7w4sQzuvHxl9Q=
   894  -----END RSA PRIVATE KEY-----
   895  `)
   896  

View as plain text