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

Replace OpenSSL dependency with pinned BoringSSL #1599

Merged
merged 9 commits into from
Sep 28, 2023
Merged

Conversation

spoonincode
Copy link
Member

@spoonincode spoonincode commented Sep 6, 2023

Historically leap has used OpenSSL for TLS support and various crypto primitives (such as SHA256 and R1 key math). TLS support was removed a while ago leaving just the crypto primitives. Still, even with the rather limited usage, we'd like to avoid depending on system provided libraries for consensus bits, or at least "mathy" consensus bits, as these libraries may unexpectedly change some nuance out from under us resulting in consensus failure. A good example of such a change occurred in openssl 3.0.4 (that did not affect Leap).

Because we no longer directly depend on a TLS library as part of our build, we can pin these crypto primitives to a specific point in time without fear of TLS security flaws in the future. Using BoringSSL as a pinned drop in replacement appears to be a great fit. BoringSSL is designed to be simply submoduled in and static linked against. Also, BoringSSL is a fork of OpenSSL so its behavior and API is likely to be the same as existing OpenSSL usage making it less risky than wholesale replacement and refactor with a different library.

This PR builds on #1233 with some changes, notably:

We now maintain our own fork of BoringSSL. While using upstream directly is nice, the trade off of that approach is that it's harder to audit the changes to the Python generation script. Since #1277 + #1255 will require modifying BoringSSL anyways, the fork seems inevitable. Keep in mind that,

BoringSSL usage typically follows a “live at head” model. Projects pin to whatever the current latest of BoringSSL is at the time of update, and regularly update it to pick up new changes.

While the BoringSSL repository may contain project-specific branches, e.g. chromium-2214, those are not supported release branches and must not as such.

So we are simply using yesterday's HEAD for the fork.

While BoringSSL really can simply be "dropped in" as a submodule that comes with a catch that a recent version of Go is required to build. We don't want that. Thus we end up with something I consider really ugly: committing post processed files in to our repository. We will simply have to tolerate this, as it's just how BoringSSL works,

Generally one checks in these generated files alongside the hand-written build files. Periodically an engineer updates the BoringSSL revision, regenerates these files and checks in the updated result. As an example, see how this is done in Chromium.

To make it worse, the generated files must be placed directly outside of the src submodule directory. I would have liked to place these files in to a generated directory to more cleanly delineate the generated files from non-generated files, but alas without hacking up the generated_build_files.py script more that doesn't seem possible.

I've tried to mitigate this ugliness a little by adding a small test in the workflow that regenerates the intermediate files and will fail if the files don't match what is committed. An example of failure can be found, https://github.com/AntelopeIO/leap/actions/runs/6091633748/job/16528533086

Also, as a reviewer note, the first commit in the PR contains the changes before running the make regenerate_boringssl target, and the third commit contains those generated files added to the repo. Ultimately the changes outside that third commit are quite minimal.

FC_ASSERT( BN_num_bytes( order ) == static_cast<int>(bin.data_size()) );
FC_ASSERT( BN_bn2bin( order, (unsigned char*) bin.data() ) == static_cast<int>(bin.data_size()) );
FC_ASSERT( BN_num_bytes( order ) == bin.data_size() );
FC_ASSERT( BN_bn2bin( order, (unsigned char*) bin.data() ) == bin.data_size() );
Copy link
Member Author

Choose a reason for hiding this comment

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

These are a good example of while BoringSSL is a drop in replacement, it isn't without slight differences. A warning was introduced here because BoringSSL returns a size_t while OpenSSL returned an int.

RAND_bytes((uint8_t*)ret.my->_key.data(), sizeof(ret.my->_key.data()));
} while(!secp256k1_ec_seckey_verify(detail::_get_context(), (const uint8_t*)ret.my->_key.data()));
return ret;
}
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 change requires special attention during review. BoringSSL does not support the K1 curve. We've previously transitioned to using libsecp256k1 for all things K1... except for private key generation as performed here.

_key is a private_key_secret,

and private_key_secret is a fc::sha256,

typedef fc::sha256 private_key_secret;

The original code in this PR is probably incorrect and does not generate 32 random bytes, which would be a disaster.

@@ -7,6 +7,8 @@ set(BN256_INSTALL_COMPONENT "dev")

set( Boost_USE_MULTITHREADED ON )
set( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" )
# don't include boost mysql library as it does a find_package(OpenSSL) thus finding the system openssl which could conflict with the bundled boringssl
set( BOOST_EXCLUDE_LIBRARIES "mysql" )
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 something we'll need to be mindful of on boost bumps (or really any bundled dependency).

@spoonincode spoonincode linked an issue Sep 6, 2023 that may be closed by this pull request
1 task
@greg7mdp
Copy link
Contributor

greg7mdp commented Sep 6, 2023

While BoringSSL really can simply be "dropped in" as a submodule that comes with a catch that a recent version of Go is required to build. We don't want that. Thus we end up with something I consider really ugly: committing post processed files in to our repository.

I think it would be nicer to check in these generated files into our fork of BoringSSL (at least keeping the workflow test that checked-in files match newly generated ones), and to use BoringSSL as a submodule in Leap.
Maybe the checkin of the generated files could be done in an automated fashion via ci/cd.

@spoonincode
Copy link
Member Author

That seems reasonable, let me play with that and see if there are any downsides.

@spoonincode
Copy link
Member Author

@greg7mdp I'm not sure how to best accomplish that. The primary problem here is that the generate_build_files.py is very inflexible with paths: the boringssl source directory (which is the submodule here) must be at src, and the generated files must be in the parent directory of src. That means placing the generated files in the boringssl submodule would require moving all the files around; that's not good.

Obviously we could change generate_build_files.py to be more flexible, but I like keeping the changes as minimal as possible (maybe they can even be upstreamed at some point).

One possibility is to utilize two submodules. We keep the close-to-upstream boringssl as it is now, but then have a new "boringssl-generated" repo that contains the generated files with the src directory pointing to the boringssl repo. Then leap only includes that "boringssl-generated" repo as a submodule. This certainly has some positives, but I'm unsure how others feel about that approach?

@greg7mdp
Copy link
Contributor

greg7mdp commented Sep 7, 2023

One possibility is to utilize two submodules.

This is a little awkward, but I think this is preferable to adding all the generated files to leap itself, and I can't think of a better way to do it. I'm curious to hear what others think.

@linh2931
Copy link
Member

One possibility is to utilize two submodules. We keep the close-to-upstream boringssl as it is now, but then have a new "boringssl-generated" repo that contains the generated files with the src directory pointing to the boringssl repo. Then leap only includes that "boringssl-generated" repo as a submodule.

I think main goals would be simple to upgrade in the future and fast to check out. This approach seems to achieve those.

@spoonincode
Copy link
Member Author

Okay, I've migrated the generated build files to
https://github.com/AntelopeIO/boringssl-build/
There is a workflow there that checks the committed files match. Notice though that I changed the mechanism to git ls-files. A failure example was committed in the failure_example branch, https://github.com/AntelopeIO/boringssl-build/actions/runs/6269480194/job/17025961921

@greg7mdp greg7mdp self-requested a review September 25, 2023 21:01
@spoonincode spoonincode merged commit 2384713 into main Sep 28, 2023
@spoonincode spoonincode deleted the boringssl branch September 28, 2023 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Replace OpenSSL usage with BoringSSL
3 participants