Skip to content

Commit caa2b42

Browse files
authored
feat: add SetCertificateFromFile and SetCertificateFromString (#941)
- refactor root cert and client root methods to make consistent signature
1 parent e7bb2cd commit caa2b42

File tree

5 files changed

+243
-90
lines changed

5 files changed

+243
-90
lines changed

.testdata/cert.pem

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIC+jCCAeKgAwIBAgIRAJce5ewsoW44j0qvSABmq7owDQYJKoZIhvcNAQELBQAw
3+
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yNTAxMDQwNzA3MTNaFw0yNjAxMDQwNzA3
4+
MTNaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
5+
ggEKAoIBAQCYkTN1g/0Z3KkS3w0lX9yhZkwiA0obXCeFs7hpRP0p4WlW3uADyXQ5
6+
h2MaYx8OCA7oGU7/dWOPhtE3rgFEz7IwLxcP5d02ukLGlFD69D6KLyTXwCFmvOWQ
7+
5fbOq4s73WTNDfYSTYNzeujDCjeu/Bk0OVhdxbyZdyrpdm+UBfH8uIDoGeCRXnji
8+
nqG9HNOQx6r/S6FqC5j/7PrVl1i66WlqRzKEJB94uejfujrHq8RjQm/wzEutU5df
9+
C39zEEEx75qQt7Jc0asm1AqAKSq34xn4rVajWrBZ/WudUUizHfaBDP61uPFvPyKW
10+
JDvTSdeoM9TPX0y0cjo6AwSrdLl7flrRAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIF
11+
oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC
12+
CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAdHvPQe3EJ4/X6K/bklJUhIfM
13+
KBauH8VMBfri7xLawleKssm7GdiFivSA0g1pArkl8SALBlPqhrx7rwlyyivLTZaR
14+
VFvXaQ9eU0zGnSnDnKVz6CX/zn3TKfcgZPEBclayh0ldm7A8xSJWaWbRZ+s9e9x1
15+
XcQTn2KkMZfBDMnGEWQ3KZrClvO5ZfkqSiyzEm9+eF0m0E7ujTyfSVMsPdyldA6U
16+
pHG8omQTyOzJl2I4z7DlS0AEsL0TJHV4iKr9rDei2xQz/wtful5qU/taYp2Y6zMH
17+
8ytnDldJhmcCwmvtqvK5p6CbkatE7TFyw2CxQJHnQef+Y4W94sSZWg9CGRKDIQ==
18+
-----END CERTIFICATE-----

.testdata/key.pem

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCYkTN1g/0Z3KkS
3+
3w0lX9yhZkwiA0obXCeFs7hpRP0p4WlW3uADyXQ5h2MaYx8OCA7oGU7/dWOPhtE3
4+
rgFEz7IwLxcP5d02ukLGlFD69D6KLyTXwCFmvOWQ5fbOq4s73WTNDfYSTYNzeujD
5+
Cjeu/Bk0OVhdxbyZdyrpdm+UBfH8uIDoGeCRXnjinqG9HNOQx6r/S6FqC5j/7PrV
6+
l1i66WlqRzKEJB94uejfujrHq8RjQm/wzEutU5dfC39zEEEx75qQt7Jc0asm1AqA
7+
KSq34xn4rVajWrBZ/WudUUizHfaBDP61uPFvPyKWJDvTSdeoM9TPX0y0cjo6AwSr
8+
dLl7flrRAgMBAAECggEAJPTPNUEilxgncGXNZmdBJ2uDN536XoRFIpL1MbK/bFyo
9+
yp00QFaVK7ZK4EJwbFKxYbF3vFOwKT0sAsPIlOWGsTtG59fzbOVTdYzJzPBLEef3
10+
kbd9n8hUB3RdA5T0Ji0r1Kv0FlzmYZu9NDmOYXm5lTfq2tQiKj5+i4zf3EhQZLng
11+
4wVxBT7yQUQcstJv5K1L6HVzunSYtbHx8ZVxmw+tJ4lMCK23KPlvncZZTT8chWdT
12+
3GOp5nYIHk9E5jQnBnj7p73sxZUCZlb8uhLtdcgAXc4scptEVO+7n5zOaXIv40Oz
13+
yfkESgHcZWAMDvnkxdySHlD38Z2LIKDGbqR6O9wcwQKBgQDBO6fFPXO41nsxdVCB
14+
nhCgL2hsGjaxJzGBLaOJNVMMFRASN3Yqvs4N1Hn7lawRI/FRRffxjLkZfNGEBSF2
15+
OipdvX19Oe2hCZxvwHPoe5sb/Dh6KE7If1hRLOCXg/8E7ADBtAp94dam1WF4Kh6N
16+
Va6+n2YKif2rqye1YtRoUU46iQKBgQDKH/eMcMRUe9IySxHLogidOUwa0X7WrxF/
17+
PkXGpPbHQtMOJF5cVzh+L+foUKXNM60lgmCH0438GKU7kirC/dVtD/bwE598/XFZ
18+
vnjPV7Adf9vBz9NN8cS/4uEfQYbvTRmrnrQK+ZhOe8hmwjapxqdWrVHNUtvx18vL
19+
qBwR4YjsCQKBgCycMx1MFJ1FludSKCXkcf4pM7hRTPMVE065VJnmn6eYbT9nYnZ3
20+
2mZC+W5lnXXPkHSs7JLtZAZIVK5f6Nu8je9aQdBZQUz+RQlfquKvNp39WqSJDbcn
21+
/yGudKNGK+fc/Ee74vgw3Tdi57+wKaGDeHY1on8oYFHzj5VGnbb/nknRAoGBAK2Z
22+
hyQ4NmfZcU+A6mfbY0qmS5c9F5OMCZsgAQ374XiDDIK4+dKVlw/KVYRSwBTerXfp
23+
4r7GFMzQ3hmsEM4o9YYWkCDiubjAdPp/fYOX7MtpZXWw6euoGzQzyObvgNVHgyTD
24+
yh8jAI1oA1c+t3RaCp+HfRq8b+vnTEI+wN0auF8BAoGBAJmw+GgHCZGpw2XPNu+X
25+
8kuVGbQYAjTOXhBM4WzZyhfH1TWKLGn7C9YixhE2AW0UWKDvy+6OqPhe8q3KVms3
26+
8YZ1W+vbUNEZNGE0XrB5ZMXfePiqisCz0jgP9OAuT+ii4aI3MAm3zgCEC6UTMvLq
27+
gNBu3Tcy6udxnUf7czzJDRtE
28+
-----END PRIVATE KEY-----

cert_watcher_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,13 @@ func TestClient_SetRootCertificateWatcher(t *testing.T) {
5757
// Make sure that TLS handshake happens for all request
5858
// (otherwise, test may succeed because 1st TLS session is re-used)
5959
DisableKeepAlives: true,
60-
}).SetRootCertificateWatcher(paths.RootCACert, &CertWatcherOptions{
61-
PoolInterval: poolingInterval,
62-
}).SetClientRootCertificateWatcher(paths.RootCACert, &CertWatcherOptions{
63-
PoolInterval: poolingInterval,
64-
}).SetDebug(false)
60+
}).SetRootCertificatesWatcher(
61+
&CertWatcherOptions{PoolInterval: poolingInterval},
62+
paths.RootCACert,
63+
).SetClientRootCertificatesWatcher(
64+
&CertWatcherOptions{PoolInterval: poolingInterval},
65+
paths.RootCACert,
66+
).SetDebug(false)
6567

6668
url := strings.Replace(ts.URL, "127.0.0.1", "localhost", 1)
6769
t.Log("Test URL:", url)

client.go

Lines changed: 155 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,7 +1488,52 @@ func (c *Client) RemoveProxy() *Client {
14881488
return c
14891489
}
14901490

1491-
// SetCertificates method helps to conveniently set client certificates into Resty.
1491+
// SetCertificateFromString method helps to set client certificates into Resty
1492+
// from cert and key files to perform SSL client authentication
1493+
//
1494+
// client.SetCertificateFromFile("certs/client.pem", "certs/client.key")
1495+
func (c *Client) SetCertificateFromFile(certFilePath, certKeyFilePath string) *Client {
1496+
cert, err := tls.LoadX509KeyPair(certFilePath, certKeyFilePath)
1497+
if err != nil {
1498+
c.Logger().Errorf("client certificate/key parsing error: %v", err)
1499+
return c
1500+
}
1501+
c.SetCertificates(cert)
1502+
return c
1503+
}
1504+
1505+
// SetCertificateFromString method helps to set client certificates into Resty
1506+
// from string to perform SSL client authentication
1507+
//
1508+
// myClientCertStr := `-----BEGIN CERTIFICATE-----
1509+
// ... cert content ...
1510+
// -----END CERTIFICATE-----`
1511+
//
1512+
// myClientCertKeyStr := `-----BEGIN PRIVATE KEY-----
1513+
// ... cert key content ...
1514+
// -----END PRIVATE KEY-----`
1515+
//
1516+
// client.SetCertificateFromString(myClientCertStr, myClientCertKeyStr)
1517+
func (c *Client) SetCertificateFromString(certStr, certKeyStr string) *Client {
1518+
cert, err := tls.X509KeyPair([]byte(certStr), []byte(certKeyStr))
1519+
if err != nil {
1520+
c.Logger().Errorf("client certificate/key parsing error: %v", err)
1521+
return c
1522+
}
1523+
c.SetCertificates(cert)
1524+
return c
1525+
}
1526+
1527+
// SetCertificates method helps to conveniently set client certificates into Resty
1528+
// to perform SSL client authentication
1529+
//
1530+
// cert, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
1531+
// if err != nil {
1532+
// log.Printf("ERROR client certificate/key parsing error: %v", err)
1533+
// return
1534+
// }
1535+
//
1536+
// client.SetCertificates(cert)
14921537
func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
14931538
config, err := c.tlsConfig()
14941539
if err != nil {
@@ -1502,72 +1547,142 @@ func (c *Client) SetCertificates(certs ...tls.Certificate) *Client {
15021547
return c
15031548
}
15041549

1505-
// SetRootCertificate method helps to add one or more root certificates into the Resty client
1550+
// SetRootCertificates method helps to add one or more root certificates into the Resty client
15061551
//
1507-
// client.SetRootCertificate("/path/to/root/pemFile.pem")
1508-
func (c *Client) SetRootCertificate(pemFilePath string) *Client {
1509-
rootPemData, err := os.ReadFile(pemFilePath)
1510-
if err != nil {
1511-
c.Logger().Errorf("%v", err)
1512-
return c
1552+
// // one pem file path
1553+
// client.SetRootCertificates("/path/to/root/pemFile.pem")
1554+
//
1555+
// // one or more pem file path(s)
1556+
// client.SetRootCertificates(
1557+
// "/path/to/root/pemFile1.pem",
1558+
// "/path/to/root/pemFile2.pem"
1559+
// "/path/to/root/pemFile3.pem"
1560+
// )
1561+
//
1562+
// // if you happen to have string slices
1563+
// client.SetRootCertificates(certs...)
1564+
func (c *Client) SetRootCertificates(pemFilePaths ...string) *Client {
1565+
for _, fp := range pemFilePaths {
1566+
rootPemData, err := os.ReadFile(fp)
1567+
if err != nil {
1568+
c.Logger().Errorf("%v", err)
1569+
return c
1570+
}
1571+
c.handleCAs("root", rootPemData)
15131572
}
1514-
c.handleCAs("root", rootPemData)
15151573
return c
15161574
}
15171575

1518-
// SetRootCertificateWatcher enables dynamic reloading of one or more root certificates.
1576+
// SetRootCertificatesWatcher method enables dynamic reloading of one or more root certificates.
15191577
// It is designed for scenarios involving long-running Resty clients where certificates may be renewed.
1520-
// The caller is responsible for calling Close to stop the watcher.
1521-
//
1522-
// client.SetRootCertificateWatcher("root-ca.crt", &CertWatcherOptions{
1523-
// PoolInterval: time.Hour * 24,
1524-
// })
15251578
//
1526-
// defer client.Close()
1527-
func (c *Client) SetRootCertificateWatcher(pemFilePath string, options *CertWatcherOptions) *Client {
1528-
c.SetRootCertificate(pemFilePath)
1529-
c.initCertWatcher(pemFilePath, "root", options)
1579+
// client.SetRootCertificatesWatcher(
1580+
// &resty.CertWatcherOptions{
1581+
// PoolInterval: 24 * time.Hour,
1582+
// },
1583+
// "root-ca.crt",
1584+
// )
1585+
func (c *Client) SetRootCertificatesWatcher(options *CertWatcherOptions, pemFilePaths ...string) *Client {
1586+
c.SetRootCertificates(pemFilePaths...)
1587+
for _, fp := range pemFilePaths {
1588+
c.initCertWatcher(fp, "root", options)
1589+
}
15301590
return c
15311591
}
15321592

15331593
// SetRootCertificateFromString method helps to add one or more root certificates
15341594
// into the Resty client
15351595
//
1536-
// client.SetRootCertificateFromString("pem certs content")
1596+
// myRootCertStr := `-----BEGIN CERTIFICATE-----
1597+
// ... cert content ...
1598+
// -----END CERTIFICATE-----`
1599+
//
1600+
// client.SetRootCertificateFromString(myRootCertStr)
15371601
func (c *Client) SetRootCertificateFromString(pemCerts string) *Client {
15381602
c.handleCAs("root", []byte(pemCerts))
15391603
return c
15401604
}
15411605

1542-
// SetClientRootCertificate method helps to add one or more client's root
1606+
// SetClientRootCertificates method helps to add one or more client's root
15431607
// certificates into the Resty client
15441608
//
1545-
// client.SetClientRootCertificate("/path/to/root/pemFile.pem")
1546-
func (c *Client) SetClientRootCertificate(pemFilePath string) *Client {
1547-
rootPemData, err := os.ReadFile(pemFilePath)
1548-
if err != nil {
1549-
c.Logger().Errorf("%v", err)
1550-
return c
1609+
// // one pem file path
1610+
// client.SetClientCertificates("/path/to/client/pemFile.pem")
1611+
//
1612+
// // one or more pem file path(s)
1613+
// client.SetClientCertificates(
1614+
// "/path/to/client/pemFile1.pem",
1615+
// "/path/to/client/pemFile2.pem"
1616+
// "/path/to/client/pemFile3.pem"
1617+
// )
1618+
//
1619+
// // if you happen to have string slices
1620+
// client.SetClientCertificates(certs...)
1621+
func (c *Client) SetClientRootCertificates(pemFilePaths ...string) *Client {
1622+
for _, fp := range pemFilePaths {
1623+
pemData, err := os.ReadFile(fp)
1624+
if err != nil {
1625+
c.Logger().Errorf("%v", err)
1626+
return c
1627+
}
1628+
c.handleCAs("client-root", pemData)
15511629
}
1552-
c.handleCAs("client", rootPemData)
15531630
return c
15541631
}
15551632

1556-
// SetClientRootCertificateWatcher enables dynamic reloading of one or more client root certificates.
1633+
// SetClientRootCertificatesWatcher method enables dynamic reloading of one or more client root certificates.
15571634
// It is designed for scenarios involving long-running Resty clients where certificates may be renewed.
1558-
// The caller is responsible for calling Close to stop the watcher.
15591635
//
1560-
// client.SetClientRootCertificateWatcher("root-ca.crt", &CertWatcherOptions{
1561-
// PoolInterval: time.Hour * 24,
1562-
// })
1563-
// defer client.Close()
1564-
func (c *Client) SetClientRootCertificateWatcher(pemFilePath string, options *CertWatcherOptions) *Client {
1565-
c.SetClientRootCertificate(pemFilePath)
1566-
c.initCertWatcher(pemFilePath, "client", options)
1636+
// client.SetClientRootCertificatesWatcher(
1637+
// &resty.CertWatcherOptions{
1638+
// PoolInterval: 24 * time.Hour,
1639+
// },
1640+
// "root-ca.crt",
1641+
// )
1642+
func (c *Client) SetClientRootCertificatesWatcher(options *CertWatcherOptions, pemFilePaths ...string) *Client {
1643+
c.SetClientRootCertificates(pemFilePaths...)
1644+
for _, fp := range pemFilePaths {
1645+
c.initCertWatcher(fp, "client-root", options)
1646+
}
1647+
return c
1648+
}
15671649

1650+
// SetClientRootCertificateFromString method helps to add one or more clients
1651+
// root certificates into the Resty client
1652+
//
1653+
// myClientRootCertStr := `-----BEGIN CERTIFICATE-----
1654+
// ... cert content ...
1655+
// -----END CERTIFICATE-----`
1656+
//
1657+
// client.SetClientRootCertificateFromString(myClientRootCertStr)
1658+
func (c *Client) SetClientRootCertificateFromString(pemCerts string) *Client {
1659+
c.handleCAs("client-root", []byte(pemCerts))
15681660
return c
15691661
}
15701662

1663+
func (c *Client) handleCAs(scope string, permCerts []byte) {
1664+
config, err := c.tlsConfig()
1665+
if err != nil {
1666+
c.Logger().Errorf("%v", err)
1667+
return
1668+
}
1669+
1670+
c.lock.Lock()
1671+
defer c.lock.Unlock()
1672+
switch scope {
1673+
case "root":
1674+
if config.RootCAs == nil {
1675+
config.RootCAs = x509.NewCertPool()
1676+
}
1677+
config.RootCAs.AppendCertsFromPEM(permCerts)
1678+
case "client-root":
1679+
if config.ClientCAs == nil {
1680+
config.ClientCAs = x509.NewCertPool()
1681+
}
1682+
config.ClientCAs.AppendCertsFromPEM(permCerts)
1683+
}
1684+
}
1685+
15711686
func (c *Client) initCertWatcher(pemFilePath, scope string, options *CertWatcherOptions) {
15721687
tickerDuration := defaultWatcherPoolingInterval
15731688
if options != nil && options.PoolInterval > 0 {
@@ -1611,9 +1726,9 @@ func (c *Client) initCertWatcher(pemFilePath, scope string, options *CertWatcher
16111726

16121727
switch scope {
16131728
case "root":
1614-
c.SetRootCertificate(pemFilePath)
1615-
case "client":
1616-
c.SetClientRootCertificate(pemFilePath)
1729+
c.SetRootCertificates(pemFilePath)
1730+
case "client-root":
1731+
c.SetClientRootCertificates(pemFilePath)
16171732
}
16181733

16191734
c.debugf("Cert %s reloaded.", pemFilePath)
@@ -1622,38 +1737,6 @@ func (c *Client) initCertWatcher(pemFilePath, scope string, options *CertWatcher
16221737
}()
16231738
}
16241739

1625-
// SetClientRootCertificateFromString method helps to add one or more clients
1626-
// root certificates into the Resty client
1627-
//
1628-
// client.SetClientRootCertificateFromString("pem certs content")
1629-
func (c *Client) SetClientRootCertificateFromString(pemCerts string) *Client {
1630-
c.handleCAs("client", []byte(pemCerts))
1631-
return c
1632-
}
1633-
1634-
func (c *Client) handleCAs(scope string, permCerts []byte) {
1635-
config, err := c.tlsConfig()
1636-
if err != nil {
1637-
c.Logger().Errorf("%v", err)
1638-
return
1639-
}
1640-
1641-
c.lock.Lock()
1642-
defer c.lock.Unlock()
1643-
switch scope {
1644-
case "root":
1645-
if config.RootCAs == nil {
1646-
config.RootCAs = x509.NewCertPool()
1647-
}
1648-
config.RootCAs.AppendCertsFromPEM(permCerts)
1649-
case "client":
1650-
if config.ClientCAs == nil {
1651-
config.ClientCAs = x509.NewCertPool()
1652-
}
1653-
config.ClientCAs.AppendCertsFromPEM(permCerts)
1654-
}
1655-
}
1656-
16571740
// OutputDirectory method returns the output directory value from the client.
16581741
func (c *Client) OutputDirectory() string {
16591742
c.lock.RLock()

0 commit comments

Comments
 (0)