Skip to content

CDRIVER-4269 support PKCS#8 keys with Secure Channel #2024

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

Merged
merged 11 commits into from
Jun 4, 2025
6 changes: 2 additions & 4 deletions .evergreen/scripts/run-auth-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,9 @@ chmod 700 "${secrets_dir}"
# Create certificate to test X509 auth with Atlas:
atlas_x509_path="${secrets_dir:?}/atlas_x509.pem"
echo "${atlas_x509_cert_base64:?}" | base64 --decode > "${secrets_dir:?}/atlas_x509.pem"
# On Windows, convert certificate to PKCS#1 to work around CDRIVER-4269:
# Fix path on Windows:
if $IS_WINDOWS; then
openssl pkey -in "${secrets_dir:?}/atlas_x509.pem" -traditional > "${secrets_dir:?}/atlas_x509_pkcs1.pem"
openssl x509 -in "${secrets_dir:?}/atlas_x509.pem" >> "${secrets_dir:?}/atlas_x509_pkcs1.pem"
atlas_x509_path="$(cygpath -m "${secrets_dir:?}/atlas_x509_pkcs1.pem")"
atlas_x509_path="$(cygpath -m "${secrets_dir:?}/atlas_x509.pem")"
fi

# Create Kerberos config and keytab files.
Expand Down
155 changes: 102 additions & 53 deletions src/libmongoc/src/mongoc/mongoc-secure-channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,56 @@ read_file_and_null_terminate (const char *filename, size_t *out_len)
}


// `decode_object` decodes a cryptographic object from a blob.
// Returns NULL on error.
static LPBYTE
decode_object (const char *structType,
const LPBYTE data,
DWORD data_len,
DWORD *out_len,
const char *descriptor,
const char *filename)
{
// Get needed output length:
if (!CryptDecodeObjectEx (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, /* dwCertEncodingType */
structType, /* lpszStructType */
data, /* pbEncoded */
data_len, /* cbEncoded */
0, /* dwFlags */
NULL, /* pDecodePara */
NULL, /* pvStructInfo */
out_len /* pcbStructInfo */
)) {
char *msg = mongoc_winerr_to_string (GetLastError ());
MONGOC_ERROR ("Failed to decode %s from '%s': %s", descriptor, filename, msg);
bson_free (msg);
return NULL;
}

if (*out_len == 0) {
return NULL;
}
LPBYTE out = (LPBYTE) bson_malloc (*out_len);

if (!CryptDecodeObjectEx (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, /* dwCertEncodingType */
structType, /* lpszStructType */
data, /* pbEncoded */
data_len, /* cbEncoded */
0, /* dwFlags */
NULL, /* pDecodePara */
out, /* pvStructInfo */
out_len /* pcbStructInfo */
)) {
char *msg = mongoc_winerr_to_string (GetLastError ());
MONGOC_ERROR ("Failed to decode %s from '%s': %s", descriptor, filename, msg);
bson_free (msg);
bson_free (out);
return NULL;
}

return out;
}

PCCERT_CONTEXT
mongoc_secure_channel_setup_certificate_from_file (const char *filename)
{
Expand All @@ -161,9 +211,9 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
LPBYTE encoded_cert = NULL;
const char *pem_public;
const char *pem_private;
LPBYTE blob_private = NULL;
LPBYTE blob_private_rsa = NULL, blob_private = NULL;
PCCERT_CONTEXT cert = NULL;
DWORD blob_private_len = 0;
DWORD blob_private_rsa_len = 0, blob_private_len = 0;
DWORD encoded_private_len = 0;
LPBYTE encoded_private = NULL;

Expand All @@ -185,16 +235,6 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
goto fail;
}

pem_private = strstr (pem, "-----BEGIN RSA PRIVATE KEY-----");
if (!pem_private) {
pem_private = strstr (pem, "-----BEGIN PRIVATE KEY-----");
}

if (!pem_private) {
MONGOC_ERROR ("Can't find private key in '%s'", filename);
goto fail;
}

encoded_cert = decode_pem_base64 (pem_public, &encoded_cert_len, "public key", filename);
if (!encoded_cert) {
goto fail;
Expand All @@ -208,43 +248,47 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
goto fail;
}

/* https://msdn.microsoft.com/en-us/library/windows/desktop/aa380285%28v=vs.85%29.aspx
*/
encoded_private = decode_pem_base64 (pem_private, &encoded_private_len, "private key", filename);
if (!encoded_private) {
goto fail;
}
if (NULL != (pem_private = strstr (pem, "-----BEGIN RSA PRIVATE KEY-----"))) {
encoded_private = decode_pem_base64 (pem_private, &encoded_private_len, "private key", filename);
if (!encoded_private) {
goto fail;
}

/* https://msdn.microsoft.com/en-us/library/windows/desktop/aa379912%28v=vs.85%29.aspx
*/
success = CryptDecodeObjectEx (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, /* dwCertEncodingType */
PKCS_RSA_PRIVATE_KEY, /* lpszStructType */
encoded_private, /* pbEncoded */
encoded_private_len, /* cbEncoded */
0, /* dwFlags */
NULL, /* pDecodePara */
NULL, /* pvStructInfo */
&blob_private_len); /* pcbStructInfo */
if (!success) {
char *msg = mongoc_winerr_to_string (GetLastError ());
MONGOC_ERROR ("Failed to parse private key. %s", msg);
bson_free (msg);
goto fail;
}
blob_private_rsa = decode_object (
PKCS_RSA_PRIVATE_KEY, encoded_private, encoded_private_len, &blob_private_rsa_len, "private key", filename);
if (!blob_private_rsa) {
goto fail;
}
} else if (NULL != (pem_private = strstr (pem, "-----BEGIN PRIVATE KEY-----"))) {
encoded_private = decode_pem_base64 (pem_private, &encoded_private_len, "private key", filename);
if (!encoded_private) {
goto fail;
}

blob_private = (LPBYTE) bson_malloc0 (blob_private_len);
success = CryptDecodeObjectEx (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
PKCS_RSA_PRIVATE_KEY,
encoded_private,
encoded_private_len,
0,
NULL,
blob_private,
&blob_private_len);
if (!success) {
char *msg = mongoc_winerr_to_string (GetLastError ());
MONGOC_ERROR ("Failed to parse private key: %s", msg);
bson_free (msg);
blob_private = decode_object (
PKCS_PRIVATE_KEY_INFO, encoded_private, encoded_private_len, &blob_private_rsa_len, "private key", filename);
if (!blob_private) {
goto fail;
}

// Have PrivateKey. Get RSA key from it.
CRYPT_PRIVATE_KEY_INFO *privateKeyInfo = (CRYPT_PRIVATE_KEY_INFO *) blob_private;
if (strcmp (privateKeyInfo->Algorithm.pszObjId, szOID_RSA_RSA) != 0) {
MONGOC_ERROR ("Non-RSA private keys are not supported");
goto fail;
}

blob_private_rsa = decode_object (PKCS_RSA_PRIVATE_KEY,
privateKeyInfo->PrivateKey.pbData,
privateKeyInfo->PrivateKey.cbData,
&blob_private_rsa_len,
"private key",
filename);
if (!blob_private_rsa) {
goto fail;
}
} else {
MONGOC_ERROR ("Can't find private key in '%s'", filename);
goto fail;
}

Expand All @@ -265,12 +309,12 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
HCRYPTKEY hKey;
/* https://msdn.microsoft.com/en-us/library/windows/desktop/aa380207%28v=vs.85%29.aspx
*/
success = CryptImportKey (provider, /* hProv */
blob_private, /* pbData */
blob_private_len, /* dwDataLen */
0, /* hPubKey */
0, /* dwFlags */
&hKey); /* phKey, OUT */
success = CryptImportKey (provider, /* hProv */
blob_private_rsa, /* pbData */
blob_private_rsa_len, /* dwDataLen */
0, /* hPubKey */
0, /* dwFlags */
&hKey); /* phKey, OUT */
if (!success) {
char *msg = mongoc_winerr_to_string (GetLastError ());
MONGOC_ERROR ("CryptImportKey for private key failed: %s", msg);
Expand Down Expand Up @@ -308,6 +352,11 @@ mongoc_secure_channel_setup_certificate_from_file (const char *filename)
bson_free (encoded_private);
}

if (blob_private_rsa) {
SecureZeroMemory (blob_private_rsa, blob_private_rsa_len);
bson_free (blob_private_rsa);
}

if (blob_private) {
SecureZeroMemory (blob_private, blob_private_len);
bson_free (blob_private);
Expand Down
23 changes: 23 additions & 0 deletions src/libmongoc/tests/test-mongoc-x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,29 @@ test_x509_auth (void *unused)
drop_x509_user (true /* ignore "not found" error */);
create_x509_user ();

// Test auth works with PKCS8 key:
{
// Create URI:
mongoc_uri_t *uri = get_x509_uri ();
{
ASSERT (mongoc_uri_set_option_as_utf8 (
uri, MONGOC_URI_TLSCERTIFICATEKEYFILE, CERT_TEST_DIR "/client-pkcs8-unencrypted.pem"));
ASSERT (mongoc_uri_set_option_as_utf8 (uri, MONGOC_URI_TLSCAFILE, CERT_CA));
}

// Try auth:
bson_error_t error = {0};
bool ok;
{
mongoc_client_t *client = test_framework_client_new_from_uri (uri, NULL);
ok = try_insert (client, &error);
mongoc_client_destroy (client);
}

ASSERT_OR_PRINT (ok, error);
mongoc_uri_destroy (uri);
}

// Test auth works:
{
// Create URI:
Expand Down
49 changes: 49 additions & 0 deletions src/libmongoc/tests/x509gen/client-pkcs8-unencrypted.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCw1LxQS6Kfv8rb
2Nd8g4ukiiHWMTJZWrGKJ7Y6fHu4kkpygqiSEDdd5SdU3STpfkzBhZw3nbFyezst
i3K3TqdGlMbb96ahXjjD24pO4ey9RuI7BJWb9OgpBDgVQVmiqitcf5mYm4ikk1+h
b7cKQTW8QMIh5wiroE2DnJKw+29K5IkcaOmZzv/llQO8UTZonxBslTdazQdeIRvr
KaLTjwuXMQvZUpffDqaqqH7t1xkYDLupiTbkVn2S0q8J5Bq7uZPJTWGw+soWIq0w
3xDZkAi1fXdqzDn8h5JOrYuAsUeVuPkqmrgaYt00KyHMflfsTDchgzwQeaxfx2YP
qaG/u+lXAgMBAAECggEAE52lviqFwb7e3ABz9wYIqZoBIueWND9RTogVOOuyNclU
pNtFo95Upf/TmyBlBdnS3ezaZXkCxDZTXDwJ37fD6pp6bNBOFbyEA4YJE7MQNfb1
BaL+jaxfTJ2BMypqrRa/dKVa/ojSYZ9PjnL8FREiyt1MK8KbPZ6suprV0vgxYldE
WNP1ROzm3CV5wg4zYgcqgYZqAWHKZEriQN7BNEL1+l+gCDJ+6zekI7/CL8XzIoY8
MTBj7OCSGslD6RJ6SLugp0YPUfnjS8HyBG0QiqywTRjqsRh/3tayxCXEmC+gkENJ
+06KNLqbC+hqwNTB8WrLI+KKtrBdZaEdeRwOMiL9cQKBgQDnF+OKXD1jIzZQZA0R
Js026c9yPuANls3fjvEnCZiaL0BR08u33E3ddoyqpHbaTUNQ7EL/mEfS6JeKvuYN
+qYL5dL+Jz9U6xrcINZ6OQSfIYuLBIdjnTbAP/FaAfBSIZUAb0djri+cMabovcoZ
cDairFqcvhXT7T4e3j2lnXqLywKBgQDD47EIca2mTOMI//PzGv7VVNJ2lz/gI/Yc
42diilyfjwp9Cd/s1qV7r3/WRmnKkmW9BIZ54DeMwaKA0kSRvz+RpWhEsKKxdzAt
zRch8JN6IroVLQprfQP/zhCbxSwfFF2kaQyRghMBravri2BEvxqzj+6R5cEoT6g6
Ffjct4R/JQKBgGR/shfY12WGybcaW8hqvHI2Kl4/08Z1H+EqU9urQ69B/1HWrtCt
wTsftDr9vSZg1Xaa2OQ5AHtLZaQUMw4/Q/kGoMSgot02RX8X1M5gf48I0pvJg2uh
0k63QCnpj+7X5enDeyNxfAkMWs76wqpfb5M9K0bhL7LziMF52wtsOgATAoGBAJPP
v8oLXWjbI4Wq/T035Yq8EoOB5aUP/aoWvfBRT0rm+JcZWGqyHPSTnPbm8vT0OujB
/WcBlWkUw7ZI84y2rxULpv4N+vXGZghpdUca7W1/vsIz1mT9VIM1zrp7satSBscE
rYMuj4D4t31pEh9NxKwxs2dL4tC0KtCJu9twbv4xAoGBAJjXWgxPZ1R6ShkTJbie
jtbkPviVHzO0yOn99ZZMOj8BkY8FjpxmhkUggKvNi0NP/kHMHQmHV/g71lCSbn46
+UVuahmhJszMcvegkIE5jkrT+R0ZcvjpZaEhElJhqQ1TpUhLtKMOkqCb0Pxt59Cp
l6hKCBKMghQsiIC1iBLzS+YY
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDgzCCAmugAwIBAgIDAxOUMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy
aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u
Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx
CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIzNTU1NFoXDTM5MDUyMjIzNTU1NFowaTEP
MA0GA1UEAxMGY2xpZW50MRAwDgYDVQQLEwdEcml2ZXJzMQwwCgYDVQQKEwNNREIx
FjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYD
VQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALDUvFBLop+/
ytvY13yDi6SKIdYxMllasYontjp8e7iSSnKCqJIQN13lJ1TdJOl+TMGFnDedsXJ7
Oy2LcrdOp0aUxtv3pqFeOMPbik7h7L1G4jsElZv06CkEOBVBWaKqK1x/mZibiKST
X6FvtwpBNbxAwiHnCKugTYOckrD7b0rkiRxo6ZnO/+WVA7xRNmifEGyVN1rNB14h
G+spotOPC5cxC9lSl98Opqqofu3XGRgMu6mJNuRWfZLSrwnkGru5k8lNYbD6yhYi
rTDfENmQCLV9d2rMOfyHkk6ti4CxR5W4+SqauBpi3TQrIcx+V+xMNyGDPBB5rF/H
Zg+pob+76VcCAwEAAaMkMCIwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUF
BwMCMA0GCSqGSIb3DQEBCwUAA4IBAQAqRcLAGvYMaGYOV4HJTzNotT2qE0I9THNQ
wOV1fBg69x6SrUQTQLjJEptpOA288Wue6Jt3H+p5qAGV5GbXjzN/yjCoItggSKxG
Xg7279nz6/C5faoIKRjpS9R+MsJGlttP9nUzdSxrHvvqm62OuSVFjjETxD39DupE
YPFQoHOxdFTtBQlc/zIKxVdd20rs1xJeeU2/L7jtRBSPuR/Sk8zot7G2/dQHX49y
kHrq8qz12kj1T6XDXf8KZawFywXaz0/Ur+fUYKmkVk1T0JZaNtF4sKqDeNE4zcns
p3xLVDSl1Q5Gwj7bgph9o4Hxs9izPwiqjmNaSjPimGYZ399zcurY
-----END CERTIFICATE-----