Skip to content

Commit 1c49429

Browse files
WIP
1 parent b30e2b6 commit 1c49429

13 files changed

+385
-69
lines changed

.github/workflows/testing.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ jobs:
143143
- sdk-version: 'bundle-2.10.0-1-gfa775b383-r486-linux-x86_64'
144144
coveralls: false
145145
ssl: true
146-
- sdk-path: 'dev/linux/x86_64/master/'
147-
sdk-version: 'sdk-gc64-2.11.0-entrypoint-113-g803baaffe-r529.linux.x86_64'
146+
- sdk-path: 'release/linux/x86_64/2.11/'
147+
sdk-version: 'sdk-gc64-2.11.0-0-r563.linux.x86_64'
148148
coveralls: true
149149
ssl: true
150150

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,15 @@
44
work_dir*
55
.rocks
66
bench*
7+
8+
testdata/*.crt
9+
!testdata/ca.crt
10+
!testdata/invalidhost.crt
11+
!testdata/localhost.crt
12+
testdata/*.csr
13+
testdata/*.ext
14+
testdata/*.key
15+
!testdata/localhost.key
16+
!testdata/localhost.enc.key
17+
testdata/*.pem
18+
testdata/*.srl

connection.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,11 @@ type SslOpts struct {
345345
//
346346
// * https://www.openssl.org/docs/man1.1.1/man1/ciphers.html
347347
Ciphers string
348+
// Password is a password for decrypting private SSL key file.
349+
Password string
350+
// PasswordFile is a path to the list of passwords for decrypting private SSL
351+
// key file. Connection tries every line from the file as a password.
352+
PasswordFile string
348353
}
349354

350355
// Clone returns a copy of the Opts object.

ssl.go

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
package tarantool
55

66
import (
7+
"bufio"
78
"errors"
9+
"fmt"
810
"io/ioutil"
911
"net"
12+
"os"
13+
"strings"
1014
"time"
1115

1216
"github.com/tarantool/go-openssl"
@@ -43,7 +47,7 @@ func sslCreateContext(opts SslOpts) (ctx interface{}, err error) {
4347
}
4448

4549
if opts.KeyFile != "" {
46-
if err = sslLoadKey(sslCtx, opts.KeyFile); err != nil {
50+
if err = sslLoadKey(sslCtx, opts.KeyFile, opts.Password, opts.PasswordFile); err != nil {
4751
return
4852
}
4953
}
@@ -95,16 +99,67 @@ func sslLoadCert(ctx *openssl.Ctx, certFile string) (err error) {
9599
return
96100
}
97101

98-
func sslLoadKey(ctx *openssl.Ctx, keyFile string) (err error) {
102+
func sslLoadKey(ctx *openssl.Ctx, keyFile string, password string,
103+
passwordFile string) error {
99104
var keyBytes []byte
105+
var err error
106+
100107
if keyBytes, err = ioutil.ReadFile(keyFile); err != nil {
101-
return
108+
return err
102109
}
103110

104111
var key openssl.PrivateKey
105-
if key, err = openssl.LoadPrivateKeyFromPEM(keyBytes); err != nil {
106-
return
112+
var errs []error
113+
114+
// If key is encrypted and password is not provided,
115+
// openssl.LoadPrivateKeyFromPEM(keyBytes) asks to enter PEM pass phrase
116+
// interactively. On the other hand,
117+
// openssl.LoadPrivateKeyFromPEMWithPassword(keyBytes, '') works fine for
118+
// non-encrypted key.
119+
if key, err = openssl.LoadPrivateKeyFromPEMWithPassword(keyBytes, password); err == nil {
120+
return ctx.UsePrivateKey(key)
121+
} else {
122+
errs = append(errs, err)
123+
}
124+
125+
if passwordFile != "" {
126+
var file *os.File
127+
file, err = os.Open(passwordFile)
128+
if err == nil {
129+
defer file.Close()
130+
131+
scanner := bufio.NewScanner(file)
132+
for scanner.Scan() {
133+
password = strings.TrimSpace(scanner.Text())
134+
135+
if key, err = openssl.LoadPrivateKeyFromPEMWithPassword(keyBytes, password); err == nil {
136+
return ctx.UsePrivateKey(key)
137+
} else {
138+
errs = append(errs, err)
139+
}
140+
}
141+
} else {
142+
errs = append(errs, err)
143+
}
144+
}
145+
146+
if len(errs) > 1 {
147+
// Convenient multiple error wrapping was introduced only in Go 1.20
148+
// https://pkg.go.dev/errors#example-Join
149+
// https://github.com/golang/go/issues/53435
150+
rerr := errors.New("Got multiple errors on SSL decryption")
151+
var i int
152+
for i, err = range errs {
153+
if i == 0 {
154+
// gofmt forbids error strings to end with punctuation or newlines
155+
rerr = fmt.Errorf("%s: %w", rerr, err)
156+
} else {
157+
rerr = fmt.Errorf("%s, %w", rerr, err)
158+
}
159+
}
160+
161+
return rerr
107162
}
108163

109-
return ctx.UsePrivateKey(key)
164+
return errs[0]
110165
}

ssl_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ func serverTnt(serverOpts, clientOpts SslOpts, auth Auth) (test_helpers.Tarantoo
117117
listen += fmt.Sprintf("ssl_ciphers=%s&", ciphers)
118118
}
119119

120+
password := serverOpts.Password
121+
if password != "" {
122+
listen += fmt.Sprintf("ssl_password=%s&", password)
123+
}
124+
125+
passwordFile := serverOpts.PasswordFile
126+
if passwordFile != "" {
127+
listen += fmt.Sprintf("ssl_password_file=%s&", passwordFile)
128+
}
129+
120130
listen = listen[:len(listen)-1]
121131

122132
return test_helpers.StartTarantool(test_helpers.StartOpts{
@@ -139,6 +149,21 @@ func serverTntStop(inst test_helpers.TarantoolInstance) {
139149
test_helpers.StopTarantoolWithCleanup(inst)
140150
}
141151

152+
func skipEncrypted(t testing.TB, serverOpts SslOpts) {
153+
if serverOpts.Password == "" && serverOpts.PasswordFile == "" {
154+
return
155+
}
156+
157+
isLess, err := test_helpers.IsTarantoolVersionLess(2, 11, 0)
158+
if err != nil {
159+
t.Fatalf("Could not check the Tarantool version")
160+
}
161+
162+
if isLess {
163+
t.Skipf("Skipping test for Tarantool without SSL encryption support")
164+
}
165+
}
166+
142167
func assertConnectionSslFail(t testing.TB, serverOpts, clientOpts SslOpts) {
143168
t.Helper()
144169

@@ -171,6 +196,8 @@ func assertConnectionSslOk(t testing.TB, serverOpts, clientOpts SslOpts) {
171196
func assertConnectionTntFail(t testing.TB, serverOpts, clientOpts SslOpts) {
172197
t.Helper()
173198

199+
skipEncrypted(t, serverOpts)
200+
174201
inst, err := serverTnt(serverOpts, clientOpts, AutoAuth)
175202
serverTntStop(inst)
176203

@@ -182,6 +209,8 @@ func assertConnectionTntFail(t testing.TB, serverOpts, clientOpts SslOpts) {
182209
func assertConnectionTntOk(t testing.TB, serverOpts, clientOpts SslOpts) {
183210
t.Helper()
184211

212+
skipEncrypted(t, serverOpts)
213+
185214
inst, err := serverTnt(serverOpts, clientOpts, AutoAuth)
186215
serverTntStop(inst)
187216

@@ -431,6 +460,142 @@ var tests = []test{
431460
Ciphers: "TLS_AES_128_GCM_SHA256",
432461
},
433462
},
463+
{
464+
"pass_no_key_encrypt",
465+
true,
466+
SslOpts{
467+
KeyFile: "testdata/localhost.key",
468+
CertFile: "testdata/localhost.crt",
469+
CaFile: "testdata/ca.crt",
470+
},
471+
SslOpts{
472+
KeyFile: "testdata/localhost.enc.key",
473+
CertFile: "testdata/localhost.crt",
474+
Password: "mysslpassword",
475+
},
476+
},
477+
{
478+
"pass_file_no_key_encrypt",
479+
true,
480+
SslOpts{
481+
KeyFile: "testdata/localhost.key",
482+
CertFile: "testdata/localhost.crt",
483+
CaFile: "testdata/ca.crt",
484+
},
485+
SslOpts{
486+
KeyFile: "testdata/localhost.enc.key",
487+
CertFile: "testdata/localhost.crt",
488+
PasswordFile: "testdata/passwords",
489+
},
490+
},
491+
{
492+
"pass_key_encrypt",
493+
true,
494+
SslOpts{
495+
KeyFile: "testdata/localhost.enc.key",
496+
CertFile: "testdata/localhost.crt",
497+
CaFile: "testdata/ca.crt",
498+
Password: "mysslpassword",
499+
},
500+
SslOpts{
501+
KeyFile: "testdata/localhost.enc.key",
502+
CertFile: "testdata/localhost.crt",
503+
Password: "mysslpassword",
504+
},
505+
},
506+
{
507+
"pass_file_key_encrypt",
508+
true,
509+
SslOpts{
510+
KeyFile: "testdata/localhost.enc.key",
511+
CertFile: "testdata/localhost.crt",
512+
CaFile: "testdata/ca.crt",
513+
PasswordFile: "testdata/passwords",
514+
},
515+
SslOpts{
516+
KeyFile: "testdata/localhost.enc.key",
517+
CertFile: "testdata/localhost.crt",
518+
PasswordFile: "testdata/passwords",
519+
},
520+
},
521+
{
522+
"pass_and_pass_file_key_encrypt",
523+
true,
524+
SslOpts{
525+
KeyFile: "testdata/localhost.enc.key",
526+
CertFile: "testdata/localhost.crt",
527+
CaFile: "testdata/ca.crt",
528+
PasswordFile: "testdata/passwords",
529+
},
530+
SslOpts{
531+
KeyFile: "testdata/localhost.enc.key",
532+
CertFile: "testdata/localhost.crt",
533+
Password: "mysslpassword",
534+
PasswordFile: "testdata/passwords",
535+
},
536+
},
537+
{
538+
"inv_pass_and_pass_file_key_encrypt",
539+
true,
540+
SslOpts{
541+
KeyFile: "testdata/localhost.enc.key",
542+
CertFile: "testdata/localhost.crt",
543+
CaFile: "testdata/ca.crt",
544+
PasswordFile: "testdata/passwords",
545+
},
546+
SslOpts{
547+
KeyFile: "testdata/localhost.enc.key",
548+
CertFile: "testdata/localhost.crt",
549+
Password: "invalidpassword",
550+
PasswordFile: "testdata/passwords",
551+
},
552+
},
553+
{
554+
"pass_and_inv_pass_file_key_encrypt",
555+
true,
556+
SslOpts{
557+
KeyFile: "testdata/localhost.enc.key",
558+
CertFile: "testdata/localhost.crt",
559+
CaFile: "testdata/ca.crt",
560+
PasswordFile: "testdata/passwords",
561+
},
562+
SslOpts{
563+
KeyFile: "testdata/localhost.enc.key",
564+
CertFile: "testdata/localhost.crt",
565+
Password: "mysslpassword",
566+
PasswordFile: "testdata/invalidpasswords",
567+
},
568+
},
569+
{
570+
"pass_and_inv_pass_file_key_encrypt",
571+
false,
572+
SslOpts{
573+
KeyFile: "testdata/localhost.enc.key",
574+
CertFile: "testdata/localhost.crt",
575+
CaFile: "testdata/ca.crt",
576+
PasswordFile: "testdata/passwords",
577+
},
578+
SslOpts{
579+
KeyFile: "testdata/localhost.enc.key",
580+
CertFile: "testdata/localhost.crt",
581+
Password: "invalidpassword",
582+
PasswordFile: "testdata/invalidpasswords",
583+
},
584+
},
585+
{
586+
"no_pass_key_encrypt",
587+
false,
588+
SslOpts{
589+
KeyFile: "testdata/localhost.enc.key",
590+
CertFile: "testdata/localhost.crt",
591+
CaFile: "testdata/ca.crt",
592+
PasswordFile: "testdata/passwords",
593+
},
594+
SslOpts{
595+
KeyFile: "testdata/localhost.enc.key",
596+
CertFile: "testdata/localhost.crt",
597+
},
598+
},
434599
}
435600

436601
func isTestTntSsl() bool {

testdata/ca.crt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
-----BEGIN CERTIFICATE-----
2-
MIIDLzCCAhegAwIBAgIUMMZTmNkhr4qOfSwInVk2dAJvoBEwDQYJKoZIhvcNAQEL
2+
MIIDLzCCAhegAwIBAgIUaw6WTgYBXFRqlGbKRYczFApoNU4wDQYJKoZIhvcNAQEL
33
BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y
4-
MjA1MjYwNjE3NDBaFw00NDEwMjkwNjE3NDBaMCcxCzAJBgNVBAYTAlVTMRgwFgYD
4+
MzA3MjYwODM3MTZaFw00NTEyMjkwODM3MTZaMCcxCzAJBgNVBAYTAlVTMRgwFgYD
55
VQQDDA9FeGFtcGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
6-
AoIBAQCRq/eaA3I6CB8t770H2XDdzcp1yuC/+TZOxV5o0LuRkogTvL2kYULBrfx1
7-
rVZu8zQJTx1fmSRj1cN8j+IrmXN5goZ3mYFTnnIOgkyi+hJysVlo5s0Kp0qtLLGM
8-
OuaVbxw2oAy75if5X3pFpiDaMvFBtJKsh8+SkncBIC5bbKC5AoLdFANLmPiH0CGr
9-
Mv3rL3ycnbciI6J4uKHcWnYGGiMjBomaZ7jd/cOjcjmGfpI5d0nq13G11omkyEyR
10-
wNX0eJRL02W+93Xu7tD+FEFMxFvak+70GvX+XWomwYw/Pjlio8KbTAlJxhfK2Lh6
11-
H798k17VfxIrOk0KjzZS7+a20hZ/AgMBAAGjUzBRMB0GA1UdDgQWBBT2f5o8r75C
12-
PWST36akpkKRRTbhvjAfBgNVHSMEGDAWgBT2f5o8r75CPWST36akpkKRRTbhvjAP
13-
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA9pb75p6mnqp2MQHSr
14-
5SKRf2UV4wQIUtXgF6V9vNfvVzJii+Lzrqir1YMk5QgavCzD96KlJcqJCcH559RY
15-
5743AxI3tdWfA3wajBctoy35oYnT4M30qbkryYLTUlv7PmeNWvrksTURchyyDt5/
16-
3T73yj5ZalmzKN6+xLfUDdnudspfWlUMutKU50MU1iuQESf4Fwd53vOg9jMcWJ2E
17-
vAgfVI0XAvYdU3ybJrUvBq5zokYR2RzGv14uHxwVPnLBjrBEHRnbrXvLZJhuIS2b
18-
xZ3CqwWi+9bvNqHz09HvhkU2b6fCGweKaAUGSo8OfQ5FRkjTUomMI/ZLs/qtJ6JR
19-
zzVt
6+
AoIBAQCunm5E+dyoYw+ECp0vOabsA4L7C+dUQLhfqdOEwFSpSanjBTuUEAPB+fEr
7+
wqaZXI2EnUSxYEYO03TkZmWoJgRJq+00laWPA4AuKHpg4SS/LUoveQiQdsie+kUj
8+
YMFu3rtP2CvTMpC4HMRK2CviOnU9iA4hPvRx4o5tESxLW31jNnBDeC/tsEVVR/6i
9+
lwB9Oh1RbZI/c429N67qq5C2rpU5+o+YszDou36WTxw6XeXdkw7QF4W2BNLysPLJ
10+
AY+aPrUVxKDOgDNk77h41HDqu+SuDg6mg528yfRqyAd4ooEE8MLcT0xztn1U8HvZ
11+
SKwWTnS8TSzCmQptRGPlb5oES/NlAgMBAAGjUzBRMB0GA1UdDgQWBBQ/S0H0dFUy
12+
OuEQ/kgDzGarWm2vlDAfBgNVHSMEGDAWgBQ/S0H0dFUyOuEQ/kgDzGarWm2vlDAP
13+
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCoP+kH96+b6GWByPTo
14+
LRK/QJmpPRWiZ5naV6CXJQNYg+nVG9wdiGXxEx4BBZs6yGdeHdiWCWbuRPMyH0Wp
15+
w2ajMmK7pC8+MGKzMSC/EISPFwKYumwe/6zNnde7eZ19n7EFwrOihgEf+hNfjOCj
16+
CqDIfMb2ztEHY7mEABMXDviKI80om2P1oIkHj5MD7z8ZetJRf1qCH7ke2cdTJ+Zr
17+
XNGiJ7sz3xRQO/QRCkbBbr/d4zeX3A5/+MXHLtzbPiWs+/XaDbGJTQIO5hFfwwAC
18+
v1/VrNmsy+3YYLLTZzmfpa60Sk3NsZbIQdvwLJJj8pOmH/zae7UZlrxlGWjQKvH5
19+
evsP
2020
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)