-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Revisit the OpenSSL interop exception model #55973
Comments
Tagging subscribers to this area: @bartonjs, @vcsjones, @krwq, @GrabYourPitchforks Issue DetailsFor various reasons, the model we ended up with for OpenSSL is that when we decide it's time to throw an exception we drain out the error queue and throw for whatever the last error was. Largely this worked because each operation (probably) only really reports one error. Any time the error queue had more than one thing in it was because of spillover from a recoverable failure. e.g.:
So we succeeded, and left 4 errors in the queue (unless we cleared them, but I doubt it). Later, when we run into some other error (e.g. signing with a public key) we go "oh, exception throwing time... drain down to the last error and throw on that, since it's what we just encountered". Assuming that the operation only assigned one error code, that's true. The "only one error" didn't quite hold up in an unfortunate way in #55787. Calling Suggested changes:
|
Sounds reasonable, thanks. |
Sadly, this doesn't appear to be a clear improvement. For diagnostics, I changed OpenSslCryptographicException to directly extend Exception. This made tests angry, so I captured their output and diffed it from before and after the model change. Most of our existing tests weren't affected, indicating that the last error and the first error are the same (either a single error or an error code sandwich), e.g.
All other "match" cases are hereby handwaved away, the ones that are different are more interesting Tests where the exception message/code changed.ecdhPrivate.DeriveKeyFromHash(otherPublic, ...), curve "mismatch"System.Security.Cryptography.EcDiffieHellman.Tests.ECDiffieHellmanTests.ValidateNistP384_0
- Interop+Crypto+OpenSslCryptographicException : error:0609B099:digital envelope routines:EVP_PKEY_derive_set_peer:different parameters
+ Interop+Crypto+OpenSslCryptographicException : error:10071065:elliptic curve routines:EC_POINT_cmp:incompatible objects
System.Security.Cryptography.EcDiffieHellman.Tests.ECDiffieHellmanTests.ValidateNistP384_1
- Interop+Crypto+OpenSslCryptographicException : error:0609B099:digital envelope routines:EVP_PKEY_derive_set_peer:different parameters
+ Interop+Crypto+OpenSslCryptographicException : error:10071065:elliptic curve routines:EC_POINT_cmp:incompatible objects The error queue:
in this case, the latter error ("different parameters") is probably the more friendly error. rsaPrivate.Decrypt(..., RSASignaturePadding.Pkcs1), wrong key / corrupted dataSystem.Security.Cryptography.Rsa.Tests.EncryptDecrypt_Span.Decrypt_WrongKey_Pkcs7
- Interop+Crypto+OpenSslCryptographicException : error:04065072:rsa routines:rsa_ossl_private_decrypt:padding check failed
+ Interop+Crypto+OpenSslCryptographicException : error:0407109F:rsa routines:RSA_padding_check_PKCS1_type_2:pkcs decoding error
System.Security.Cryptography.Rsa.Tests.RSAKeyExchangeFormatterTests.VerifyDecryptKeyExchangePkcs1
- Interop+Crypto+OpenSslCryptographicException : error:04065072:rsa routines:rsa_ossl_private_decrypt:padding check failed
+ Interop+Crypto+OpenSslCryptographicException : error:0407109F:rsa routines:RSA_padding_check_PKCS1_type_2:pkcs decoding error The error queue:
The latter error is... slightly... more friendly. rsa.ImportRSAPublicKey(badBytes)System.Security.Cryptography.Rsa.Tests.RSAKeyFileTests.NoFuzzyRSAPrivateKey
- Interop+Crypto+OpenSslCryptographicException : error:0606F091:digital envelope routines:EVP_PKCS82PKEY:private key decode error
+ Interop+Crypto+OpenSslCryptographicException : error:0D0650DF:asn1 encoding routines:c2i_uint64_int:too large
System.Security.Cryptography.Rsa.Tests.RSAKeyFileTests.ReadWriteBigExponentPrivatePkcs1
- Interop+Crypto+OpenSslCryptographicException : error:0606F091:digital envelope routines:EVP_PKCS82PKEY:private key decode error
+ Interop+Crypto+OpenSslCryptographicException : error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag
System.Security.Cryptography.Rsa.Tests.RSAKeyFileTests.ReadWriteDiminishedDPPrivatePkcs1
- Interop+Crypto+OpenSslCryptographicException : error:0606F091:digital envelope routines:EVP_PKCS82PKEY:private key decode error
+ Interop+Crypto+OpenSslCryptographicException : error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag The error queue:
Honestly, all of the errors here are less than ideal. Basically, our implementation for this is "take the goo they give us, validate it's DER, and if it is, just wrap it in a PKCS8 header assuming it's an RSAPrivateKey... then call ImportPkcs8PrivateKey". The outermost error "private key decode error" is the most useful, even though it says it came from PKCS8 ('cuz, well, it did). We /could/ validate the ASN structure before doing the data wrapping and improve this exception in managed code. For rsa.ImportRSAPublicKey(badBytes)System.Security.Cryptography.Rsa.Tests.RSAKeyFileTests.NoFuzzyRSAPublicKey
- Interop+Crypto+OpenSslCryptographicException : error:0B09407D:x509 certificate routines:x509_pubkey_decode:public key decode error
+ Interop+Crypto+OpenSslCryptographicException : error:0D078094:asn1 encoding routines:asn1_item_embed_d2i:sequence length mismatch
System.Security.Cryptography.Rsa.Tests.RSAKeyFileTests.ReadWritePublicPkcs1
- Interop+Crypto+OpenSslCryptographicException : error:0B09407D:x509 certificate routines:x509_pubkey_decode:public key decode error
+ Interop+Crypto+OpenSslCryptographicException : error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag The error queue:
Similar to the one for RSAPrivateKey, the last one is not great, but probably the best of the three, and for similar reasons. rsa.ImportParameters(badParameters)System.Security.Cryptography.Rsa.Tests.RSAXml.FromNonsenseXml
- Interop+Crypto+OpenSslCryptographicException : error:040A007E:rsa routines:RSA_check_key_ex:iqmp not inverse of q
+ Interop+Crypto+OpenSslCryptographicException : error:040A0080:rsa routines:RSA_check_key_ex:p not prime The error queue
These errors are all good, actually (aside maybe from 040A07B, since n/e/d are consistent, it's only p that's out of place... but it probably used "calculated N" instead of "the given N") This particular test uses some valid parameters, but then copies D over top P, which produces some gibberish results. Potential paths forward(DSA and ECDSA would likely change more once they move to EVP_PKEY operations)
|
Isn't that more or less what we already did for the RSA case in #63804?
I think there is still merit in this, maybe each shim function should
We can bundle all that up in to a macro or something: #define error_check_preflight() \
do { assert(ERR_peek_error() == 0); ERR_clear_error(); } while(0) |
Hm. Interestingly, I think that's the same function that failed (in the same way) for DSA. Wonder what changed that it had been passing for me with RSA... and then the behaviors unified.
Asserting seems a bit harsh, especially since we accept/expose SafeEvpPKeyHandle... debug builds could randomly shut down because of some other interop conditions.
That's what I've wired up in https://github.com/dotnet/runtime/compare/main...bartonjs:ossl_exception_model?expand=1. Since we're back to using the last error it's mostly unnecessary, but it signals intent and probably makes some debugging easier. I'll probably open that as a PR tomorrow if I don't get an overnight feeling of it being a bad idea. |
Your description in #55787 (comment) does sound very reminiscent of what was happening with RSA. I would not be surprised if the issue is "fixed" for the DSA portion now.
Fair. |
For various reasons, the model we ended up with for OpenSSL is that when we decide it's time to throw an exception we drain out the error queue and throw for whatever the last error was. Largely this worked because each operation (probably) only really reports one error. Any time the error queue had more than one thing in it was because of spillover from a recoverable failure.
e.g.:
new X509Certificate2(bytes)
So we succeeded, and left 4 errors in the queue (unless we cleared them, but I doubt it).
Later, when we run into some other error (e.g. signing with a public key) we go "oh, exception throwing time... drain down to the last error and throw on that, since it's what we just encountered". Assuming that the operation only assigned one error code, that's true.
The "only one error" didn't quite hold up in an unfortunate way in #55787. Calling
EVP_PKEY2PKCS8
with a public-only DSA key on OpenSSL 3 Beta 1 produced two errors: 1)ERR_LIB_PROV | ERR_R_NOT_A_PRIVATE_KEY
, and 2)ERR_LIB_PROV | ERR_R_MALLOC_FAILURE
. While the second error seems pretty spurious for the situation, it shows that we really wanted "the first error that happened during our operation".Suggested changes:
The text was updated successfully, but these errors were encountered: