Skip to content

Commit 78922a5

Browse files
CDRIVER-4269 support RSA PKCS#8 keys with Secure Channel (#2024)
Co-authored-by: Ezra Chung <[email protected]>
1 parent 190fe9f commit 78922a5

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
@@ -149,6 +149,62 @@ read_file_and_null_terminate (const char *filename, size_t *out_len)
149149
}
150150

151151

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

@@ -185,16 +243,6 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
185243
goto fail;
186244
}
187245

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

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

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

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

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

363+
if (blob_private_rsa) {
364+
SecureZeroMemory (blob_private_rsa, blob_private_rsa_len);
365+
bson_free (blob_private_rsa);
366+
}
367+
311368
if (blob_private) {
312369
SecureZeroMemory (blob_private, blob_private_len);
313370
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)