Skip to content

Commit 5f9341c

Browse files
committed
Merge branch 'jtv4k-master'
Change-Id: I84b0b443c8eec74a4ade17fd48804934876c841a
2 parents 915670b + b9b5bc1 commit 5f9341c

File tree

6 files changed

+124
-58
lines changed

6 files changed

+124
-58
lines changed

core/connection/tlsconfig.go

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package connection
22

33
import (
4+
"bytes"
45
"crypto/tls"
56
"crypto/x509"
67
"encoding/asn1"
@@ -9,10 +10,14 @@ import (
910
"errors"
1011
"fmt"
1112
"io/ioutil"
13+
"strings"
1214
)
1315

1416
// TLSConfig contains options for configuring a TLS connection to the server.
15-
type TLSConfig struct{ *tls.Config }
17+
type TLSConfig struct {
18+
*tls.Config
19+
clientCertPass func() string
20+
}
1621

1722
// NewTLSConfig creates a new TLSConfig.
1823
func NewTLSConfig() *TLSConfig {
@@ -22,6 +27,13 @@ func NewTLSConfig() *TLSConfig {
2227
return cfg
2328
}
2429

30+
// SetClientCertDecryptPassword sets a function to retrieve the decryption password
31+
// necessary to read a certificate. This is a function instead of a string to
32+
// provide greater flexibility when deciding how to retrieve and store the password.
33+
func (c *TLSConfig) SetClientCertDecryptPassword(f func() string) {
34+
c.clientCertPass = f
35+
}
36+
2537
// SetInsecure sets whether the client should verify the server's certificate
2638
// chain and hostnames.
2739
func (c *TLSConfig) SetInsecure(allow bool) {
@@ -63,23 +75,55 @@ func (c *TLSConfig) AddClientCertFromFile(clientFile string) (string, error) {
6375
return "", err
6476
}
6577

66-
cert, err := tls.X509KeyPair(data, data)
78+
var currentBlock *pem.Block
79+
var certBlock, certDecodedBlock, keyBlock []byte
80+
81+
remaining := data
82+
start := 0
83+
for {
84+
currentBlock, remaining = pem.Decode(remaining)
85+
if currentBlock == nil {
86+
break
87+
}
88+
89+
if currentBlock.Type == "CERTIFICATE" {
90+
certBlock = data[start : len(data)-len(remaining)]
91+
certDecodedBlock = currentBlock.Bytes
92+
start += len(certBlock)
93+
} else if strings.HasSuffix(currentBlock.Type, "PRIVATE KEY") {
94+
if c.clientCertPass != nil && x509.IsEncryptedPEMBlock(currentBlock) {
95+
var encoded bytes.Buffer
96+
buf, err := x509.DecryptPEMBlock(currentBlock, []byte(c.clientCertPass()))
97+
if err != nil {
98+
return "", err
99+
}
100+
101+
pem.Encode(&encoded, &pem.Block{Type: currentBlock.Type, Bytes: buf})
102+
keyBlock = encoded.Bytes()
103+
start = len(data) - len(remaining)
104+
} else {
105+
keyBlock = data[start : len(data)-len(remaining)]
106+
start += len(keyBlock)
107+
}
108+
}
109+
}
110+
if len(certBlock) == 0 {
111+
return "", fmt.Errorf("failed to find CERTIFICATE")
112+
}
113+
if len(keyBlock) == 0 {
114+
return "", fmt.Errorf("failed to find PRIVATE KEY")
115+
}
116+
117+
cert, err := tls.X509KeyPair(certBlock, keyBlock)
67118
if err != nil {
68119
return "", err
69120
}
70121

71122
c.Certificates = append(c.Certificates, cert)
72123

73124
// The documentation for the tls.X509KeyPair indicates that the Leaf certificate is not
74-
// retained. Because there isn't any way of creating a tls.Certificate from an x509.Certificate
75-
// short of calling X509KeyPair on the raw bytes, we're forced to parse the certificate over
76-
// again to get the subject name.
77-
certBytes, err := loadCert(data)
78-
if err != nil {
79-
return "", err
80-
}
81-
82-
crt, err := x509.ParseCertificate(certBytes)
125+
// retained.
126+
crt, err := x509.ParseCertificate(certDecodedBlock)
83127
if err != nil {
84128
return "", err
85129
}

core/connection/tlsconfig_clone_17.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "crypto/tls"
66
// used concurrently by a TLS client or server.
77
func (c *TLSConfig) Clone() *TLSConfig {
88
cfg := cloneconfig(c.Config)
9-
return &TLSConfig{cfg}
9+
return &TLSConfig{cfg, c.clientCertPass}
1010
}
1111

1212
func cloneconfig(c *tls.Config) *tls.Config {

core/connstring/connstring.go

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -32,52 +32,54 @@ func Parse(s string) (ConnString, error) {
3232

3333
// ConnString represents a connection string to mongodb.
3434
type ConnString struct {
35-
Original string
36-
AppName string
37-
AuthMechanism string
38-
AuthMechanismProperties map[string]string
39-
AuthSource string
40-
Connect ConnectMode
41-
ConnectSet bool
42-
ConnectTimeout time.Duration
43-
ConnectTimeoutSet bool
44-
Database string
45-
HeartbeatInterval time.Duration
46-
HeartbeatIntervalSet bool
47-
Hosts []string
48-
J bool
49-
JSet bool
50-
LocalThreshold time.Duration
51-
LocalThresholdSet bool
52-
MaxConnIdleTime time.Duration
53-
MaxConnIdleTimeSet bool
54-
MaxConnLifeTime time.Duration
55-
MaxConnsPerHost uint16
56-
MaxConnsPerHostSet bool
57-
MaxIdleConnsPerHost uint16
58-
MaxIdleConnsPerHostSet bool
59-
Password string
60-
PasswordSet bool
61-
ReadConcernLevel string
62-
ReadPreference string
63-
ReadPreferenceTagSets []map[string]string
64-
ReplicaSet string
65-
ServerSelectionTimeout time.Duration
66-
ServerSelectionTimeoutSet bool
67-
SocketTimeout time.Duration
68-
SocketTimeoutSet bool
69-
SSL bool
70-
SSLSet bool
71-
SSLClientCertificateKeyFile string
72-
SSLClientCertificateKeyFileSet bool
73-
SSLInsecure bool
74-
SSLInsecureSet bool
75-
SSLCaFile string
76-
SSLCaFileSet bool
77-
WString string
78-
WNumber int
79-
WNumberSet bool
80-
Username string
35+
Original string
36+
AppName string
37+
AuthMechanism string
38+
AuthMechanismProperties map[string]string
39+
AuthSource string
40+
Connect ConnectMode
41+
ConnectSet bool
42+
ConnectTimeout time.Duration
43+
ConnectTimeoutSet bool
44+
Database string
45+
HeartbeatInterval time.Duration
46+
HeartbeatIntervalSet bool
47+
Hosts []string
48+
J bool
49+
JSet bool
50+
LocalThreshold time.Duration
51+
LocalThresholdSet bool
52+
MaxConnIdleTime time.Duration
53+
MaxConnIdleTimeSet bool
54+
MaxConnLifeTime time.Duration
55+
MaxConnsPerHost uint16
56+
MaxConnsPerHostSet bool
57+
MaxIdleConnsPerHost uint16
58+
MaxIdleConnsPerHostSet bool
59+
Password string
60+
PasswordSet bool
61+
ReadConcernLevel string
62+
ReadPreference string
63+
ReadPreferenceTagSets []map[string]string
64+
ReplicaSet string
65+
ServerSelectionTimeout time.Duration
66+
ServerSelectionTimeoutSet bool
67+
SocketTimeout time.Duration
68+
SocketTimeoutSet bool
69+
SSL bool
70+
SSLSet bool
71+
SSLClientCertificateKeyFile string
72+
SSLClientCertificateKeyFileSet bool
73+
SSLClientCertificateKeyPassword func() string
74+
SSLClientCertificateKeyPasswordSet bool
75+
SSLInsecure bool
76+
SSLInsecureSet bool
77+
SSLCaFile string
78+
SSLCaFileSet bool
79+
WString string
80+
WNumber int
81+
WNumberSet bool
82+
Username string
8183

8284
WTimeout time.Duration
8385
WTimeoutSet bool
@@ -472,6 +474,9 @@ func (p *parser) addOption(pair string) error {
472474
p.SSLSet = true
473475
p.SSLClientCertificateKeyFile = value
474476
p.SSLClientCertificateKeyFileSet = true
477+
case "sslclientcertificatekeypassword":
478+
p.SSLClientCertificateKeyPassword = func() string { return value }
479+
p.SSLClientCertificateKeyPasswordSet = true
475480
case "sslinsecure":
476481
switch value {
477482
case "true":

core/topology/topology_options.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ func WithConnString(fn func(connstring.ConnString) connstring.ConnString) Option
101101
}
102102

103103
if cs.SSLClientCertificateKeyFileSet {
104+
if cs.SSLClientCertificateKeyPasswordSet && cs.SSLClientCertificateKeyPassword != nil {
105+
tlsConfig.SetClientCertDecryptPassword(cs.SSLClientCertificateKeyPassword)
106+
}
104107
s, err := tlsConfig.AddClientCertFromFile(cs.SSLClientCertificateKeyFile)
105108
if err != nil {
106109
return err

mongo/client_options.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,19 @@ func (co *ClientOptions) SSLClientCertificateKeyFile(s string) *ClientOptions {
323323
return &ClientOptions{next: co, opt: fn}
324324
}
325325

326+
// SSLClientCertificateKeyPassword provides a callback that returns a password used for decrypting the
327+
// private key of a PEM file (if one is provided).
328+
func (co *ClientOptions) SSLClientCertificateKeyPassword(s func() string) *ClientOptions {
329+
var fn option = func(c *Client) error {
330+
if !c.connString.SSLClientCertificateKeyPasswordSet {
331+
c.connString.SSLClientCertificateKeyPassword = s
332+
c.connString.SSLClientCertificateKeyPasswordSet = true
333+
}
334+
return nil
335+
}
336+
return &ClientOptions{next: co, opt: fn}
337+
}
338+
326339
// SSLInsecure indicates whether to skip the verification of the server certificate and hostname.
327340
func (co *ClientOptions) SSLInsecure(b bool) *ClientOptions {
328341
var fn option = func(c *Client) error {

mongo/client_options_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func TestClientOptions_chainAll(t *testing.T) {
8686
SocketTimeout(2 * time.Second).
8787
SSL(true).
8888
SSLClientCertificateKeyFile("client.pem").
89+
SSLClientCertificateKeyPassword(func() string { return "password" }).
8990
SSLInsecure(false).
9091
SSLCaFile("ca.pem").
9192
WString("majority").

0 commit comments

Comments
 (0)