@@ -3,144 +3,18 @@ package pq
33import (
44 "bytes"
55 "context"
6- "crypto/ecdsa"
7- "crypto/elliptic"
8- "crypto/rand"
9- _ "crypto/sha256"
106 "crypto/tls"
117 "crypto/x509"
12- "crypto/x509/pkix"
138 "database/sql"
149 "encoding/pem"
1510 "fmt"
1611 "io"
17- "math/big"
1812 "net"
1913 "os"
2014 "testing"
2115 "time"
22-
23- "github.com/lib/pq/internal/pqtest"
2416)
2517
26- type certChain struct {
27- rootPEM []byte
28- intermediatePEM []byte
29- serverTLSCert tls.Certificate
30- clientCertPEM []byte
31- clientKeyPEM []byte
32- }
33-
34- // generateIntermediateCAChain creates:
35- // - root CA
36- // - intermediate CA (signed by root)
37- // - server cert (signed by intermediate)
38- // - client cert (signed by intermediate)
39- func generateIntermediateCAChain (t * testing.T ) certChain {
40- t .Helper ()
41-
42- now := time .Now ()
43-
44- // Root CA
45- rootKey , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
46- if err != nil {
47- t .Fatal (err )
48- }
49- rootTemplate := & x509.Certificate {
50- SerialNumber : big .NewInt (1 ),
51- Subject : pkix.Name {CommonName : "Test Root CA" },
52- NotBefore : now ,
53- NotAfter : now .Add (time .Hour ),
54- KeyUsage : x509 .KeyUsageCertSign | x509 .KeyUsageCRLSign ,
55- BasicConstraintsValid : true ,
56- IsCA : true ,
57- }
58- rootCertDER , err := x509 .CreateCertificate (rand .Reader , rootTemplate , rootTemplate , & rootKey .PublicKey , rootKey )
59- if err != nil {
60- t .Fatal (err )
61- }
62- rootCert , err := x509 .ParseCertificate (rootCertDER )
63- if err != nil {
64- t .Fatal (err )
65- }
66-
67- // Intermediate CA signed by root
68- interKey , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
69- if err != nil {
70- t .Fatal (err )
71- }
72- interTemplate := & x509.Certificate {
73- SerialNumber : big .NewInt (2 ),
74- Subject : pkix.Name {CommonName : "Test Intermediate CA" },
75- NotBefore : now ,
76- NotAfter : now .Add (time .Hour ),
77- KeyUsage : x509 .KeyUsageCertSign | x509 .KeyUsageCRLSign ,
78- BasicConstraintsValid : true ,
79- IsCA : true ,
80- }
81- interCertDER , err := x509 .CreateCertificate (rand .Reader , interTemplate , rootCert , & interKey .PublicKey , rootKey )
82- if err != nil {
83- t .Fatal (err )
84- }
85- interCert , err := x509 .ParseCertificate (interCertDER )
86- if err != nil {
87- t .Fatal (err )
88- }
89-
90- // Server cert signed by intermediate
91- serverKey , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
92- if err != nil {
93- t .Fatal (err )
94- }
95- serverTemplate := & x509.Certificate {
96- SerialNumber : big .NewInt (3 ),
97- Subject : pkix.Name {CommonName : "localhost" },
98- DNSNames : []string {"localhost" },
99- IPAddresses : []net.IP {net .IPv4 (127 , 0 , 0 , 1 )},
100- NotBefore : now ,
101- NotAfter : now .Add (time .Hour ),
102- KeyUsage : x509 .KeyUsageDigitalSignature | x509 .KeyUsageKeyEncipherment ,
103- ExtKeyUsage : []x509.ExtKeyUsage {x509 .ExtKeyUsageServerAuth },
104- }
105- serverCertDER , err := x509 .CreateCertificate (rand .Reader , serverTemplate , interCert , & serverKey .PublicKey , interKey )
106- if err != nil {
107- t .Fatal (err )
108- }
109-
110- // Client cert signed by intermediate
111- clientKey , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
112- if err != nil {
113- t .Fatal (err )
114- }
115- clientTemplate := & x509.Certificate {
116- SerialNumber : big .NewInt (4 ),
117- Subject : pkix.Name {CommonName : "testclient" },
118- NotBefore : now ,
119- NotAfter : now .Add (time .Hour ),
120- KeyUsage : x509 .KeyUsageDigitalSignature | x509 .KeyUsageKeyEncipherment ,
121- ExtKeyUsage : []x509.ExtKeyUsage {x509 .ExtKeyUsageClientAuth },
122- }
123- clientCertDER , err := x509 .CreateCertificate (rand .Reader , clientTemplate , interCert , & clientKey .PublicKey , interKey )
124- if err != nil {
125- t .Fatal (err )
126- }
127- clientKeyDER , err := x509 .MarshalECPrivateKey (clientKey )
128- if err != nil {
129- t .Fatal (err )
130- }
131-
132- return certChain {
133- rootPEM : pem .EncodeToMemory (& pem.Block {Type : "CERTIFICATE" , Bytes : rootCertDER }),
134- intermediatePEM : pem .EncodeToMemory (& pem.Block {Type : "CERTIFICATE" , Bytes : interCertDER }),
135- serverTLSCert : tls.Certificate {
136- Certificate : [][]byte {serverCertDER , interCertDER },
137- PrivateKey : serverKey ,
138- },
139- clientCertPEM : pem .EncodeToMemory (& pem.Block {Type : "CERTIFICATE" , Bytes : clientCertDER }),
140- clientKeyPEM : pem .EncodeToMemory (& pem.Block {Type : "EC PRIVATE KEY" , Bytes : clientKeyDER }),
141- }
142- }
143-
14418type mockSSLServerOpts struct {
14519 serverCert tls.Certificate
14620 clientCAs * x509.CertPool // nil means don't request client certs
@@ -307,20 +181,46 @@ func pingMockServer(t *testing.T, dsn string, port string, errCh chan error) err
307181 return clientErr
308182}
309183
184+ // loadServerCert loads a TLS server certificate, optionally including the
185+ // intermediate CA cert in the chain.
186+ func loadServerCert (t * testing.T , certFile , keyFile , intermediateFile string ) tls.Certificate {
187+ t .Helper ()
188+
189+ cert , err := tls .LoadX509KeyPair (certFile , keyFile )
190+ if err != nil {
191+ t .Fatal (err )
192+ }
193+ if intermediateFile != "" {
194+ interPEM , err := os .ReadFile (intermediateFile )
195+ if err != nil {
196+ t .Fatal (err )
197+ }
198+ block , _ := pem .Decode (interPEM )
199+ if block != nil && block .Type == "CERTIFICATE" {
200+ cert .Certificate = append (cert .Certificate , block .Bytes )
201+ }
202+ }
203+ return cert
204+ }
205+
310206// TestSSLIntermediateCA tests various intermediate CA scenarios for both
311207// server certificate verification (verify-ca, verify-full) and client
312208// certificate authentication.
313209func TestSSLIntermediateCA (t * testing.T ) {
314- chain := generateIntermediateCAChain (t )
315-
210+ const (
211+ rootCert = "testdata/init/root.crt"
212+ bundleCert = "testdata/init/root+intermediate.crt"
213+ interCert = "testdata/init/intermediate.crt"
214+ serverCert = "testdata/init/server_intermediate.crt"
215+ serverKey = "testdata/init/server_intermediate.key"
216+ clientCert = "testdata/init/client_intermediate.crt"
217+ clientKey = "testdata/init/client_intermediate.key"
218+ )
219+
220+ // Server cert with full chain [leaf, intermediate]
221+ serverFullChain := loadServerCert (t , serverCert , serverKey , interCert )
316222 // Server cert with only the leaf (no intermediate in chain)
317- serverCertLeafOnly := tls.Certificate {
318- Certificate : [][]byte {chain .serverTLSCert .Certificate [0 ]},
319- PrivateKey : chain .serverTLSCert .PrivateKey ,
320- }
321-
322- rootCertFile := pqtest .TempFile (t , "root.crt" , string (chain .rootPEM ))
323- bundleCertFile := pqtest .TempFile (t , "bundle.crt" , string (chain .rootPEM )+ string (chain .intermediatePEM ))
223+ serverLeafOnly := loadServerCert (t , serverCert , serverKey , "" )
324224
325225 t .Run ("server cert verification" , func (t * testing.T ) {
326226 tests := []struct {
@@ -334,43 +234,43 @@ func TestSSLIntermediateCA(t *testing.T) {
334234 {
335235 name : "verify-ca full chain root only" ,
336236 sslmode : "verify-ca" ,
337- rootcert : rootCertFile ,
338- serverCert : chain . serverTLSCert ,
237+ rootcert : rootCert ,
238+ serverCert : serverFullChain ,
339239 },
340240 {
341241 name : "verify-full full chain root only" ,
342242 sslmode : "verify-full" ,
343- rootcert : rootCertFile ,
344- serverCert : chain . serverTLSCert ,
243+ rootcert : rootCert ,
244+ serverCert : serverFullChain ,
345245 },
346246
347247 // Server sends only leaf, sslrootcert has root+intermediate bundle.
348248 {
349249 name : "verify-ca leaf only bundle rootcert" ,
350250 sslmode : "verify-ca" ,
351- rootcert : bundleCertFile ,
352- serverCert : serverCertLeafOnly ,
251+ rootcert : bundleCert ,
252+ serverCert : serverLeafOnly ,
353253 },
354254 {
355255 name : "verify-full leaf only bundle rootcert" ,
356256 sslmode : "verify-full" ,
357- rootcert : bundleCertFile ,
358- serverCert : serverCertLeafOnly ,
257+ rootcert : bundleCert ,
258+ serverCert : serverLeafOnly ,
359259 },
360260
361261 // Server sends only leaf, sslrootcert has root only — can't build chain.
362262 {
363263 name : "verify-ca leaf only root only fails" ,
364264 sslmode : "verify-ca" ,
365- rootcert : rootCertFile ,
366- serverCert : serverCertLeafOnly ,
265+ rootcert : rootCert ,
266+ serverCert : serverLeafOnly ,
367267 wantErr : true ,
368268 },
369269 {
370270 name : "verify-full leaf only root only fails" ,
371271 sslmode : "verify-full" ,
372- rootcert : rootCertFile ,
373- serverCert : serverCertLeafOnly ,
272+ rootcert : rootCert ,
273+ serverCert : serverLeafOnly ,
374274 wantErr : true ,
375275 },
376276 }
@@ -397,25 +297,27 @@ func TestSSLIntermediateCA(t *testing.T) {
397297 t .Run ("client cert with intermediate CA" , func (t * testing.T ) {
398298 // Server's CA trust store has only the root CA. It needs the client to
399299 // send the intermediate cert in its TLS certificate chain.
300+ rootPEM , err := os .ReadFile (rootCert )
301+ if err != nil {
302+ t .Fatal (err )
303+ }
400304 serverCAs := x509 .NewCertPool ()
401- serverCAs .AppendCertsFromPEM (chain . rootPEM )
305+ serverCAs .AppendCertsFromPEM (rootPEM )
402306
403- clientCertFile := pqtest .TempFile (t , "client.crt" , string (chain .clientCertPEM ))
404- clientKeyFile := pqtest .TempFile (t , "client.key" , string (chain .clientKeyPEM ))
405- if err := os .Chmod (clientKeyFile , 0600 ); err != nil {
307+ if err := os .Chmod (clientKey , 0600 ); err != nil {
406308 t .Fatal (err )
407309 }
408310
409311 port , errCh := mockPostgresSSLServer (t , mockSSLServerOpts {
410- serverCert : chain . serverTLSCert ,
312+ serverCert : serverFullChain ,
411313 clientCAs : serverCAs ,
412314 })
413315
414316 dsn := fmt .Sprintf (
415317 "host=127.0.0.1 port=%s sslmode=verify-ca sslrootcert=%s sslcert=%s sslkey=%s user=test dbname=test connect_timeout=5" ,
416- port , bundleCertFile , clientCertFile , clientKeyFile )
318+ port , bundleCert , clientCert , clientKey )
417319
418- err : = pingMockServer (t , dsn , port , errCh )
320+ err = pingMockServer (t , dsn , port , errCh )
419321 if err != nil {
420322 t .Fatalf ("client cert with intermediate CA failed: %s" , err )
421323 }
0 commit comments