...

Source file src/github.com/go-openapi/runtime/client/runtime_tls_test.go

Documentation: github.com/go-openapi/runtime/client

     1  package client
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"encoding/json"
     8  	"encoding/pem"
     9  	"errors"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/url"
    13  	"os"
    14  	"path/filepath"
    15  	goruntime "runtime"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/go-openapi/runtime"
    20  	"github.com/go-openapi/strfmt"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func TestRuntimeTLSOptions(t *testing.T) {
    26  	fixtures := newTLSFixtures(t)
    27  
    28  	t.Run("with TLSAuthConfig configured with files", func(t *testing.T) {
    29  		opts := TLSClientOptions{
    30  			CA:          fixtures.RSA.CAFile,
    31  			Key:         fixtures.RSA.KeyFile,
    32  			Certificate: fixtures.RSA.CertFile,
    33  			ServerName:  fixtures.Subject,
    34  		}
    35  
    36  		cfg, err := TLSClientAuth(opts)
    37  		require.NoError(t, err)
    38  
    39  		require.NotNil(t, cfg)
    40  		assert.Len(t, cfg.Certificates, 1)
    41  		assert.NotNil(t, cfg.RootCAs)
    42  		assert.Equal(t, fixtures.Subject, cfg.ServerName)
    43  	})
    44  
    45  	t.Run("with loaded TLS material", func(t *testing.T) {
    46  		t.Run("with TLSConfig from loaded RSA key/cert pair", func(t *testing.T) {
    47  			opts := TLSClientOptions{
    48  				LoadedKey:         fixtures.RSA.LoadedKey,
    49  				LoadedCertificate: fixtures.RSA.LoadedCert,
    50  			}
    51  
    52  			cfg, err := TLSClientAuth(opts)
    53  			require.NoError(t, err)
    54  			require.NotNil(t, cfg)
    55  			assert.Len(t, cfg.Certificates, 1)
    56  		})
    57  
    58  		t.Run("with TLSAuthConfig configured with loaded TLS Elliptic Curve key/certificate", func(t *testing.T) {
    59  			opts := TLSClientOptions{
    60  				LoadedKey:         fixtures.ECDSA.LoadedKey,
    61  				LoadedCertificate: fixtures.ECDSA.LoadedCert,
    62  			}
    63  
    64  			cfg, err := TLSClientAuth(opts)
    65  			require.NoError(t, err)
    66  			require.NotNil(t, cfg)
    67  			assert.Len(t, cfg.Certificates, 1)
    68  		})
    69  
    70  		t.Run("with TLSAuthConfig configured with loaded Certificate Authority", func(t *testing.T) {
    71  			opts := TLSClientOptions{
    72  				LoadedCA: fixtures.RSA.LoadedCA,
    73  			}
    74  
    75  			cfg, err := TLSClientAuth(opts)
    76  			require.NoError(t, err)
    77  			require.NotNil(t, cfg)
    78  			assert.NotNil(t, cfg.RootCAs)
    79  		})
    80  
    81  		t.Run("with TLSAuthConfig configured with loaded CA pool", func(t *testing.T) {
    82  			pool := x509.NewCertPool()
    83  			pool.AddCert(fixtures.RSA.LoadedCA)
    84  
    85  			opts := TLSClientOptions{
    86  				LoadedCAPool: pool,
    87  			}
    88  
    89  			cfg, err := TLSClientAuth(opts)
    90  			require.NoError(t, err)
    91  			require.NotNil(t, cfg)
    92  			require.NotNil(t, cfg.RootCAs)
    93  			require.Equal(t, pool, cfg.RootCAs)
    94  		})
    95  
    96  		t.Run("with TLSAuthConfig configured with loaded CA and CA pool", func(t *testing.T) {
    97  			pool := systemCAPool(t)
    98  			opts := TLSClientOptions{
    99  				LoadedCAPool: pool,
   100  				LoadedCA:     fixtures.RSA.LoadedCA,
   101  			}
   102  
   103  			cfg, err := TLSClientAuth(opts)
   104  			require.NoError(t, err)
   105  			require.NotNil(t, cfg)
   106  			require.NotNil(t, cfg.RootCAs)
   107  
   108  			// verify that the CA cert is indeed valid against the configured pool.
   109  			// NOTE: fixtures may be expired certs, but may validate with a fixed date in the past.
   110  			chains, err := fixtures.RSA.LoadedCA.Verify(x509.VerifyOptions{
   111  				Roots:       cfg.RootCAs,
   112  				CurrentTime: time.Date(2017, 1, 1, 1, 1, 1, 1, time.UTC),
   113  			})
   114  			require.NoError(t, err)
   115  			require.NotEmpty(t, chains)
   116  		})
   117  
   118  		t.Run("with TLSAuthConfig with VerifyPeer option", func(t *testing.T) {
   119  			verify := func(_ [][]byte, _ [][]*x509.Certificate) error {
   120  				return nil
   121  			}
   122  
   123  			opts := TLSClientOptions{
   124  				InsecureSkipVerify:    true,
   125  				VerifyPeerCertificate: verify,
   126  			}
   127  
   128  			cfg, err := TLSClientAuth(opts)
   129  			require.NoError(t, err)
   130  			require.NotNil(t, cfg)
   131  			assert.True(t, cfg.InsecureSkipVerify)
   132  			assert.NotNil(t, cfg.VerifyPeerCertificate)
   133  		})
   134  	})
   135  }
   136  
   137  func TestRuntimeManualCertificateValidation(t *testing.T) {
   138  	// test manual verification of server certificates
   139  	// against root certificate on client side.
   140  	//
   141  	// The client compares the received cert against the root cert,
   142  	// explicitly omitting DNSName check.
   143  	fixtures := newTLSFixtures(t)
   144  	result := []task{
   145  		{false, "task 1 content", 1},
   146  		{false, "task 2 content", 2},
   147  	}
   148  	host, clean := testTLSServer(t, fixtures, result)
   149  	t.Cleanup(clean)
   150  	var certVerifyCalled bool
   151  	client := testTLSClient(t, fixtures, &certVerifyCalled)
   152  	rt := NewWithClient(host, "/", []string{schemeHTTPS}, client)
   153  
   154  	var received []task
   155  	operation := &runtime.ClientOperation{
   156  		ID:          "getTasks",
   157  		Method:      http.MethodGet,
   158  		PathPattern: "/",
   159  		Params: runtime.ClientRequestWriterFunc(func(_ runtime.ClientRequest, _ strfmt.Registry) error {
   160  			return nil
   161  		}),
   162  		Reader: runtime.ClientResponseReaderFunc(func(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
   163  			if response.Code() == http.StatusOK {
   164  				if e := consumer.Consume(response.Body(), &received); e != nil {
   165  					return nil, e
   166  				}
   167  				return result, nil
   168  			}
   169  			return nil, errors.New("generic error")
   170  		}),
   171  	}
   172  
   173  	resp, err := rt.Submit(operation)
   174  	require.NoError(t, err)
   175  
   176  	require.NotEmpty(t, resp)
   177  	assert.IsType(t, []task{}, resp)
   178  
   179  	assert.Truef(t, certVerifyCalled, "the client cert verification has not been called")
   180  	assert.EqualValues(t, result, received)
   181  }
   182  
   183  func testTLSServer(t testing.TB, fixtures *tlsFixtures, expectedResult []task) (string, func()) {
   184  	server := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
   185  		rw.Header().Add(runtime.HeaderContentType, runtime.JSONMime)
   186  		rw.WriteHeader(http.StatusOK)
   187  		jsongen := json.NewEncoder(rw)
   188  		require.NoError(t, jsongen.Encode(expectedResult))
   189  	}))
   190  
   191  	// create server tls config
   192  	serverCACertPool := x509.NewCertPool()
   193  	serverCACertPool.AddCert(fixtures.Server.LoadedCA)
   194  	// load server certs
   195  	serverCert, err := tls.LoadX509KeyPair(
   196  		fixtures.Server.CertFile,
   197  		fixtures.Server.KeyFile,
   198  	)
   199  	require.NoError(t, err)
   200  
   201  	server.TLS = &tls.Config{
   202  		RootCAs:      serverCACertPool,
   203  		MinVersion:   tls.VersionTLS12,
   204  		Certificates: []tls.Certificate{serverCert},
   205  	}
   206  	require.NoError(t, err)
   207  
   208  	server.StartTLS()
   209  	testURL, err := url.Parse(server.URL)
   210  	require.NoError(t, err)
   211  
   212  	return testURL.Host, server.Close
   213  }
   214  
   215  func testTLSClient(t testing.TB, fixtures *tlsFixtures, verifyCalled *bool) *http.Client {
   216  	client, err := TLSClient(TLSClientOptions{
   217  		InsecureSkipVerify: true,
   218  		VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
   219  			*verifyCalled = true
   220  
   221  			caCertPool := x509.NewCertPool()
   222  			caCertPool.AddCert(fixtures.RSA.LoadedCA)
   223  
   224  			opts := x509.VerifyOptions{
   225  				Roots:       caCertPool,
   226  				CurrentTime: time.Date(2017, time.July, 1, 1, 1, 1, 1, time.UTC),
   227  			}
   228  
   229  			cert, e := x509.ParseCertificate(rawCerts[0])
   230  			if e != nil {
   231  				return e
   232  			}
   233  
   234  			_, e = cert.Verify(opts)
   235  			return e
   236  		},
   237  	})
   238  	require.NoError(t, err)
   239  
   240  	return client
   241  }
   242  
   243  type (
   244  	tlsFixtures struct {
   245  		RSA     tlsFixture
   246  		ECDSA   tlsFixture
   247  		Server  tlsFixture
   248  		Subject string
   249  	}
   250  
   251  	tlsFixture struct {
   252  		LoadedCA   *x509.Certificate
   253  		LoadedCert *x509.Certificate
   254  		LoadedKey  crypto.PrivateKey
   255  
   256  		CAFile   string
   257  		KeyFile  string
   258  		CertFile string
   259  	}
   260  )
   261  
   262  // newTLSFixtures loads TLS material for testing
   263  func newTLSFixtures(t testing.TB) *tlsFixtures {
   264  	const subject = "somewhere"
   265  
   266  	certFixturesDir := filepath.Join("..", "fixtures", "certs")
   267  
   268  	keyFile := filepath.Join(certFixturesDir, "myclient.key")
   269  	keyPem, err := os.ReadFile(keyFile)
   270  	require.NoError(t, err)
   271  
   272  	keyDer, _ := pem.Decode(keyPem)
   273  	require.NotNil(t, keyDer)
   274  
   275  	key, err := x509.ParsePKCS1PrivateKey(keyDer.Bytes)
   276  	require.NoError(t, err)
   277  
   278  	certFile := filepath.Join(certFixturesDir, "myclient.crt")
   279  	certPem, err := os.ReadFile(certFile)
   280  	require.NoError(t, err)
   281  
   282  	certDer, _ := pem.Decode(certPem)
   283  	require.NotNil(t, certDer)
   284  
   285  	cert, err := x509.ParseCertificate(certDer.Bytes)
   286  	require.NoError(t, err)
   287  
   288  	eccKeyFile := filepath.Join(certFixturesDir, "myclient-ecc.key")
   289  	eckeyPem, err := os.ReadFile(eccKeyFile)
   290  	require.NoError(t, err)
   291  
   292  	_, remainder := pem.Decode(eckeyPem)
   293  	ecKeyDer, _ := pem.Decode(remainder)
   294  	require.NotNil(t, ecKeyDer)
   295  
   296  	ecKey, err := x509.ParseECPrivateKey(ecKeyDer.Bytes)
   297  	require.NoError(t, err)
   298  
   299  	eccCertFile := filepath.Join(certFixturesDir, "myclient-ecc.crt")
   300  	ecCertPem, err := os.ReadFile(eccCertFile)
   301  	require.NoError(t, err)
   302  
   303  	ecCertDer, _ := pem.Decode(ecCertPem)
   304  	require.NotNil(t, ecCertDer)
   305  
   306  	ecCert, err := x509.ParseCertificate(ecCertDer.Bytes)
   307  	require.NoError(t, err)
   308  
   309  	caFile := filepath.Join(certFixturesDir, "myCA.crt")
   310  	caPem, err := os.ReadFile(caFile)
   311  	require.NoError(t, err)
   312  
   313  	caBlock, _ := pem.Decode(caPem)
   314  	require.NotNil(t, caBlock)
   315  
   316  	caCert, err := x509.ParseCertificate(caBlock.Bytes)
   317  	require.NoError(t, err)
   318  
   319  	serverKeyFile := filepath.Join(certFixturesDir, "mycert1.key")
   320  	serverKeyPem, err := os.ReadFile(serverKeyFile)
   321  	require.NoError(t, err)
   322  
   323  	serverKeyDer, _ := pem.Decode(serverKeyPem)
   324  	require.NotNil(t, serverKeyDer)
   325  
   326  	serverKey, err := x509.ParsePKCS1PrivateKey(serverKeyDer.Bytes)
   327  	require.NoError(t, err)
   328  
   329  	serverCertFile := filepath.Join(certFixturesDir, "mycert1.crt")
   330  	serverCertPem, err := os.ReadFile(serverCertFile)
   331  	require.NoError(t, err)
   332  
   333  	serverCertDer, _ := pem.Decode(serverCertPem)
   334  	require.NotNil(t, serverCertDer)
   335  
   336  	serverCert, err := x509.ParseCertificate(serverCertDer.Bytes)
   337  	require.NoError(t, err)
   338  
   339  	return &tlsFixtures{
   340  		Subject: subject,
   341  		RSA: tlsFixture{
   342  			CAFile:     caFile,
   343  			KeyFile:    keyFile,
   344  			CertFile:   certFile,
   345  			LoadedCA:   caCert,
   346  			LoadedKey:  key,
   347  			LoadedCert: cert,
   348  		},
   349  		ECDSA: tlsFixture{
   350  			KeyFile:    eccKeyFile,
   351  			CertFile:   eccCertFile,
   352  			LoadedKey:  ecKey,
   353  			LoadedCert: ecCert,
   354  		},
   355  		Server: tlsFixture{
   356  			KeyFile:    serverKeyFile,
   357  			CertFile:   serverCertFile,
   358  			LoadedCA:   caCert,
   359  			LoadedKey:  serverKey,
   360  			LoadedCert: serverCert,
   361  		},
   362  	}
   363  }
   364  
   365  func systemCAPool(t testing.TB) *x509.CertPool {
   366  	if goruntime.GOOS == "windows" {
   367  		// Windows doesn't have the system cert pool.
   368  		return x509.NewCertPool()
   369  	}
   370  
   371  	pool, err := x509.SystemCertPool()
   372  	require.NoError(t, err)
   373  
   374  	return pool
   375  }
   376  

View as plain text