@@ -18,18 +18,33 @@ package ffresty
18
18
19
19
import (
20
20
"context"
21
+ "crypto/rand"
22
+ "crypto/rsa"
23
+ "crypto/tls"
24
+ "crypto/x509"
25
+ "crypto/x509/pkix"
26
+ "encoding/json"
27
+ "encoding/pem"
21
28
"fmt"
29
+ "log"
30
+ "math/big"
31
+ "net"
22
32
"net/http"
33
+ "os"
23
34
"strings"
24
35
"testing"
36
+ "time"
25
37
26
38
"github.com/hyperledger/firefly-common/pkg/config"
27
39
"github.com/hyperledger/firefly-common/pkg/ffapi"
40
+ "github.com/hyperledger/firefly-common/pkg/fftls"
28
41
"github.com/hyperledger/firefly-common/pkg/i18n"
29
42
"github.com/jarcoal/httpmock"
30
43
"github.com/stretchr/testify/assert"
31
44
)
32
45
46
+ const configDir = "../../test/data/config"
47
+
33
48
var utConf = config .RootSection ("http_unit_tests" )
34
49
35
50
func resetConf () {
@@ -51,7 +66,8 @@ func TestRequestOK(t *testing.T) {
51
66
utConf .Set (HTTPConfigRetryEnabled , true )
52
67
utConf .Set (HTTPCustomClient , customClient )
53
68
54
- c := New (context .Background (), utConf )
69
+ c , err := New (context .Background (), utConf )
70
+ assert .Nil (t , err )
55
71
httpmock .ActivateNonDefault (customClient )
56
72
defer httpmock .DeactivateAndReset ()
57
73
@@ -79,7 +95,8 @@ func TestRequestRetry(t *testing.T) {
79
95
utConf .Set (HTTPConfigRetryEnabled , true )
80
96
utConf .Set (HTTPConfigRetryInitDelay , 1 )
81
97
82
- c := New (ctx , utConf )
98
+ c , err := New (ctx , utConf )
99
+ assert .Nil (t , err )
83
100
httpmock .ActivateNonDefault (c .GetClient ())
84
101
defer httpmock .DeactivateAndReset ()
85
102
@@ -105,7 +122,8 @@ func TestConfWithProxy(t *testing.T) {
105
122
utConf .Set (HTTPConfigProxyURL , "http://myproxy.example.com:12345" )
106
123
utConf .Set (HTTPConfigRetryEnabled , false )
107
124
108
- c := New (ctx , utConf )
125
+ c , err := New (ctx , utConf )
126
+ assert .Nil (t , err )
109
127
assert .True (t , c .IsProxySet ())
110
128
}
111
129
@@ -117,7 +135,8 @@ func TestLongResponse(t *testing.T) {
117
135
utConf .Set (HTTPConfigURL , "http://localhost:12345" )
118
136
utConf .Set (HTTPConfigRetryEnabled , false )
119
137
120
- c := New (ctx , utConf )
138
+ c , err := New (ctx , utConf )
139
+ assert .Nil (t , err )
121
140
httpmock .ActivateNonDefault (c .GetClient ())
122
141
defer httpmock .DeactivateAndReset ()
123
142
@@ -141,7 +160,8 @@ func TestErrResponse(t *testing.T) {
141
160
utConf .Set (HTTPConfigURL , "http://localhost:12345" )
142
161
utConf .Set (HTTPConfigRetryEnabled , false )
143
162
144
- c := New (ctx , utConf )
163
+ c , err := New (ctx , utConf )
164
+ assert .Nil (t , err )
145
165
httpmock .ActivateNonDefault (c .GetClient ())
146
166
defer httpmock .DeactivateAndReset ()
147
167
@@ -180,7 +200,8 @@ func TestPassthroughHeaders(t *testing.T) {
180
200
utConf .Set (HTTPCustomClient , customClient )
181
201
utConf .Set (HTTPPassthroughHeadersEnabled , true )
182
202
183
- c := New (context .Background (), utConf )
203
+ c , err := New (context .Background (), utConf )
204
+ assert .Nil (t , err )
184
205
httpmock .ActivateNonDefault (customClient )
185
206
defer httpmock .DeactivateAndReset ()
186
207
@@ -200,3 +221,132 @@ func TestPassthroughHeaders(t *testing.T) {
200
221
201
222
assert .Equal (t , 1 , httpmock .GetTotalCallCount ())
202
223
}
224
+
225
+ func TestMissingCAFile (t * testing.T ) {
226
+ resetConf ()
227
+ utConf .Set (HTTPConfigURL , "https://localhost:12345" )
228
+ tlsSection := utConf .SubSection ("tls" )
229
+ tlsSection .Set (fftls .HTTPConfTLSEnabled , true )
230
+ tlsSection .Set (fftls .HTTPConfTLSCAFile , "non-existent.pem" )
231
+
232
+ _ , err := New (context .Background (), utConf )
233
+ assert .Regexp (t , "FF00153" , err )
234
+ }
235
+
236
+ func TestBadCAFile (t * testing.T ) {
237
+ resetConf ()
238
+ utConf .Set (HTTPConfigURL , "https://localhost:12345" )
239
+ tlsSection := utConf .SubSection ("tls" )
240
+ tlsSection .Set (fftls .HTTPConfTLSEnabled , true )
241
+ tlsSection .Set (fftls .HTTPConfTLSCAFile , configDir + "/firefly.common.yaml" )
242
+
243
+ _ , err := New (context .Background (), utConf )
244
+ assert .Regexp (t , "FF00152" , err )
245
+ }
246
+
247
+ func TestBadKeyPair (t * testing.T ) {
248
+ resetConf ()
249
+ utConf .Set (HTTPConfigURL , "https://localhost:12345" )
250
+ tlsSection := utConf .SubSection ("tls" )
251
+ tlsSection .Set (fftls .HTTPConfTLSEnabled , true )
252
+ tlsSection .Set (fftls .HTTPConfTLSCertFile , configDir + "/firefly.common.yaml" )
253
+ tlsSection .Set (fftls .HTTPConfTLSKeyFile , configDir + "/firefly.common.yaml" )
254
+
255
+ _ , err := New (context .Background (), utConf )
256
+ assert .Regexp (t , "FF00206" , err )
257
+ }
258
+
259
+ func TestMTLSClientWithServer (t * testing.T ) {
260
+ // Create an X509 certificate pair
261
+ privatekey , _ := rsa .GenerateKey (rand .Reader , 2048 )
262
+ publickey := & privatekey .PublicKey
263
+ var privateKeyBytes []byte = x509 .MarshalPKCS1PrivateKey (privatekey )
264
+ privateKeyFile , _ := os .CreateTemp ("" , "key.pem" )
265
+ defer os .Remove (privateKeyFile .Name ())
266
+ privateKeyBlock := & pem.Block {Type : "RSA PRIVATE KEY" , Bytes : privateKeyBytes }
267
+ pem .Encode (privateKeyFile , privateKeyBlock )
268
+ serialNumber , _ := rand .Int (rand .Reader , new (big.Int ).Lsh (big .NewInt (1 ), 128 ))
269
+ x509Template := & x509.Certificate {
270
+ SerialNumber : serialNumber ,
271
+ Subject : pkix.Name {
272
+ Organization : []string {"Unit Tests" },
273
+ },
274
+ NotBefore : time .Now (),
275
+ NotAfter : time .Now ().Add (100 * time .Second ),
276
+ KeyUsage : x509 .KeyUsageDigitalSignature ,
277
+ BasicConstraintsValid : true ,
278
+ IPAddresses : []net.IP {net .IPv4 (127 , 0 , 0 , 1 )},
279
+ }
280
+ derBytes , err := x509 .CreateCertificate (rand .Reader , x509Template , x509Template , publickey , privatekey )
281
+ assert .NoError (t , err )
282
+ publicKeyFile , _ := os .CreateTemp ("" , "cert.pem" )
283
+ defer os .Remove (publicKeyFile .Name ())
284
+ pem .Encode (publicKeyFile , & pem.Block {Type : "CERTIFICATE" , Bytes : derBytes })
285
+
286
+ http .HandleFunc ("/hello" , func (res http.ResponseWriter , req * http.Request ) {
287
+ res .WriteHeader (200 )
288
+ json .NewEncoder (res ).Encode (map [string ]interface {}{"hello" : "world" })
289
+ })
290
+
291
+ // Create a CA certificate pool and add cert.pem to it
292
+ caCert , err := os .ReadFile (publicKeyFile .Name ())
293
+ if err != nil {
294
+ log .Fatal (err )
295
+ }
296
+ caCertPool := x509 .NewCertPool ()
297
+ caCertPool .AppendCertsFromPEM (caCert )
298
+
299
+ // Create the TLS Config with the CA pool and enable Client certificate validation
300
+ tlsConfig := & tls.Config {
301
+ ClientCAs : caCertPool ,
302
+ ClientAuth : tls .RequireAndVerifyClientCert ,
303
+ }
304
+ tlsConfig .BuildNameToCertificate ()
305
+
306
+ // Create a Server instance to listen on port 8443 with the TLS config
307
+ server := & http.Server {
308
+ Addr : "127.0.0.1:8443" ,
309
+ TLSConfig : tlsConfig ,
310
+ }
311
+
312
+ ctx , cancelCtx := context .WithCancel (context .Background ())
313
+ go func () {
314
+ select {
315
+ case <- ctx .Done ():
316
+ shutdownContext , cancel := context .WithTimeout (context .Background (), 2 * time .Second )
317
+ defer cancel ()
318
+ if err := server .Shutdown (shutdownContext ); err != nil {
319
+ return
320
+ }
321
+ }
322
+ }()
323
+
324
+ go server .ListenAndServeTLS (publicKeyFile .Name (), privateKeyFile .Name ())
325
+
326
+ // Use ffresty to test the mTLS client as well
327
+ var restyConfig = config .RootSection ("resty" )
328
+ InitConfig (restyConfig )
329
+ clientTLSSection := restyConfig .SubSection ("tls" )
330
+ restyConfig .Set (HTTPConfigURL , "https://127.0.0.1" )
331
+ clientTLSSection .Set (fftls .HTTPConfTLSEnabled , true )
332
+ clientTLSSection .Set (fftls .HTTPConfTLSKeyFile , privateKeyFile .Name ())
333
+ clientTLSSection .Set (fftls .HTTPConfTLSCertFile , publicKeyFile .Name ())
334
+ clientTLSSection .Set (fftls .HTTPConfTLSCAFile , publicKeyFile .Name ())
335
+
336
+ c , err := New (context .Background (), restyConfig )
337
+ assert .Nil (t , err )
338
+
339
+ //httpsAddr := fmt.Sprintf("https://localhost:8443/hello", server.Addr)
340
+ res , err := c .R ().Get ("https://127.0.0.1:8443/hello" )
341
+ assert .NoError (t , err )
342
+
343
+ assert .NoError (t , err )
344
+ if res != nil {
345
+ assert .Equal (t , 200 , res .StatusCode ())
346
+ var resBody map [string ]interface {}
347
+ err = json .Unmarshal (res .Body (), & resBody )
348
+ assert .NoError (t , err )
349
+ assert .Equal (t , "world" , resBody ["hello" ])
350
+ }
351
+ cancelCtx ()
352
+ }
0 commit comments