Skip to content

Commit 235b962

Browse files
committed
Use pre-generated certs in testdata/init/.
Instead of making them inside tests as needed, just have static certs that are made with a Makefile.
1 parent 08f8950 commit 235b962

16 files changed

+422
-268
lines changed

ssl_intermediate_test.go

Lines changed: 56 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -3,144 +3,18 @@ package pq
33
import (
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-
14418
type 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.
313209
func 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
}

testdata/init/Makefile

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
.PHONY: all root-ssl server-ssl client-ssl
1+
.PHONY: all root-ssl server-ssl client-ssl intermediate-ssl server-intermediate-ssl client-intermediate-ssl
22

33
# Rebuilds self-signed root/server/client certs/keys in a consistent way
4-
all: root-ssl server-ssl client-ssl
5-
rm -f .srl
4+
all: root-ssl server-ssl client-ssl intermediate-ssl server-intermediate-ssl client-intermediate-ssl
5+
rm -f .srl *.srl
6+
cat root.crt intermediate.crt > root+intermediate.crt
67

78
root-ssl:
89
openssl req -new -sha256 -nodes -newkey rsa:2048 \
@@ -35,3 +36,35 @@ client-ssl:
3536
-CA ./certs/root.crt -CAkey /tmp/root.key -CAcreateserial \
3637
-in /tmp/postgresql.csr \
3738
-out ./certs/postgresql.crt
39+
40+
intermediate-ssl:
41+
openssl req -new -sha256 -nodes -newkey rsa:2048 \
42+
-config intermediate.cnf \
43+
-keyout /tmp/intermediate.key \
44+
-out /tmp/intermediate.csr
45+
openssl x509 -req -days 3653 -sha256 \
46+
-extfile <(printf "basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,cRLSign\nsubjectKeyIdentifier=hash\nauthorityKeyIdentifier=keyid,issuer") \
47+
-CA root.crt -CAkey /tmp/root.key -CAcreateserial \
48+
-in /tmp/intermediate.csr \
49+
-out intermediate.crt
50+
51+
server-intermediate-ssl:
52+
openssl req -new -sha256 -nodes -newkey rsa:2048 \
53+
-config server_intermediate.cnf \
54+
-keyout server_intermediate.key \
55+
-out /tmp/server_intermediate.csr
56+
openssl x509 -req -days 3653 -sha256 \
57+
-extfile server_intermediate.cnf -extensions req_ext \
58+
-CA intermediate.crt -CAkey /tmp/intermediate.key -CAcreateserial \
59+
-in /tmp/server_intermediate.csr \
60+
-out server_intermediate.crt
61+
62+
client-intermediate-ssl:
63+
openssl req -new -sha256 -nodes -newkey rsa:2048 \
64+
-config client_intermediate.cnf \
65+
-keyout client_intermediate.key \
66+
-out /tmp/client_intermediate.csr
67+
openssl x509 -req -days 3653 -sha256 \
68+
-CA intermediate.crt -CAkey /tmp/intermediate.key -CAcreateserial \
69+
-in /tmp/client_intermediate.csr \
70+
-out client_intermediate.crt
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[req]
2+
distinguished_name = req_distinguished_name
3+
prompt = no
4+
5+
[req_distinguished_name]
6+
C = US
7+
ST = Nevada
8+
L = Las Vegas
9+
O = github.com/lib/pq
10+
CN = pqgosslcert
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDnzCCAoegAwIBAgIUMmQ+3iTA688OIt+AIz7OF87gohkwDQYJKoZIhvcNAQEL
3+
BQAwazELMAkGA1UEBhMCVVMxDzANBgNVBAgMBk5ldmFkYTESMBAGA1UEBwwJTGFz
4+
IFZlZ2FzMRowGAYDVQQKDBFnaXRodWIuY29tL2xpYi9wcTEbMBkGA1UEAwwScHEg
5+
SW50ZXJtZWRpYXRlIENBMB4XDTI2MDMwNTE3NDkwNloXDTM2MDMwNTE3NDkwNlow
6+
ZDELMAkGA1UEBhMCVVMxDzANBgNVBAgMBk5ldmFkYTESMBAGA1UEBwwJTGFzIFZl
7+
Z2FzMRowGAYDVQQKDBFnaXRodWIuY29tL2xpYi9wcTEUMBIGA1UEAwwLcHFnb3Nz
8+
bGNlcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hDnWJaHuDWMf
9+
Q7JCDGTZuqdFfiP97SPT6ca4a2iWdmttfUhoBkrjrpAmg4z3YHU+ogKyqZtJ7gEh
10+
y+GyntyPEmL8Rua4/5QhW17xzeDqaVbj231azUfeVjbZlqH/ysN+3rVt8rvFlFRs
11+
p0QROqYBClMZNzDBBpSw6hyU0dVNBSMccHGa37xN8jILtq+NI9wVatIRdM/DqTYN
12+
4NmwOdvlJ6cmoyhzfLl1lKVZvxBsj8JVjc/RlK0IvXoGKKQBILk257yEITzkrVT4
13+
swCBbptc+wTtMkLrxYYVF75HqQa/F33gsPP9GnWL9kY48gWI3yHl97gyarQe9DkK
14+
idOsuBO7AgMBAAGjQjBAMB0GA1UdDgQWBBT5l0kq3uJh5GYbxsF1StUr9NauJTAf
15+
BgNVHSMEGDAWgBSMTYV4MVWf9hD/cI3IJWluimVfXTANBgkqhkiG9w0BAQsFAAOC
16+
AQEAjZGXps9DHxJZB2Tg3MafmSu9mXxfSXKFuorSA0vaLyCsUp/ZBKq1o+mh9KSC
17+
iho2/Ya6R/ZFt3yuzgRicPHd+3kNlmFwgtg8OX5WxCGpDIb6TjHoRDLCm9VKxvF5
18+
Fc2FSJVopzaRdhYxHr2SXe86vRExQTHwo0L/dMgQZswitg54RR0E2qSZaw5E/yk/
19+
aBKd9OeZKrjCSwnx1CtW4QMQIjkoE+VcMo6ZWhETyfmF7CdKoby5G2Xj9o/QOK5c
20+
gksAGsVaPeht63/+uoizNdKdQCt/cpRyWOLJYsIuwjlFEmDNel2EPIecXmotk48o
21+
2GK2REcCXq+F8W1itywnpmNPlQ==
22+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)