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
109
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
139
140
141
142
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
192 serverCACertPool := x509.NewCertPool()
193 serverCACertPool.AddCert(fixtures.Server.LoadedCA)
194
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
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
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