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

Use the platform native APIs with C++ SSL transport #2063

Merged
merged 52 commits into from
May 3, 2024

Conversation

pepone
Copy link
Member

@pepone pepone commented Apr 19, 2024

This PR adds the ability to programmatically configure the C++ SSL transport using the platform native API.

This PR adds two structs for configuring the SSL transport, Ice::SSL::ClientAuthenticationOptions and Ice::SSL::ServerAuthenticationOptions.

I considered two important use cases for this PR:

  • User must be able to provide its own validation logic using the platform native API (OpenSSL, Schannel, SecureTransport)
  • User must be able to easy provide credentials for a new connection without restarting.

The new configuration API also allow for using the native API for setting lower level details.

The PR reworks the SSL engines to create the ClientAuthenticationOptions and ServerAuthenticationOptions based on the IceSSL properties, when the user doesn't explicitly provide its own options. This shows that the new option structs are enough to replace the IceSSL properties currently supported.

Things to consider for follow up PRs:

  • Rework IceSSL/configuration, and add more tests for the new API.
  • Move IceSSL -> to Ice::SSL in all languages
  • Remove the C++ Certificate API, it is currently used to expose the peer certificate chain in IceSSL::ConnectionInfo
  • I was thinking of also removing info.certs and rely on custom validation callbacks to obtain the peer certificates, but I think we use this in a Glacier2 and IceGrid implementations to fill SSLInfo forwarder to session managers.

cpp/include/Ice/SSL.h Outdated Show resolved Hide resolved
cpp/include/Ice/SSLConnectionInfo.h Outdated Show resolved Hide resolved
cpp/src/Ice/ObjectAdapterFactory.cpp Outdated Show resolved Hide resolved
cpp/src/Ice/ObjectAdapterI.h Outdated Show resolved Hide resolved
@@ -26,6 +27,7 @@ namespace IceSSL

Ice::LoggerPtr getLogger() const;
Ice::PropertiesPtr getProperties() const;
Ice::InitializationData getInitializationData() const;
Copy link
Member

@bernardnormier bernardnormier Apr 21, 2024

Choose a reason for hiding this comment

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

It's not clear to me who is the client for this public exported class.

Is it a public / user-documented API even though there is no doc-comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

We should move this to IceInternal, it is only used by the SSL transport implementation.

# undef SECURITY_WIN32
#endif

namespace Ice::SSL
Copy link
Member

Choose a reason for hiding this comment

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

Is the idea to rename IceSSL -> Ice::SSL in another PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, and do the same in C#/Java replacing the top-level IceSSL namespace with Ice::SSL.

/**
* The server's certificate chain.
*/
CFArrayRef serverCertificateChain;
Copy link
Member Author

@pepone pepone Apr 24, 2024

Choose a reason for hiding this comment

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

This is the simplest when you want to always use the same certificate chain.

It is used to call SSLSetCertificate before the handshake starts.

* @param host The server's host name.
* @return The server's certificate chain.
*/
std::function<CFArrayRef(const std::string& host)> serverCertificateSelectionCallback;
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 callback allows selecting the certificate before SSL handshake starts, host parameter should probably be the adapter name, a bit more convenient way when you want to be able to upgrade the certificate without restart.

* A callback that is called before a new SSL handshake starts. The callback can be used to set additional SSL
* context parameters.
*/
std::function<void(SSLContextRef)> sslNewSessionCallback;
Copy link
Member Author

Choose a reason for hiding this comment

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

You can also set the certificates from this callback, but the other certificate properties simplify it, and the use case for this callback is for more advanced or less used settings for which we don't provide a property.

Comment on lines 155 to 156
std::function<int(int ok, X509_STORE_CTX*, const IceSSL::ConnectionInfoPtr& info)>
clientCertificateVerificationCallback;
Copy link
Member Author

Choose a reason for hiding this comment

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

The implementation wraps this callback and uses it as the OpenSSL verification callback. The signature is the same with the additional ConnectionInfo. The info param is required for the trust verifier, but I plan to remove this dependency in a follow-up PR.

The wrapper saves exceptions thrown from the callback, and rethrows them from the transceiver initialization.

* A callback that is called before a new SSL handshake starts. The callback can be used to set additional SSL
* parameters.
*/
std::function<void(::SSL* ssl, const std::string& host)> sslNewSessionCallback;
Copy link
Member Author

@pepone pepone Apr 24, 2024

Choose a reason for hiding this comment

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

This allows configuring the SSL session before the handshake starts, the SSL object is created from the provided SSL_CTX.

Need to think about the defaults we should use here, or if we can count on OpenSSL defaults being always appropriate.

While more of the parameters can be set in both SSL_CTX and SSL, a few like SNI are only available for SSL object, that is why we use to callbacks.

/**
* The server's SSL context, this objects holds configuration relevant the SSL session establishment.
*/
SSL_CTX* serverSslContext;
Copy link
Member Author

Choose a reason for hiding this comment

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

A callback would be more convenient here too.

Copy link
Member

@externl externl 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 to me! I may have been a bit overzealous with nullptr comments, please ignore any wrong ones :)

@@ -9,7 +9,7 @@ RUN set -eux \
&& sudo dpkg -i packages-microsoft-prod.deb \
&& rm packages-microsoft-prod.deb \
&& sudo apt update \
&& sudo apt-get install -y python3 python3-dev python3-passlib ruby-full \
Copy link
Member

Choose a reason for hiding this comment

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

Is gdb necessary?

Copy link
Member Author

Choose a reason for hiding this comment

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

I use it to debug the tests, I think it is fine for a development container.

* @remarks The trusted root certificates are used by both the default validation callback, and by custom
* validation callback set in clientCertificateValidationCallback.
*
* The application must ensure that the certificate store remains valid during the setup of the Communicator. It
Copy link
Member

Choose a reason for hiding this comment

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

Should setup be initialization?

Suggested change
* The application must ensure that the certificate store remains valid during the setup of the Communicator. It
* The application must ensure that the certificate store remains valid during the setup of the Communicator. It

Copy link
Member

Choose a reason for hiding this comment

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

I think that's actually obvious and not necessary to document. initialize() is a synchronous function. While it runs, you should not free the arguments you passed via initData.

Copy link
Member Author

Choose a reason for hiding this comment

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

What about the second part about releasing the certificate store?

*
* @remarks This callback is invoked by the SSL transport for each new outgoing connection before starting the
* SSL handshake to determine the appropriate client certificate chain. The callback should return a CFArrayRef
* that represents the client's certificate chain, or NULL if no certificate chain should be used for the
Copy link
Member

Choose a reason for hiding this comment

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

nullptr?

Suggested change
* that represents the client's certificate chain, or NULL if no certificate chain should be used for the
* that represents the client's certificate chain, or nullptr if no certificate chain should be used for the

Copy link
Member Author

Choose a reason for hiding this comment

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

I have to check if nullptr works with CFArrayRef

Copy link
Member

Choose a reason for hiding this comment

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

I thought that nullptr as a drop in replacement for 0 or NULL.

Copy link
Member

Choose a reason for hiding this comment

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

Nice API!

cpp/include/Ice/ServerAuthenticationOptions.h Outdated Show resolved Hide resolved
cpp/src/IceSSL/SSLEngine.cpp Show resolved Hide resolved
cpp/src/IceSSL/SSLEngine.cpp Outdated Show resolved Hide resolved
cpp/src/IceSSL/SecureTransportTransceiverI.cpp Outdated Show resolved Hide resolved
cpp/src/IceSSL/SecureTransportTransceiverI.cpp Outdated Show resolved Hide resolved
cpp/src/IceSSL/SecureTransportTransceiverI.cpp Outdated Show resolved Hide resolved
Copy link
Member

@bernardnormier bernardnormier left a comment

Choose a reason for hiding this comment

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

I like the API but I am concerned about the generated C++ API reference. How is it going to work with all these ifdef?

When you look-up Ice::Ssl::ClientAutenticationOptions::clientCertificateSelectionCallback, which platform-specific doc-comment do you see? All of them?

It would be clearer for the API reference to have different names, like:

#if defined (ICE_SCHANNEL)
struct SchannelClientAuthenticationOptions
{
    // lots of Schannel-specific doc-comment
   ...
};

using ClientAuthenticationOptions = SchannelClientAuthenticationOptions; // alias for portable code

#elif defined(ICE_SECURE_TRANSPORT)
...

cpp/include/Ice/Certificate.h Show resolved Hide resolved
{
#if defined(_WIN32)
/**
* A callback that allows selecting the client's SSL certificate based on the target server host name.
Copy link
Member

Choose a reason for hiding this comment

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

This target server host name is the host name in the endpoint you're using to establish the connection?

If yes, I think something like "based on the hostname or IP address in the endpoint used to establish the connection" would be clearer.

Copy link
Member Author

@pepone pepone May 3, 2024

Choose a reason for hiding this comment

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

Yes, it is.

cpp/include/Ice/ClientAuthenticationOptions.h Show resolved Hide resolved
* server's certificate chain is validated against these certificates; otherwise, the system's default root
* certificates are used.
*
* @remarks The trusted root certificates are used by both the default validation callback, and by custom
Copy link
Member

Choose a reason for hiding this comment

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

are used by both the default validation callback and by the custom validation ... (no comma, and the).

Copy link
Member

Choose a reason for hiding this comment

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

Did you mean serverCertificateValidationCallback?

Since these fields are not sorted in alphabetical order, I would put this trustedRootCertificates next to serverCertificateValidationCallback.

Also, do we document what the default validation callback is?

* @remarks The trusted root certificates are used by both the default validation callback, and by custom
* validation callback set in clientCertificateValidationCallback.
*
* The application must ensure that the certificate store remains valid during the setup of the Communicator. It
Copy link
Member

Choose a reason for hiding this comment

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

I think that's actually obvious and not necessary to document. initialize() is a synchronous function. While it runs, you should not free the arguments you passed via initData.

cpp/include/Ice/ServerAuthenticationOptions.h Outdated Show resolved Hide resolved
cpp/include/Ice/ServerAuthenticationOptions.h Outdated Show resolved Hide resolved
cpp/include/Ice/ClientAuthenticationOptions.h Outdated Show resolved Hide resolved
cpp/include/Ice/ClientAuthenticationOptions.h Outdated Show resolved Hide resolved
cpp/include/Ice/ServerAuthenticationOptions.h Outdated Show resolved Hide resolved
pepone and others added 6 commits May 3, 2024 10:29
Co-authored-by: Joe George <joe@externl.com>
Co-authored-by: Joe George <joe@externl.com>
Co-authored-by: Joe George <joe@externl.com>
Co-authored-by: Joe George <joe@externl.com>
Co-authored-by: Joe George <joe@externl.com>
Co-authored-by: Joe George <joe@externl.com>
@pepone
Copy link
Member Author

pepone commented May 3, 2024

It would be clearer for the API reference to have different names, like:

That makes sense, assuming we can still use the alias in ObjectAdapter/InitializationData declarations.

@bernardnormier bernardnormier self-requested a review May 3, 2024 13:14
Copy link
Member

@bernardnormier bernardnormier 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!

cpp/src/Ice/ObjectAdapterI.cpp Outdated Show resolved Hide resolved
cpp/src/Ice/ObjectAdapterI.h Outdated Show resolved Hide resolved
@pepone pepone merged commit 91ff0c7 into zeroc-ice:main May 3, 2024
17 checks passed
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.

3 participants