Skip to content

Commit e6bacd0

Browse files
kevinAlbseramongodb
andcommitted
CDRIVER-4269 support RSA PKCS#8 keys with Secure Channel (mongodb#2024)
Co-authored-by: Ezra Chung <[email protected]>
1 parent e206584 commit e6bacd0

File tree

4 files changed

+190
-57
lines changed

4 files changed

+190
-57
lines changed

.evergreen/scripts/run-auth-tests.sh

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@ chmod 700 "${secrets_dir}"
2727
# Create certificate to test X509 auth with Atlas:
2828
atlas_x509_path="${secrets_dir:?}/atlas_x509.pem"
2929
echo "${atlas_x509_cert_base64:?}" | base64 --decode > "${secrets_dir:?}/atlas_x509.pem"
30-
# On Windows, convert certificate to PKCS#1 to work around CDRIVER-4269:
30+
# Fix path on Windows:
3131
if $IS_WINDOWS; then
32-
openssl pkey -in "${secrets_dir:?}/atlas_x509.pem" -traditional > "${secrets_dir:?}/atlas_x509_pkcs1.pem"
33-
openssl x509 -in "${secrets_dir:?}/atlas_x509.pem" >> "${secrets_dir:?}/atlas_x509_pkcs1.pem"
34-
atlas_x509_path="$(cygpath -m "${secrets_dir:?}/atlas_x509_pkcs1.pem")"
32+
atlas_x509_path="$(cygpath -m "${secrets_dir:?}/atlas_x509.pem")"
3533
fi
3634

3735
# Create Kerberos config and keytab files.

src/libmongoc/src/mongoc/mongoc-secure-channel.c

Lines changed: 109 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,62 @@ read_file_and_null_terminate (const char *filename, size_t *out_len)
150150
}
151151

152152

153+
// `decode_object` decodes a cryptographic object from a blob.
154+
// Returns NULL on error.
155+
static LPBYTE
156+
decode_object (const char *structType,
157+
const LPBYTE data,
158+
DWORD data_len,
159+
DWORD *out_len,
160+
const char *descriptor,
161+
const char *filename)
162+
{
163+
BSON_ASSERT_PARAM (structType);
164+
BSON_ASSERT_PARAM (data);
165+
BSON_ASSERT_PARAM (structType);
166+
BSON_ASSERT_PARAM (out_len);
167+
BSON_ASSERT_PARAM (descriptor);
168+
BSON_ASSERT_PARAM (filename);
169+
// Get needed output length:
170+
if (!CryptDecodeObjectEx (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, /* dwCertEncodingType */
171+
structType, /* lpszStructType */
172+
data, /* pbEncoded */
173+
data_len, /* cbEncoded */
174+
0, /* dwFlags */
175+
NULL, /* pDecodePara */
176+
NULL, /* pvStructInfo */
177+
out_len /* pcbStructInfo */
178+
)) {
179+
char *msg = mongoc_winerr_to_string (GetLastError ());
180+
MONGOC_ERROR ("Failed to decode %s from '%s': %s", descriptor, filename, msg);
181+
bson_free (msg);
182+
return NULL;
183+
}
184+
185+
if (*out_len == 0) {
186+
return NULL;
187+
}
188+
LPBYTE out = (LPBYTE) bson_malloc (*out_len);
189+
190+
if (!CryptDecodeObjectEx (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, /* dwCertEncodingType */
191+
structType, /* lpszStructType */
192+
data, /* pbEncoded */
193+
data_len, /* cbEncoded */
194+
0, /* dwFlags */
195+
NULL, /* pDecodePara */
196+
out, /* pvStructInfo */
197+
out_len /* pcbStructInfo */
198+
)) {
199+
char *msg = mongoc_winerr_to_string (GetLastError ());
200+
MONGOC_ERROR ("Failed to decode %s from '%s': %s", descriptor, filename, msg);
201+
bson_free (msg);
202+
bson_free (out);
203+
return NULL;
204+
}
205+
206+
return out;
207+
}
208+
153209
PCCERT_CONTEXT
154210
mongoc_secure_channel_setup_certificate_from_file (const char *filename)
155211
{
@@ -162,9 +218,11 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
162218
LPBYTE encoded_cert = NULL;
163219
const char *pem_public;
164220
const char *pem_private;
165-
LPBYTE blob_private = NULL;
166221
PCCERT_CONTEXT cert = NULL;
222+
LPBYTE blob_private = NULL;
167223
DWORD blob_private_len = 0;
224+
LPBYTE blob_private_rsa = NULL;
225+
DWORD blob_private_rsa_len = 0;
168226
DWORD encoded_private_len = 0;
169227
LPBYTE encoded_private = NULL;
170228

@@ -186,16 +244,6 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
186244
goto fail;
187245
}
188246

189-
pem_private = strstr (pem, "-----BEGIN RSA PRIVATE KEY-----");
190-
if (!pem_private) {
191-
pem_private = strstr (pem, "-----BEGIN PRIVATE KEY-----");
192-
}
193-
194-
if (!pem_private) {
195-
MONGOC_ERROR ("Can't find private key in '%s'", filename);
196-
goto fail;
197-
}
198-
199247
encoded_cert = decode_pem_base64 (pem_public, &encoded_cert_len, "public key", filename);
200248
if (!encoded_cert) {
201249
goto fail;
@@ -209,43 +257,47 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
209257
goto fail;
210258
}
211259

212-
/* https://msdn.microsoft.com/en-us/library/windows/desktop/aa380285%28v=vs.85%29.aspx
213-
*/
214-
encoded_private = decode_pem_base64 (pem_private, &encoded_private_len, "private key", filename);
215-
if (!encoded_private) {
216-
goto fail;
217-
}
260+
if (NULL != (pem_private = strstr (pem, "-----BEGIN RSA PRIVATE KEY-----"))) {
261+
encoded_private = decode_pem_base64 (pem_private, &encoded_private_len, "private key", filename);
262+
if (!encoded_private) {
263+
goto fail;
264+
}
218265

219-
/* https://msdn.microsoft.com/en-us/library/windows/desktop/aa379912%28v=vs.85%29.aspx
220-
*/
221-
success = CryptDecodeObjectEx (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, /* dwCertEncodingType */
222-
PKCS_RSA_PRIVATE_KEY, /* lpszStructType */
223-
encoded_private, /* pbEncoded */
224-
encoded_private_len, /* cbEncoded */
225-
0, /* dwFlags */
226-
NULL, /* pDecodePara */
227-
NULL, /* pvStructInfo */
228-
&blob_private_len); /* pcbStructInfo */
229-
if (!success) {
230-
char *msg = mongoc_winerr_to_string (GetLastError ());
231-
MONGOC_ERROR ("Failed to parse private key. %s", msg);
232-
bson_free (msg);
233-
goto fail;
234-
}
266+
blob_private_rsa = decode_object (
267+
PKCS_RSA_PRIVATE_KEY, encoded_private, encoded_private_len, &blob_private_rsa_len, "private key", filename);
268+
if (!blob_private_rsa) {
269+
goto fail;
270+
}
271+
} else if (NULL != (pem_private = strstr (pem, "-----BEGIN PRIVATE KEY-----"))) {
272+
encoded_private = decode_pem_base64 (pem_private, &encoded_private_len, "private key", filename);
273+
if (!encoded_private) {
274+
goto fail;
275+
}
235276

236-
blob_private = (LPBYTE) bson_malloc0 (blob_private_len);
237-
success = CryptDecodeObjectEx (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
238-
PKCS_RSA_PRIVATE_KEY,
239-
encoded_private,
240-
encoded_private_len,
241-
0,
242-
NULL,
243-
blob_private,
244-
&blob_private_len);
245-
if (!success) {
246-
char *msg = mongoc_winerr_to_string (GetLastError ());
247-
MONGOC_ERROR ("Failed to parse private key: %s", msg);
248-
bson_free (msg);
277+
blob_private = decode_object (
278+
PKCS_PRIVATE_KEY_INFO, encoded_private, encoded_private_len, &blob_private_len, "private key", filename);
279+
if (!blob_private) {
280+
goto fail;
281+
}
282+
283+
// Have PrivateKey. Get RSA key from it.
284+
CRYPT_PRIVATE_KEY_INFO *privateKeyInfo = (CRYPT_PRIVATE_KEY_INFO *) blob_private;
285+
if (strcmp (privateKeyInfo->Algorithm.pszObjId, szOID_RSA_RSA) != 0) {
286+
MONGOC_ERROR ("Non-RSA private keys are not supported");
287+
goto fail;
288+
}
289+
290+
blob_private_rsa = decode_object (PKCS_RSA_PRIVATE_KEY,
291+
privateKeyInfo->PrivateKey.pbData,
292+
privateKeyInfo->PrivateKey.cbData,
293+
&blob_private_rsa_len,
294+
"private key",
295+
filename);
296+
if (!blob_private_rsa) {
297+
goto fail;
298+
}
299+
} else {
300+
MONGOC_ERROR ("Can't find private key in '%s'", filename);
249301
goto fail;
250302
}
251303

@@ -266,12 +318,12 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
266318
HCRYPTKEY hKey;
267319
/* https://msdn.microsoft.com/en-us/library/windows/desktop/aa380207%28v=vs.85%29.aspx
268320
*/
269-
success = CryptImportKey (provider, /* hProv */
270-
blob_private, /* pbData */
271-
blob_private_len, /* dwDataLen */
272-
0, /* hPubKey */
273-
0, /* dwFlags */
274-
&hKey); /* phKey, OUT */
321+
success = CryptImportKey (provider, /* hProv */
322+
blob_private_rsa, /* pbData */
323+
blob_private_rsa_len, /* dwDataLen */
324+
0, /* hPubKey */
325+
0, /* dwFlags */
326+
&hKey); /* phKey, OUT */
275327
if (!success) {
276328
char *msg = mongoc_winerr_to_string (GetLastError ());
277329
MONGOC_ERROR ("CryptImportKey for private key failed: %s", msg);
@@ -309,6 +361,11 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
309361
bson_free (encoded_private);
310362
}
311363

364+
if (blob_private_rsa) {
365+
SecureZeroMemory (blob_private_rsa, blob_private_rsa_len);
366+
bson_free (blob_private_rsa);
367+
}
368+
312369
if (blob_private) {
313370
SecureZeroMemory (blob_private, blob_private_len);
314371
bson_free (blob_private);

src/libmongoc/tests/test-mongoc-x509.c

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,29 @@ test_x509_auth (void *unused)
143143
drop_x509_user (true /* ignore "not found" error */);
144144
create_x509_user ();
145145

146+
// Test auth works with PKCS8 key:
147+
{
148+
// Create URI:
149+
mongoc_uri_t *uri = get_x509_uri ();
150+
{
151+
ASSERT (mongoc_uri_set_option_as_utf8 (
152+
uri, MONGOC_URI_TLSCERTIFICATEKEYFILE, CERT_TEST_DIR "/client-pkcs8-unencrypted.pem"));
153+
ASSERT (mongoc_uri_set_option_as_utf8 (uri, MONGOC_URI_TLSCAFILE, CERT_CA));
154+
}
155+
156+
// Try auth:
157+
bson_error_t error = {0};
158+
bool ok;
159+
{
160+
mongoc_client_t *client = test_framework_client_new_from_uri (uri, NULL);
161+
ok = try_insert (client, &error);
162+
mongoc_client_destroy (client);
163+
}
164+
165+
ASSERT_OR_PRINT (ok, error);
166+
mongoc_uri_destroy (uri);
167+
}
168+
146169
// Test auth works:
147170
{
148171
// Create URI:
@@ -431,7 +454,13 @@ void
431454
test_x509_install (TestSuite *suite)
432455
{
433456
#ifdef MONGOC_ENABLE_SSL
434-
TestSuite_AddFull (suite, "/X509/auth", test_x509_auth, NULL, NULL, test_framework_skip_if_no_auth);
457+
TestSuite_AddFull (suite,
458+
"/X509/auth",
459+
test_x509_auth,
460+
NULL,
461+
NULL,
462+
test_framework_skip_if_no_auth,
463+
test_framework_skip_if_no_server_ssl);
435464
TestSuite_AddFull (suite, "/X509/crl", test_crl, NULL, NULL, test_framework_skip_if_no_server_ssl);
436465
#endif
437466

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCw1LxQS6Kfv8rb
3+
2Nd8g4ukiiHWMTJZWrGKJ7Y6fHu4kkpygqiSEDdd5SdU3STpfkzBhZw3nbFyezst
4+
i3K3TqdGlMbb96ahXjjD24pO4ey9RuI7BJWb9OgpBDgVQVmiqitcf5mYm4ikk1+h
5+
b7cKQTW8QMIh5wiroE2DnJKw+29K5IkcaOmZzv/llQO8UTZonxBslTdazQdeIRvr
6+
KaLTjwuXMQvZUpffDqaqqH7t1xkYDLupiTbkVn2S0q8J5Bq7uZPJTWGw+soWIq0w
7+
3xDZkAi1fXdqzDn8h5JOrYuAsUeVuPkqmrgaYt00KyHMflfsTDchgzwQeaxfx2YP
8+
qaG/u+lXAgMBAAECggEAE52lviqFwb7e3ABz9wYIqZoBIueWND9RTogVOOuyNclU
9+
pNtFo95Upf/TmyBlBdnS3ezaZXkCxDZTXDwJ37fD6pp6bNBOFbyEA4YJE7MQNfb1
10+
BaL+jaxfTJ2BMypqrRa/dKVa/ojSYZ9PjnL8FREiyt1MK8KbPZ6suprV0vgxYldE
11+
WNP1ROzm3CV5wg4zYgcqgYZqAWHKZEriQN7BNEL1+l+gCDJ+6zekI7/CL8XzIoY8
12+
MTBj7OCSGslD6RJ6SLugp0YPUfnjS8HyBG0QiqywTRjqsRh/3tayxCXEmC+gkENJ
13+
+06KNLqbC+hqwNTB8WrLI+KKtrBdZaEdeRwOMiL9cQKBgQDnF+OKXD1jIzZQZA0R
14+
Js026c9yPuANls3fjvEnCZiaL0BR08u33E3ddoyqpHbaTUNQ7EL/mEfS6JeKvuYN
15+
+qYL5dL+Jz9U6xrcINZ6OQSfIYuLBIdjnTbAP/FaAfBSIZUAb0djri+cMabovcoZ
16+
cDairFqcvhXT7T4e3j2lnXqLywKBgQDD47EIca2mTOMI//PzGv7VVNJ2lz/gI/Yc
17+
42diilyfjwp9Cd/s1qV7r3/WRmnKkmW9BIZ54DeMwaKA0kSRvz+RpWhEsKKxdzAt
18+
zRch8JN6IroVLQprfQP/zhCbxSwfFF2kaQyRghMBravri2BEvxqzj+6R5cEoT6g6
19+
Ffjct4R/JQKBgGR/shfY12WGybcaW8hqvHI2Kl4/08Z1H+EqU9urQ69B/1HWrtCt
20+
wTsftDr9vSZg1Xaa2OQ5AHtLZaQUMw4/Q/kGoMSgot02RX8X1M5gf48I0pvJg2uh
21+
0k63QCnpj+7X5enDeyNxfAkMWs76wqpfb5M9K0bhL7LziMF52wtsOgATAoGBAJPP
22+
v8oLXWjbI4Wq/T035Yq8EoOB5aUP/aoWvfBRT0rm+JcZWGqyHPSTnPbm8vT0OujB
23+
/WcBlWkUw7ZI84y2rxULpv4N+vXGZghpdUca7W1/vsIz1mT9VIM1zrp7satSBscE
24+
rYMuj4D4t31pEh9NxKwxs2dL4tC0KtCJu9twbv4xAoGBAJjXWgxPZ1R6ShkTJbie
25+
jtbkPviVHzO0yOn99ZZMOj8BkY8FjpxmhkUggKvNi0NP/kHMHQmHV/g71lCSbn46
26+
+UVuahmhJszMcvegkIE5jkrT+R0ZcvjpZaEhElJhqQ1TpUhLtKMOkqCb0Pxt59Cp
27+
l6hKCBKMghQsiIC1iBLzS+YY
28+
-----END PRIVATE KEY-----
29+
-----BEGIN CERTIFICATE-----
30+
MIIDgzCCAmugAwIBAgIDAxOUMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy
31+
aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u
32+
Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx
33+
CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIzNTU1NFoXDTM5MDUyMjIzNTU1NFowaTEP
34+
MA0GA1UEAxMGY2xpZW50MRAwDgYDVQQLEwdEcml2ZXJzMQwwCgYDVQQKEwNNREIx
35+
FjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYD
36+
VQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALDUvFBLop+/
37+
ytvY13yDi6SKIdYxMllasYontjp8e7iSSnKCqJIQN13lJ1TdJOl+TMGFnDedsXJ7
38+
Oy2LcrdOp0aUxtv3pqFeOMPbik7h7L1G4jsElZv06CkEOBVBWaKqK1x/mZibiKST
39+
X6FvtwpBNbxAwiHnCKugTYOckrD7b0rkiRxo6ZnO/+WVA7xRNmifEGyVN1rNB14h
40+
G+spotOPC5cxC9lSl98Opqqofu3XGRgMu6mJNuRWfZLSrwnkGru5k8lNYbD6yhYi
41+
rTDfENmQCLV9d2rMOfyHkk6ti4CxR5W4+SqauBpi3TQrIcx+V+xMNyGDPBB5rF/H
42+
Zg+pob+76VcCAwEAAaMkMCIwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUF
43+
BwMCMA0GCSqGSIb3DQEBCwUAA4IBAQAqRcLAGvYMaGYOV4HJTzNotT2qE0I9THNQ
44+
wOV1fBg69x6SrUQTQLjJEptpOA288Wue6Jt3H+p5qAGV5GbXjzN/yjCoItggSKxG
45+
Xg7279nz6/C5faoIKRjpS9R+MsJGlttP9nUzdSxrHvvqm62OuSVFjjETxD39DupE
46+
YPFQoHOxdFTtBQlc/zIKxVdd20rs1xJeeU2/L7jtRBSPuR/Sk8zot7G2/dQHX49y
47+
kHrq8qz12kj1T6XDXf8KZawFywXaz0/Ur+fUYKmkVk1T0JZaNtF4sKqDeNE4zcns
48+
p3xLVDSl1Q5Gwj7bgph9o4Hxs9izPwiqjmNaSjPimGYZ399zcurY
49+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)