Skip to content
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

Fix parsing EC private keys with EC parameters #535

Closed

Conversation

dbussink
Copy link

The logic used with OpenSSL 1.1.1 allows for parsing private keys with EC parameters in the PEM format. The OpenSSL 3.0 logic doesn't allow for this and breaks loading these types of private keys.

The parsing loop here is changed to continue parsing PEM blocks also if it succeeds to parse a PEM block, like for EC parameters. We want to ensure that all valid PEM blocks are loaded, not only the first one.

Combined with it also trying to parse non valid PEM bits, it means we always want to continue parsing until we hit EOF on the BIO, or if we no longer see progress. These two checks were already implemented, so all that's needed is to unconditionally try to grab all PEM blocks.

The logic used with OpenSSL 1.1.1 allows for parsing private keys with
EC parameters in the PEM format. The OpenSSL 3.0 logic doesn't allow for
this and breaks loading these types of private keys.

The parsing loop here is changed to continue parsing PEM blocks also if
it succeeds to parse a PEM block, like for EC parameters. We want to
ensure that all valid PEM blocks are loaded, not only the first one.

Combined with it also trying to parse non valid PEM bits, it means we
always want to continue parsing until we hit EOF on the BIO, or if we no
longer see progress. These two checks were already implemented, so all
that's needed is to unconditionally try to grab all PEM blocks.
@dbussink dbussink mentioned this pull request Aug 23, 2022
@dbussink
Copy link
Author

I realized there's also another bug. If the data is only the EC parameters, we don't trigger a parser error since the private key will be a partial filed structure with the type setup correctly, but no public nor private key data is then present. So it returns an unusable OpenSSL::PKey object.

I think ossl_pkey_read_generic also needs to do some basic validations in the OpenSSL 3.0 path to check if the key is a valid key then.

It's possible that an EC key is parsed with just parameters, but it's
not usable at that point as a valid public or private key. So we error
in that case for now.
@dbussink
Copy link
Author

I think ossl_pkey_read_generic also needs to do some basic validations in the OpenSSL 3.0 path to check if the key is a valid key then.

I've looked more at this and it only seems possible with EC keys, so I added a validation check. But not sure if that's desired, so also fine to drop that if that's better then.

@dbussink
Copy link
Author

Looks like #527 is somewhat related, as that would provide easier ways of checking if it's a valid public or private key compared to what is done here.

@rhenium
Copy link
Member

rhenium commented Aug 31, 2022

Interesting. Is it a commonly used format? I didn't think of a case where the input contains two decodable PEM blocks into OpenSSL::PKey::PKey.

(Note that in the example input, EC PARAMETERS/ECParameters structure is actually redundant because the following EC PRIVATE KEY/ECPrivateKey structure repeats the same ECParameters structure in it. [To be exact, the ECParameters portion within an ECPrivateKey is optional according to the original specification SECG SEC 1, but it is required by OpenSSL's API and later IETF RFCs.])

Technically speaking, the result of decoding EC PARAMETERS is a proper OpenSSL::PKey::EC object, equivalent of OpenSSL::PKey::EC.new("prime256v1") or OpenSSL::PKey::EC.new(OpenSSL::PKey::EC::Group.new("prime256v1")).

It is expected itself that ruby/openssl now decodes EC PARAMETERS PEM block. (cf. 867e5c0, DHParameters and ECParameters fall into the same category.)

@rhenium
Copy link
Member

rhenium commented Aug 31, 2022

The current change in #527 appears to revert the behavior, as it will scan input string repeatedly attempting to decode it as private key -> public key -> parameters, in order, but this is more of a side-effect. I had no choice due to the lack of openssl/openssl#9467 in OpenSSL 3.0.

@dbussink
Copy link
Author

Interesting. Is it a commonly used format? I didn't think of a case where the input contains two decodable PEM blocks into OpenSSL::PKey::PKey.

I think it's fairly standard, the simplest openssl invocation it does it by default:

$ openssl ecparam  -name prime256v1 -genkey
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMUoBGN1tPaCCRHL8wCQeaitV/p5rnMJqP7e0Gk7qVqPoAoGCCqGSM49
AwEHoUQDQgAEJxUvwDPujh79kRFlyOENwgYvV1N+/DFO8Ofxun4grp31MIWjwmv1
9JvKtO0+K4HYKjNgiCrF38005Ta7918ebA==
-----END EC PRIVATE KEY-----

I imagine people do regularly put that in a file then and expect it to work when it's being loaded. It also does work with the OpenSSL 1.1.1 implementation. Our problem is that we deal with user provided keys in this situation so handling different formats including a format with EC parameters makes things more robust.

Otherwise we'd have to filter out EC parameters manually before we pass it to Ruby OpenSSL for systems on OpenSSL 3.0.

@dbussink
Copy link
Author

The current change in #527 appears to revert the behavior, as it will scan input string repeatedly attempting to decode it as private key -> public key -> parameters, in order, but this is more of a side-effect. I had no choice due to the lack of openssl/openssl#9467 in OpenSSL 3.0.

Ah, if that also (somewhat accidentally) fixes the behavior, I'm all for that fix. Probably still good to add a test for this to avoid a future regression then?

@rhenium
Copy link
Member

rhenium commented Sep 1, 2022

I think it's fairly standard, the simplest openssl invocation it does it by default:

$ openssl ecparam  -name prime256v1 -genkey
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMUoBGN1tPaCCRHL8wCQeaitV/p5rnMJqP7e0Gk7qVqPoAoGCCqGSM49
AwEHoUQDQgAEJxUvwDPujh79kRFlyOENwgYvV1N+/DFO8Ofxun4grp31MIWjwmv1
9JvKtO0+K4HYKjNgiCrF38005Ta7918ebA==
-----END EC PRIVATE KEY-----

Hmm. I didn't know about this behavior. This seems odd to me and it is even inconsistent with the DSA counterpart openssl dsaparam, which won't print a DSA parameters PEM block if -genkey is specified... But I can see this command would be widely used compared to other command, openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:prime256v1.

I agree the format should work as it did in previous versions of ruby/openssl.

rhenium added a commit to rhenium/ruby-openssl that referenced this pull request Sep 2, 2022
Scan through the input for a private key, then fallback to generic
decoder.

OpenSSL 3.0's OSSL_DECODER supports encoded key parameters. The PEM
header "-----BEGIN EC PARAMETERS-----" is used by one of such encoding
formats. While this is useful for OpenSSL::PKey::PKey, an edge case has
been discovered.

The openssl CLI command line "openssl ecparam -genkey" prints two PEM
blocks in a row, one for EC parameters and another for the private key.
Feeding the whole output into OSSL_DECODER results in only the first PEM
block, the key parameters, being decoded. Previously, ruby/openssl did
not support decoding key parameters and it would decode the private key
PEM block instead.

While the new behavior is technically correct, "openssl ecparam -genkey"
is so widely used that ruby/openssl does not want to break existing
applications.

Fixes ruby#535
@rhenium
Copy link
Member

rhenium commented Sep 2, 2022

I created an alternative PR for this at #540. It doesn't actively reject "EC PARAMETERS" PEM but looks for the private key PEM block first, like OpenSSL <= 1.1 code (or #527 accidentally) did.

@dbussink
Copy link
Author

dbussink commented Sep 6, 2022

Closing in favor of #540

@dbussink dbussink closed this Sep 6, 2022
@dbussink dbussink deleted the fix-ec-parameters-private-key-openssl-3 branch September 6, 2022 08:18
matzbot pushed a commit to ruby/ruby that referenced this pull request Oct 17, 2022
…enkey" output

Scan through the input for a private key, then fallback to generic
decoder.

OpenSSL 3.0's OSSL_DECODER supports encoded key parameters. The PEM
header "-----BEGIN EC PARAMETERS-----" is used by one of such encoding
formats. While this is useful for OpenSSL::PKey::PKey, an edge case has
been discovered.

The openssl CLI command line "openssl ecparam -genkey" prints two PEM
blocks in a row, one for EC parameters and another for the private key.
Feeding the whole output into OSSL_DECODER results in only the first PEM
block, the key parameters, being decoded. Previously, ruby/openssl did
not support decoding key parameters and it would decode the private key
PEM block instead.

While the new behavior is technically correct, "openssl ecparam -genkey"
is so widely used that ruby/openssl does not want to break existing
applications.

Fixes ruby/openssl#535

ruby/openssl@d486c82833
tenderlove pushed a commit to Shopify/ruby that referenced this pull request Oct 27, 2022
…enkey" output

Scan through the input for a private key, then fallback to generic
decoder.

OpenSSL 3.0's OSSL_DECODER supports encoded key parameters. The PEM
header "-----BEGIN EC PARAMETERS-----" is used by one of such encoding
formats. While this is useful for OpenSSL::PKey::PKey, an edge case has
been discovered.

The openssl CLI command line "openssl ecparam -genkey" prints two PEM
blocks in a row, one for EC parameters and another for the private key.
Feeding the whole output into OSSL_DECODER results in only the first PEM
block, the key parameters, being decoded. Previously, ruby/openssl did
not support decoding key parameters and it would decode the private key
PEM block instead.

While the new behavior is technically correct, "openssl ecparam -genkey"
is so widely used that ruby/openssl does not want to break existing
applications.

Fixes ruby/openssl#535

ruby/openssl@d486c82833
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants