Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TOOLS-2013: Support PKCS #8 encrypted client private keys #64

Merged
merged 3 commits into from Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .evergreen.yml
Expand Up @@ -195,6 +195,7 @@ functions:
if [ "${USE_SSL}" = "true" ]; then
export TOOLS_TESTING_SSL="true"
export TOOLS_TESTING_INTEGRATION="true"
export TOOLS_TESTING_PKCS8_PASSWORD=${pkcs8_password}
fi;
if [ "${AUTH_TEST}" = "true" ]; then
export TOOLS_TESTING_AUTH="true"
Expand Down
9 changes: 9 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Expand Up @@ -48,3 +48,7 @@
go-tests = true
unused-packages = true


[[constraint]]
name = "github.com/youmark/pkcs8"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: we need to update the THIRD-PARTY-NOTICES file in mirror and sqlproxy once we revendor mtc.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@edobranov could you file a ticket for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, filed TOOLS-2787.

version = "1.1.0"
26 changes: 26 additions & 0 deletions THIRD-PARTY-NOTICES
Expand Up @@ -1257,6 +1257,32 @@ License notice for github.com/xdg/stringprep
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

----------------------------------------------------------------------
License notice for github.com/youmark/pkcs8
----------------------------------------------------------------------

The MIT License (MIT)

Copyright (c) 2014 youmark

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

----------------------------------------------------------------------
License notice for golang.org/x/crypto
----------------------------------------------------------------------
Expand Down
35 changes: 29 additions & 6 deletions db/db.go
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/mongodb/mongo-tools-common/log"
"github.com/mongodb/mongo-tools-common/options"
"github.com/mongodb/mongo-tools-common/password"
"github.com/youmark/pkcs8"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
mopt "go.mongodb.org/mongo-driver/mongo/options"
Expand Down Expand Up @@ -189,14 +190,35 @@ func addClientCertFromBytes(cfg *tls.Config, data []byte, keyPasswd string) (str
certDecodedBlock = currentBlock.Bytes
start += len(certBlock)
} else if strings.HasSuffix(currentBlock.Type, "PRIVATE KEY") {
if keyPasswd != "" && x509.IsEncryptedPEMBlock(currentBlock) {
var encoded bytes.Buffer
buf, err := x509.DecryptPEMBlock(currentBlock, []byte(keyPasswd))
if err != nil {
return "", err
isEncrypted := x509.IsEncryptedPEMBlock(currentBlock) || strings.Contains(currentBlock.Type, "ENCRYPTED PRIVATE KEY")
if isEncrypted {
if keyPasswd == "" {
return "", fmt.Errorf("no password provided to decrypt private key")
}

var keyBytes []byte
var err error
// Process the X.509-encrypted or PKCS-encrypted PEM block.
if x509.IsEncryptedPEMBlock(currentBlock) {
// Only covers encrypted PEM data with a DEK-Info header.
keyBytes, err = x509.DecryptPEMBlock(currentBlock, []byte(keyPasswd))
if err != nil {
return "", err
}
} else if strings.Contains(currentBlock.Type, "ENCRYPTED") {
// The pkcs8 package only handles the PKCS #5 v2.0 scheme.
decrypted, err := pkcs8.ParsePKCS8PrivateKey(currentBlock.Bytes, []byte(keyPasswd))
if err != nil {
return "", err
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my understanding, there's a couple ways to encrypt a PEM key in PKCS 8 format. The only one that the pkcs8 Go package can currently handle is called the PKCS 5 v2.0 scheme. The first time I tried this, I accidentally used the wrong scheme and got this error:

error configuring the connector: error configuring client, can't load client certificate: pkcs8: only PKCS #5 v2.0 supported

Even though the error is probably specific enough, I'm wondering if we should make a note of this in the docs under the --sslPEMKeyFile entry and provide guidance on getting the right scheme (e.g. using openssl with the -v2 des3 option to get PKCS 5 v2.0).

I'm okay either way, but curious to hear others' thoughts.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! I agree we should add this limitation to the doc so that it won't cause confusion to the client.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this is a limitation significant enough to warrant a mention in the docs. Generally, the docs describe what we do support, and I think a limitation has to be pretty notable/important for us to want to describe what we don't do.

keyBytes, err = x509.MarshalPKCS8PrivateKey(decrypted)
if err != nil {
return "", err
}
}

pem.Encode(&encoded, &pem.Block{Type: currentBlock.Type, Bytes: buf})
var encoded bytes.Buffer
pem.Encode(&encoded, &pem.Block{Type: currentBlock.Type, Bytes: keyBytes})
keyBlock = encoded.Bytes()
start = len(data) - len(remaining)
} else {
Expand All @@ -205,6 +227,7 @@ func addClientCertFromBytes(cfg *tls.Config, data []byte, keyPasswd string) (str
}
}
}

if len(certBlock) == 0 {
return "", fmt.Errorf("failed to find CERTIFICATE")
}
Expand Down
58 changes: 57 additions & 1 deletion db/db_test.go
Expand Up @@ -8,10 +8,11 @@ package db

import (
"context"
"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"
"os"
"testing"

"go.mongodb.org/mongo-driver/x/mongo/driver/connstring"

"github.com/mongodb/mongo-tools-common/options"
"github.com/mongodb/mongo-tools-common/testtype"
. "github.com/smartystreets/goconvey/convey"
Expand All @@ -24,6 +25,7 @@ var (
UserAdminPassword = "password"
CreatedUserNameEnv = "TOOLS_TESTING_AUTH_USERNAME"
CreatedUserPasswordEnv = "TOOLS_TESTING_AUTH_PASSWORD"
PKCS8Password = "TOOLS_TESTING_PKCS8_PASSWORD"
kerberosUsername = "drivers%40LDAPTEST.10GEN.CC"
kerberosConnection = "ldaptest.10gen.cc:27017"
)
Expand Down Expand Up @@ -345,6 +347,60 @@ func TestServerCertificateVerification(t *testing.T) {
})
}

func TestServerPKCS8Verification(t *testing.T) {
testtype.SkipUnlessTestType(t, testtype.IntegrationTestType)
testtype.SkipUnlessTestType(t, testtype.SSLTestType)

Convey("when initializing a session provider, connection succeeds", t, func() {
auth := DBGetAuthOptions()
ssl := options.SSL{
UseSSL: true,
SSLCAFile: "../db/testdata/ca-ia.pem",
}

Convey("if provided with PEM file in PKCS#8 format with unencrypted password", func() {
ssl.SSLPEMKeyFile = "../db/testdata/test-client-pkcs8-unencrypted.pem"
opts := options.ToolOptions{
Connection: &options.Connection{
Port: DefaultTestPort,
Timeout: 10,
},
URI: DBGetConnString(),
SSL: &ssl,
Auth: &auth,
}
opts.URI.ConnString.SSLCaFile = "../db/testdata/ca-ia.pem"
provider, err := NewSessionProvider(opts)
So(err, ShouldBeNil)
So(provider.client.Ping(context.Background(), nil), ShouldBeNil)
Convey("and should be closeable", func() {
provider.Close()
})
})

Convey("if provided with PEM file in PKCS#8 format with encrypted password", func() {
ssl.SSLPEMKeyFile = "../db/testdata/test-client-pkcs8-encrypted.pem"
ssl.SSLPEMKeyPassword = os.Getenv(PKCS8Password)
opts := options.ToolOptions{
Connection: &options.Connection{
Port: DefaultTestPort,
Timeout: 10,
},
URI: DBGetConnString(),
SSL: &ssl,
Auth: &auth,
}
opts.URI.ConnString.SSLCaFile = "../db/testdata/ca-ia.pem"
provider, err := NewSessionProvider(opts)
So(err, ShouldBeNil)
So(provider.client.Ping(context.Background(), nil), ShouldBeNil)
Convey("and should be closeable", func() {
provider.Close()
})
})
})
}

func TestAuthConnection(t *testing.T) {
if !testtype.HasTestType(testtype.AWSAuthTestType) && !testtype.HasTestType(testtype.KerberosTestType) {
t.SkipNow()
Expand Down
94 changes: 94 additions & 0 deletions db/testdata/test-client-pkcs8-encrypted.pem
@@ -0,0 +1,94 @@
-----BEGIN CERTIFICATE-----
MIIGYTCCBEmgAwIBAgIJAL+8WDUncZFZMA0GCSqGSIb3DQEBCwUAMIGaMQswCQYD
VQQGEwJVUzELMAkGA1UECAwCTlkxJDAiBgNVBAcMG1Rlc3RDZXJ0aWZpY2F0ZUxv
Y2FsaXR5TmFtZTEfMB0GA1UECgwWVGVzdENlcnRpZmljYXRlT3JnTmFtZTEjMCEG
A1UECwwaVGVzdENlcnRpZmljYXRlT3JnVW5pdE5hbWUxEjAQBgNVBAMMCWxvY2Fs
aG9zdDAeFw0yMDA1MTQyMjU2MzRaFw0yMTA1MTQyMjU2MzRaMIGMMQswCQYDVQQG
EwJVUzELMAkGA1UECAwCTlkxJjAkBgNVBAcMHVRlc3RDbGllbnRDZXJ0aWZpY2F0
ZUxvY2FsaXR5MSEwHwYDVQQKDBhUZXN0Q2xpZW50Q2VydGlmaWNhdGVPcmcxJTAj
BgNVBAsMHFRlc3RDbGllbnRDZXJ0aWZpY2F0ZU9yZ1VuaXQwggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQDCxxLZEQlJ8WKABPr7d74ra/Mqh6dPwk0Zos5A
r3JIlq6/OJ91k0GjgewQn8N/Jhr3BYhlscXhuB1C0gI7agbi58fU8TMOvcqsDM3G
T9XehOedls5HMBmxkz6l6KXcCRHamhQv09nyBw3VOkoby/AoMdwCZgJIAOGfw8Lp
dAO71X5llzDPKVQtpA8NQF7uZq6Qv72Papf5OgxN5LwOH1IXW2Yd+Y/j4p0Z7j+x
31zJ/1Don31CQZK2b7qDojh3X0DH5HO883fw8Q1j9PQc7pPJ4OoXK170cycwln/o
fsKQsGlaXzJcz0F3XO3xlpgA6ScfJ4F/92kB34IlutFbxacNjoLa1QeolwNYcoO2
cdL/4XHpgQgwblOcBnSkJoakYeVBwRVaRUxmG8b9bBzLXtOy078naFED6Q6LzP7+
cT9ZSPdOxk728JMpjIsEFAi5Fpv/nVuGx9/+xIOF1w/Q+Y/nvvThEGc7X08WtLeJ
tKFTE7OWX+UVeuR4g8UEYEdAGQzstBaDjVtLGky57QY7HZaNbWYUPu6As6HYKazK
xBXDFs2Cpg8zIroTwjBh7+F56bq06xGZnXX/iXUULKwgwOaAN9cAd5dLX+PFPO9z
R2Mt5UWYtUL4295JGabDPqI0nDWmv8Omtbat4cHHajAePUn28jy5+32zbt2Bqe/T
R9/tdQIDAQABo4G1MIGyMB0GA1UdDgQWBBQMaRN+ZH/PO1MrfSYYsOoSRXuR7jAJ
BgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIFoDBXBglghkgBhvhCAQ0EShZIT3BlblNT
TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUgZm9yIFRFU1RJTkcgb25seS4gIE5PVCBG
T1IgUFJPRFVDVElPTiBVU0UuMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
AjANBgkqhkiG9w0BAQsFAAOCAgEAgCSb//bj5EOp6v8mvmn688e34DHRevkrBfUt
SrVyO7vsuZvb0xa8nAKnvpH6h85n5S6VDBKznQndYOPYX1w7QeJK6ZHJYNv9wRs3
9bO7hT9LiRIas+DwEAheW9KRC2U1xtHNlr5UnkbWqfK0n4u/b1oJKA2wz0wl+B/k
tbZgEYaa8RIMc0uOel4hzaYUygXKNL8JXoDGzXKqcUO/a7tCW55CQQW8o5NT8sTX
wwGyNl3vjxxjL3DDJd8HdE3pqnh3Q8sYihipF6K6KGfAj6N4ZH1lZfwP+n7C5vHf
w5XXhrJ0il+psjSbnGWtZx5GOC1axx1xUxfOMGY//iiwnzSqXfghBX2eMHix1RuB
4vmLDI6l/t2LfI84Xm+BYrOed6jkb2pfGsK9WK8jzeEX6zkZSDBeHU7ztD7/Jc3p
Ggl9vRji4Gdqf1rXfiy6P3ke8rVFEoWT73Ocqa+pPhVUhVt4VPnG7dxw3hIA+WOX
GUOkdG5BLBByhixgTv/dK6B5ka+Y4qfFHRB+DvlMnmDRu+q3fhpAsG2ED6xapLOa
eDRJYVtFPlgtGzT+b38tnbWHDzk7OCjJdRGF9x+gBZuLsxfhJ4wKcFrbwie+aypj
5QrzX7X9PG36YCcQPxvzHDwH1OGJaqcd/ZUwHxCapPGamC2jSM5ATzhgTNTFDxVh
On1Mo4o=
-----END CERTIFICATE-----
-----BEGIN ENCRYPTED PRIVATE KEY-----
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
fWE=
-----END ENCRYPTED PRIVATE KEY-----

Generated via:
$ openssl pkcs8 -v2 des3 -topk8 -inform PEM -outform PEM -in testdata/test-client.pem -out testdata/test-client-pkcs8-encrypted.pem