Skip to content

Commit 6489611

Browse files
authored
Fix OpenSSL 3 reporting an OutOfMemoryException for missing private key
1 parent 5a12420 commit 6489611

File tree

4 files changed

+66
-11
lines changed

4 files changed

+66
-11
lines changed

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EvpPkey.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,28 @@ internal static unsafe SafeEvpPKeyHandle DecodePkcs8PrivateKey(
105105
}
106106

107107
[GeneratedDllImport(Libraries.CryptoNative)]
108-
private static partial int CryptoNative_GetPkcs8PrivateKeySize(IntPtr pkey);
108+
private static partial int CryptoNative_GetPkcs8PrivateKeySize(IntPtr pkey, out int p8size);
109109

110110
private static int GetPkcs8PrivateKeySize(IntPtr pkey)
111111
{
112-
int ret = CryptoNative_GetPkcs8PrivateKeySize(pkey);
112+
const int Success = 1;
113+
const int Error = -1;
114+
const int MissingPrivateKey = -2;
113115

114-
if (ret < 0)
116+
int ret = CryptoNative_GetPkcs8PrivateKeySize(pkey, out int p8size);
117+
118+
switch (ret)
115119
{
116-
throw CreateOpenSslCryptographicException();
120+
case Success:
121+
return p8size;
122+
case Error:
123+
throw CreateOpenSslCryptographicException();
124+
case MissingPrivateKey:
125+
throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey);
126+
default:
127+
Debug.Fail($"Unexpected return '{ret}' value from {nameof(CryptoNative_GetPkcs8PrivateKeySize)}.");
128+
throw new CryptographicException();
117129
}
118-
119-
return ret;
120130
}
121131

122132
[GeneratedDllImport(Libraries.CryptoNative)]

src/native/libs/System.Security.Cryptography.Native/opensslshim.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void);
270270
LEGACY_FUNCTION(ERR_load_crypto_strings) \
271271
LIGHTUP_FUNCTION(ERR_new) \
272272
REQUIRED_FUNCTION(ERR_peek_error) \
273+
REQUIRED_FUNCTION(ERR_peek_error_line) \
273274
REQUIRED_FUNCTION(ERR_peek_last_error) \
274275
FALLBACK_FUNCTION(ERR_put_error) \
275276
REQUIRED_FUNCTION(ERR_reason_error_string) \
@@ -728,6 +729,7 @@ FOR_ALL_OPENSSL_FUNCTIONS
728729
#define ERR_load_crypto_strings ERR_load_crypto_strings_ptr
729730
#define ERR_new ERR_new_ptr
730731
#define ERR_peek_error ERR_peek_error_ptr
732+
#define ERR_peek_error_line ERR_peek_error_line_ptr
731733
#define ERR_peek_last_error ERR_peek_last_error_ptr
732734
#define ERR_put_error ERR_put_error_ptr
733735
#define ERR_reason_error_string ERR_reason_error_string_ptr

src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,20 +152,59 @@ EVP_PKEY* CryptoNative_DecodePkcs8PrivateKey(const uint8_t* buf, int32_t len, in
152152
return key;
153153
}
154154

155-
int32_t CryptoNative_GetPkcs8PrivateKeySize(EVP_PKEY* pkey)
155+
int32_t CryptoNative_GetPkcs8PrivateKeySize(EVP_PKEY* pkey, int32_t* p8size)
156156
{
157157
assert(pkey != NULL);
158+
assert(p8size != NULL);
159+
160+
*p8size = 0;
161+
ERR_clear_error();
158162

159163
PKCS8_PRIV_KEY_INFO* p8 = EVP_PKEY2PKCS8(pkey);
160164

161165
if (p8 == NULL)
162166
{
167+
// OpenSSL 1.1 and 3 have a behavioral change with EVP_PKEY2PKCS8
168+
// with regard to handling EVP_PKEYs that do not contain a private key.
169+
//
170+
// In OpenSSL 1.1, it would always succeed, but the private parameters
171+
// would be missing (thus making an invalid PKCS8 structure).
172+
// Over in the managed side, we detect these invalid PKCS8 blobs and
173+
// convert that to a "no private key" error.
174+
//
175+
// In OpenSSL 3, this now correctly errors, with the error
176+
// ASN1_R_ILLEGAL_ZERO_CONTENT. We want to preserve allocation failures
177+
// as OutOfMemoryException. So we peek at the error. If it's a malloc
178+
// failure, -1 is returned to indcate "throw what is on the error queue".
179+
// If the error is not a malloc failure, return -2 to mean "no private key".
180+
// If OpenSSL ever changes the error to something more to explicitly mean
181+
// "no private key" then we should test for that explicitly. Until then,
182+
// we treat all errors, except a malloc error, to mean "no private key".
183+
184+
const char* file = NULL;
185+
int line = 0;
186+
unsigned long error = ERR_peek_error_line(&file, &line);
187+
188+
// If it's not a malloc failure, assume it's because the private key is
189+
// missing.
190+
if (ERR_GET_REASON(error) != ERR_R_MALLOC_FAILURE)
191+
{
192+
ERR_clear_error();
193+
return -2;
194+
}
195+
196+
// It is a malloc failure. Clear the error queue and set the error
197+
// as a malloc error so it's the only error in the queue.
198+
ERR_clear_error();
199+
ERR_put_error(ERR_GET_LIB(error), 0, ERR_R_MALLOC_FAILURE, file, line);
200+
163201
return -1;
164202
}
165203

166-
int ret = i2d_PKCS8_PRIV_KEY_INFO(p8, NULL);
204+
*p8size = i2d_PKCS8_PRIV_KEY_INFO(p8, NULL);
167205
PKCS8_PRIV_KEY_INFO_free(p8);
168-
return ret;
206+
207+
return *p8size < 0 ? -1 : 1;
169208
}
170209

171210
int32_t CryptoNative_EncodePkcs8PrivateKey(EVP_PKEY* pkey, uint8_t* buf)

src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,13 @@ Requres a non-null buf, and len > 0.
5858
PALEXPORT EVP_PKEY* CryptoNative_DecodePkcs8PrivateKey(const uint8_t* buf, int32_t len, int32_t algId);
5959

6060
/*
61-
Reports the number of bytes rqeuired to encode an EVP_PKEY* as a Pkcs8PrivateKeyInfo, or a negative value on error.
61+
Gets the number of bytes rqeuired to encode an EVP_PKEY* as a Pkcs8PrivateKeyInfo.
62+
63+
On success, 1 is returned and p8size contains the size of the Pkcs8PrivateKeyInfo.
64+
On failure, -1 is used to indicate the openssl error queue contains the error.
65+
On failure, -2 is used to indcate that the supplied EVP_PKEY* is possibly missing a private key.
6266
*/
63-
PALEXPORT int32_t CryptoNative_GetPkcs8PrivateKeySize(EVP_PKEY* pkey);
67+
PALEXPORT int32_t CryptoNative_GetPkcs8PrivateKeySize(EVP_PKEY* pkey, int32_t* p8size);
6468

6569
/*
6670
Encodes the EVP_PKEY* as a Pkcs8PrivateKeyInfo, writing the encoded value to buf.

0 commit comments

Comments
 (0)