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

Honor OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT as set and default to UNCOMPRESSED #16624

Closed

Conversation

romen
Copy link
Member

@romen romen commented Sep 18, 2021

Originally the code to im/export the EC pubkey was meant to be consumed
only by the im/export functions when crossing the provider boundary.
Having our providers exporting to a COMPRESSED format octet string made
sense to avoid memory waste, as it wasn't exposed outside the provider
API, and providers had all tools available to convert across the three
formats.

Later on, with #13139 deprecating the EC_KEY_* functions, more state
was added among the params imported/exported on an EC provider-native
key (including OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT, although it
did not affect the format used to export OSSL_PKEY_PARAM_PUB_KEY).

Finally, in #14800, EVP_PKEY_todata() was introduced and prominently
exposed directly to users outside the provider API, and the choice of
COMPRESSED over UNCOMPRESSED as the default became less sensible in
light of usability, given the latter is more often needed by
applications and protocols.

This commit fixes it, by using EC_KEY_get_conv_form() to get the
point format from the internal state (an EC_KEY under the hood) of the
provider-side object, and using it on
EVP_PKEY_export()/EVP_PKEY_todata() to format
OSSL_PKEY_PARAM_PUB_KEY.
The default for an EC_KEY was already UNCOMPRESSED, and it is altered
if the user sets OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT via
EVP_PKEY_fromdata(), EVP_PKEY_set_params(), or one of the
more specialized methods.

For symmetry, this commit also alters ec_pkey_export_to() in
crypto/ec/ec_ameth.c, part of the EVP_PKEY_ASN1_METHOD for legacy EC
keys: it exclusively used COMPRESSED format, and now it honors the
conversion format specified in the EC_KEY object being exported to a
provider when this function is called.

Fixes #16595

Checklist
  • documentation is added or updated
  • tests are added or updated

@romen romen added triaged: bug The issue/pr is/fixes a bug hold: need otc decision The OTC needs to make a decision branch: 3.0 Merge to openssl-3.0 branch labels Sep 18, 2021
@romen romen self-assigned this Sep 18, 2021
@github-actions github-actions bot added the severity: fips change The pull request changes FIPS provider sources label Sep 18, 2021
@romen
Copy link
Member Author

romen commented Sep 18, 2021

The PR is based on the openssl-3.0 stable branch, but the commit can be cleanly cherry-picked to master.

@romen romen added the branch: master Merge to master branch label Sep 18, 2021
@romen
Copy link
Member Author

romen commented Sep 18, 2021

I applied the hold: needs OTC decision label as this is fixing a bug in the user facing API (EVP_PKEY_todata()), but can be seen as constituting a breaking change in the provider API.

On top of that, it is affecting the fips provider.

Copy link
Member

@levitte levitte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me.
And... nicely done, this was an easily digestable change.

@paulidale paulidale added the approval: done This pull request has the required number of approvals label Sep 20, 2021
Copy link
Contributor

@paulidale paulidale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

doc/man7/EVP_PKEY-EC.pod Show resolved Hide resolved
Copy link
Member

@slontis slontis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

ossl_ec_key_fromdata() uses EC_POINT_oct2point() so that should work in the other direction. I assume ossl_ec_key_fromdata() is not supposed to play with asn1_format?

@mattcaswell
Copy link
Member

Question: I had in mind that we defaulted conv_form to whatever format we read from the input file. Is that not the case? Or maybe it's only in the apps.

@t8m
Copy link
Member

t8m commented Sep 20, 2021

Question: I had in mind that we defaulted conv_form to whatever format we read from the input file. Is that not the case? Or maybe it's only in the apps.

I think you're mistaking the EC domain parameters encoding and the point conversion format. The domain parameters encoding should be kept by the ec/pkey apps in 3.0.0.

@mattcaswell
Copy link
Member

I think you're mistaking the EC domain parameters encoding and the point conversion format.

No. I'm definitely thinking of point conversion format.

@romen
Copy link
Member Author

romen commented Sep 20, 2021

I think you're mistaking the EC domain parameters encoding and the point conversion format.

No. I'm definitely thinking of point conversion format.

@mattcaswell for the app with 3.0.0 what you expect is what happens (and I seem to remember there was a test for this):

; openssl version
OpenSSL 3.0.0 7 sep 2021 (Library: OpenSSL 3.0.0 7 sep 2021)

; openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 | tee priv.pem 
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg7Lfgw6D2g54DXm0b
AZERlCrHrXvtzDrb/wqysHdnksihRANCAASt5oqPL/4Ky0raklVoshq7cpHvoSZr
+FDPoPzalV6IAsv1b1fKnwNg+1hkt81N9dtLnn6GLYLEYmK7xxIIqAJ1
-----END PRIVATE KEY-----
; openssl pkey -in priv.pem -pubout | tee pub.default.pem
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEreaKjy/+CstK2pJVaLIau3KR76Em
a/hQz6D82pVeiALL9W9Xyp8DYPtYZLfNTfXbS55+hi2CxGJiu8cSCKgCdQ==
-----END PUBLIC KEY-----

; openssl pkey -in priv.pem -pubout -ec_conv_form uncompressed | tee pub.uncompressed.pem
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEreaKjy/+CstK2pJVaLIau3KR76Em
a/hQz6D82pVeiALL9W9Xyp8DYPtYZLfNTfXbS55+hi2CxGJiu8cSCKgCdQ==
-----END PUBLIC KEY-----
; openssl asn1parse -dump -in pub.uncompressed.pem 
    0:d=0  hl=2 l=  89 cons: SEQUENCE          
    2:d=1  hl=2 l=  19 cons: SEQUENCE          
    4:d=2  hl=2 l=   7 prim: OBJECT            :id-ecPublicKey
   13:d=2  hl=2 l=   8 prim: OBJECT            :prime256v1
   23:d=1  hl=2 l=  66 prim: BIT STRING        
      0000 - 00 04 ad e6 8a 8f 2f fe-0a cb 4a da 92 55 68 b2   ....../...J..Uh.
      0010 - 1a bb 72 91 ef a1 26 6b-f8 50 cf a0 fc da 95 5e   ..r...&k.P.....^
      0020 - 88 02 cb f5 6f 57 ca 9f-03 60 fb 58 64 b7 cd 4d   ....oW...`.Xd..M
      0030 - f5 db 4b 9e 7e 86 2d 82-c4 62 62 bb c7 12 08 a8   ..K.~.-..bb.....
      0040 - 02 75                                             .u
; openssl pkey -pubin -in pub.uncompressed.pem 
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEreaKjy/+CstK2pJVaLIau3KR76Em
a/hQz6D82pVeiALL9W9Xyp8DYPtYZLfNTfXbS55+hi2CxGJiu8cSCKgCdQ==
-----END PUBLIC KEY-----


; openssl pkey -in priv.pem -pubout -ec_conv_form compressed | tee pub.compressed.pem 
-----BEGIN PUBLIC KEY-----
MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADreaKjy/+CstK2pJVaLIau3KR76Em
a/hQz6D82pVeiAI=
-----END PUBLIC KEY-----
; openssl asn1parse -dump -in pub.compressed.pem  
    0:d=0  hl=2 l=  57 cons: SEQUENCE          
    2:d=1  hl=2 l=  19 cons: SEQUENCE          
    4:d=2  hl=2 l=   7 prim: OBJECT            :id-ecPublicKey
   13:d=2  hl=2 l=   8 prim: OBJECT            :prime256v1
   23:d=1  hl=2 l=  34 prim: BIT STRING        
      0000 - 00 03 ad e6 8a 8f 2f fe-0a cb 4a da 92 55 68 b2   ....../...J..Uh.
      0010 - 1a bb 72 91 ef a1 26 6b-f8 50 cf a0 fc da 95 5e   ..r...&k.P.....^
      0020 - 88 02                                             ..
; openssl pkey -pubin -in pub.compressed.pem 
-----BEGIN PUBLIC KEY-----
MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADreaKjy/+CstK2pJVaLIau3KR76Em
a/hQz6D82pVeiAI=
-----END PUBLIC KEY-----


; openssl pkey -in priv.pem -pubout -ec_conv_form hybrid | tee pub.hybrid.pem    
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAHreaKjy/+CstK2pJVaLIau3KR76Em
a/hQz6D82pVeiALL9W9Xyp8DYPtYZLfNTfXbS55+hi2CxGJiu8cSCKgCdQ==
-----END PUBLIC KEY-----
; openssl asn1parse -dump -in pub.hybrid.pem    
    0:d=0  hl=2 l=  89 cons: SEQUENCE          
    2:d=1  hl=2 l=  19 cons: SEQUENCE          
    4:d=2  hl=2 l=   7 prim: OBJECT            :id-ecPublicKey
   13:d=2  hl=2 l=   8 prim: OBJECT            :prime256v1
   23:d=1  hl=2 l=  66 prim: BIT STRING        
      0000 - 00 07 ad e6 8a 8f 2f fe-0a cb 4a da 92 55 68 b2   ....../...J..Uh.
      0010 - 1a bb 72 91 ef a1 26 6b-f8 50 cf a0 fc da 95 5e   ..r...&k.P.....^
      0020 - 88 02 cb f5 6f 57 ca 9f-03 60 fb 58 64 b7 cd 4d   ....oW...`.Xd..M
      0030 - f5 db 4b 9e 7e 86 2d 82-c4 62 62 bb c7 12 08 a8   ..K.~.-..bb.....
      0040 - 02 75                                             .u
; openssl pkey -pubin -in pub.hybrid.pem     
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAHreaKjy/+CstK2pJVaLIau3KR76Em
a/hQz6D82pVeiALL9W9Xyp8DYPtYZLfNTfXbS55+hi2CxGJiu8cSCKgCdQ==
-----END PUBLIC KEY-----

For the app as of the commit at the top of this PR, things work as expected:

; $BLDDIR/util/opensslwrap.sh pkey -pubin -in pub.uncompressed.pem
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEreaKjy/+CstK2pJVaLIau3KR76Em
a/hQz6D82pVeiALL9W9Xyp8DYPtYZLfNTfXbS55+hi2CxGJiu8cSCKgCdQ==
-----END PUBLIC KEY-----
; $BLDDIR/util/opensslwrap.sh pkey -pubin -in pub.compressed.pem
-----BEGIN PUBLIC KEY-----
MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADreaKjy/+CstK2pJVaLIau3KR76Em
a/hQz6D82pVeiAI=
-----END PUBLIC KEY-----
; $BLDDIR/util/opensslwrap.sh pkey -pubin -in pub.hybrid.pem      
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAHreaKjy/+CstK2pJVaLIau3KR76Em
a/hQz6D82pVeiALL9W9Xyp8DYPtYZLfNTfXbS55+hi2CxGJiu8cSCKgCdQ==
-----END PUBLIC KEY-----

I will shortly update with a fixup commit for the test to show what happens when we give fromdata() a compressed point (without explicitly providing OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT as an input param). Spoiler: we default to uncompressed format as the description says.

if ((pub_key_len = EC_POINT_point2buf(ecg, pub_point,
POINT_CONVERSION_COMPRESSED,
format,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little hard to square this change with our promise of a stable libcrypto/provider ABI.
Wouldn't it be "safer" (in that regard) to redefine OSSL_KEYMGMT_SELECT_PUBLIC_KEY to always be compressed, and define a new selector flag to get the public key in the form associated with the key (or in uncompressed form specifically)?
In particular, how would a mismatched 3.0.0/3.0.1 libcrypto/default-provider (or vice versa) handle this change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my concern as well, and the reason I put the OTC hold to discuss it tomorrow!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, documentation never restricted which SECG format the point would be encoded with, and AFAICT the format for import/export (used to?) be considered a provider specific detail.

doc/man7/EVP_PKEY-EC.pod Show resolved Hide resolved
@romen
Copy link
Member Author

romen commented Sep 20, 2021

@mattcaswell see ad692cc for an update to the test at the function level.

Now it gives the point in compressed format as an input param to create the key object, does not explicitly set OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT, and then expects uncompressed format when getting back OSSL_PKEY_PARAM_PUB_KEY out of it.

At least locally on my machine the new test passes.

@levitte @paulidale @t8m @slontis you might want to explicitly reconfirm your approval after the new change.

@romen
Copy link
Member Author

romen commented Sep 20, 2021

(last fixup commit is just to properly capitalize a comment I modified in the commit before)

@openssl-machine
Copy link
Collaborator

24 hours has passed since 'approval: done' was set, but as this PR has been updated in that time the label 'approval: ready to merge' is not being automatically set. Please review the updates and set the label manually.

Copy link
Member

@t8m t8m left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still OK

@mattcaswell
Copy link
Member

OTC: We should respect the OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT. Otherwise the default point conversion format should be as it is in 1.1.1. We should confirm what that default is.

@mattcaswell mattcaswell removed hold: need otc decision The OTC needs to make a decision approval: done This pull request has the required number of approvals labels Sep 21, 2021
@beldmit
Copy link
Member

beldmit commented Oct 22, 2021

where did we stuck with it?

@romen
Copy link
Member Author

romen commented Oct 22, 2021

I have still to work on writing a set of tests for 1.1.1 to port to 3.0 to ensure the behavior is the same under all conditions, I am struggling with finding the time to work on it.

@slontis
Copy link
Member

slontis commented Oct 27, 2021

Hopefully this helps?
By code inspection In 1.1.1

Functions that set conv_form:
(i) EC_KEY_new_method() sets it to the default value of POINT_CONVERSION_UNCOMPRESSED.
(ii) EC_KEY_oct2key() reads it from the first octet byte and sets it for non custom curves
EC_KEY_oct2key() is called from:
(1) ASN1_PKEY_CTRL_SET1_TLS_ENCPT
(2) d2i_ECPrivateKey (Used by PEM_X509_INFO_read_bio, d2i_ECPrivateKey_bio, d2i_ECPrivateKey_fp, eckey_priv_decode, old_ec_priv_decode)
(3) o2i_ECPublicKey (Used by d2i_PublicKey, ecdh_cms_set_peerkey, eckey_pub_decode)
(iii) EC_KEY_copy().
(iv) EC_KEY_set_conv_form() (also sets group->asn1_form)

Functions the read conv_form:
(i) EC_KEY_print() and the eckey_asn1_meth methods eckey_pub_print() & eckey_priv_print() read conv_form.
(ii) i2d_ECPrivateKey() (Used by i2d_ECPrivateKey_fp, i2d_ECPrivateKey_bio, eckey_priv_encode, old_ec_priv_encode)
(iii) i2o_ECPublicKey() (Used by i2d_PublicKey, eckey_pub_encode, ecdh_cms_encrypt)

@openssl-machine
Copy link
Collaborator

This PR is in a state where it requires action by @openssl/committers but the last update was 30 days ago

@jogme
Copy link

jogme commented Dec 2, 2021

Hey guy, any updates on this?

@romen
Copy link
Member Author

romen commented Dec 2, 2021

I am sorry, haven't found the time to work on this yet.
The OTC request before merging this PR is to write a new suite of tests to check CLI and API results calling the high level functions that end up exporting EC public keys for 1.1.1, port them to 3.0 and make sure that same behavior is guaranteed after merging this PR.
This should be done starting with keys in either of the 3 supported compression formats, to check what the output is on 1.1.1 and make sure the same is achieved in 3.0 after this change.

Unfortunately I have been quite busy lately, so I haven't found the time to actually design, write and tests the 1.1.1 tests, let alone port them to 3.0, and detect if the changes in this PR need amending.

@romen romen removed the approval: review pending This pull request needs review by a committer label Dec 2, 2021
@jogme
Copy link

jogme commented Jan 27, 2022

@romen do you have any updates on this?

@t8m
Copy link
Member

t8m commented Mar 11, 2022

@romen we could help with writing the tests if you could provide some guidance.

@romen
Copy link
Member Author

romen commented Mar 11, 2022

@romen we could help with writing the tests if you could provide some guidance.

@t8m that would be highly appreciated, as I seem to be unable to find the time required to work on this as requested by OTC.

The guidance I have was mostly summed up here: #16624 (comment)

The idea was to start with 1.1.1 and write tests there:

  • the tests should exhaustively cover both the CLI usage and the API usage w.r.t. EC point conversion formats
  • the tests should cover both keys freshly generated and keys deserialized from different sources (using all the conversion format options)
  • the tests should cover all supported known curves, plus at least one prime-field and one binary-field curves from explicit parameters
  • the tests should test the export/serialize results for each key both when the conversion format is not explicitly configured (checking the default behavior for a fresh key or a key deserialized from a given format) and when conv_form is explicitly changed

Once the tests for 1.1.1 are written to test all the combinations above, they are to be ported to 3.0 to verify nothing has changed in the existing behavior. Finally a few new tests should be added to cover the new EVP and OSSL_PARAMS options to alter the conv_form or to generate/ingest keys, and make sure the results are sensible compared to expectations dictated by the behavior recorded for 1.1.1.

@hlandau
Copy link
Member

hlandau commented Apr 11, 2022

@romen I've created a first attempt at tests for 1.1 in #18083.

simonsj pushed a commit to simonsj/libssh that referenced this pull request Aug 4, 2022
Remove usage of deprecated functions.
Exceptions are:
  - pkcs11 (no openssl provider support yet)
  - ec (no support for uncompressed EC keys
    openssl/openssl#16624)

Signed-off-by: Norbert Pocs <npocs@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
@beldmit
Copy link
Member

beldmit commented Oct 6, 2022

What are the circumstances for not merging it?

@romen
Copy link
Member Author

romen commented Oct 6, 2022

What are the circumstances for not merging it?

@beldmit OTC wanted test runs against 1.1.1 and 3.0, before and after the changes of this PR, to ensure there is a regression, and that these changes fully address it.

For details on the OTC ask, see (TL;DR: I just rehashed the same notes over and over in different comments, with different level of detail):

My original concern, upon which I put the original OTC hold and raised this PR to the attention of OTC, was actually on what these changes meant to the stability guarantees on the Provider API, but during the discussion it emerged that OTC was less concerned about that issue, and more about matching exactly the behavior of 1.1.1.

@openssl openssl deleted a comment Oct 8, 2022
…o UNCOMPRESSED

Originally the code to im/export the EC pubkey was meant to be consumed
only by the im/export functions when crossing the provider boundary.
Having our providers exporting to a COMPRESSED format octet string made
sense to avoid memory waste, as it wasn't exposed outside the provider
API, and providers had all tools available to convert across the three
formats.

Later on, with openssl#13139 deprecating the `EC_KEY_*` functions, more state
was added among the params imported/exported on an EC provider-native
key (including `OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT`, although it
did not affect the format used to export `OSSL_PKEY_PARAM_PUB_KEY`).

Finally, in openssl#14800, `EVP_PKEY_todata()` was introduced and prominently
exposed directly to users outside the provider API, and the choice of
COMPRESSED over UNCOMPRESSED as the default became less sensible in
light of usability, given the latter is more often needed by
applications and protocols.

This commit fixes it, by using `EC_KEY_get_conv_form()` to get the
point format from the internal state (an `EC_KEY` under the hood) of the
provider-side object, and using it on
`EVP_PKEY_export()`/`EVP_PKEY_todata()` to format
`OSSL_PKEY_PARAM_PUB_KEY`.
The default for an `EC_KEY` was already UNCOMPRESSED, and it is altered
if the user sets `OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT` via
`EVP_PKEY_fromdata()`, `EVP_PKEY_set_params()`, or one of the
more specialized methods.

For symmetry, this commit also alters `ec_pkey_export_to()` in
`crypto/ec/ec_ameth.c`, part of the `EVP_PKEY_ASN1_METHOD` for legacy EC
keys: it exclusively used COMPRESSED format, and now it honors the
conversion format specified in the EC_KEY object being exported to a
provider when this function is called.

Fixes openssl#16595
@romen
Copy link
Member Author

romen commented Oct 8, 2022

This PR, when the tip was 6206daa used to pass the existing tests and the new tests added as part of it: at that time it was based on 814271e as the tip of the openssl-3.0 branch.

@romen
Copy link
Member Author

romen commented Oct 8, 2022

I am now going to push the squashed commits (plus some fixed typos after @paulidale's feedback in #18320 ), rebased on top of latest openssl-3.0.

@Jakuje
Copy link
Contributor

Jakuje commented Dec 21, 2022

If I am right, this was merged as #19681 and there is #19901 for the backport to 3.0 branch. Can we get this closed/updated as resolved?

@t8m
Copy link
Member

t8m commented Dec 22, 2022

Closing as duplicate of #19901

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
branch: master Merge to master branch branch: 3.0 Merge to openssl-3.0 branch branch: 3.1 Merge to openssl-3.1 severity: fips change The pull request changes FIPS provider sources triaged: bug The issue/pr is/fixes a bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet