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

Proposed Update to support OpenSSL 3.x and earlier versions #529

Open
wants to merge 29 commits into
base: master
Choose a base branch
from

Conversation

MWASoftware
Copy link

This pull request includes work performed by @mezen in developing a version of Indy supporting OpenSSL 1.1.1. This version is intended to overcome two criticisms of @mezen's update: The first is that the proposal for 1.1.1 did not use the existing Indy OpenSSL classes. The second is that Openssl 3.x was not yet supported.

This pull request has taken @mezen's new openssl loader, and intermediate code OpenSSL header files, added Pascal versions of additional OpenSSL header files (e.g. for DES support) and a new code processor (a sed script) to process the intermediate code into usable unit files. With a few mods to IdSSLOpenSSL, the result supports OpenSSL 1.0.2, 1.1.1 and 3.x and probably works with earlier versions - but not tested.

The IdSSLOpenSSL components are unchanged except for the addition of two useful properties:

  • to report the TLS protocol in use (ideally always TLS 1.3), and
  • a new feature to automatically use the Windows crypto API so that the Windows Root Certificate store can be used for validation on Windows hosts (the default - but can be disabled at compile time).

There are also some low level changes to handle the change to opaque data structures (e.g. SSL_CTX) in 1.1.1 and 3.x.

Note that the IdSSLOpenSSLHeaders.pas and IdSSLOpenSSLHeaders_static.pas units have been removed and have been replaced by the header files in Lib/Protocols/opensslHdrs. The new header files are intended to have a one-to-one correspondence with the OpenSSL header (.h) files.

Three link models are supported.

  • Dynamic Library Load (the default and the approach used in previous versions and supports OpenSSL 1.0.2, 1.1.1 and 3.x).
  • static linkage to a shared (.so or .dll) library (OpenSSL 3.x only)
  • static linkage to a static library (FPC only with gcc compiled OpenSSL 3.x).

This version has been tested on both Windows and Linux with both Lazarus/FPC
(3.2.2) and Delphi (Berlin 32 bit only). There are two test programs used for
validation:

  • Https Get from a public website with certification validation
  • Https Get from an embedded TIdHttpServer using a private PKI and password protected keys.

Hopefully the result will be found useful.

Please read README.OpenSSL for further information on the update.

NOTES FOR USING THE TEST PROGRAMS

The directory Test/OpenSSL contains the two test programs together with a
README giving more details on their use. They are intended to run "standalone".
That is without having to integrate Indy into the IDE. There are variants for
both Delphi and Lazarus/fpc.

Note: when testing under Windows, if there are no default OpenSSL dlls in the
search path then you will either need to copy libssl and libcrypto into the
project folder or add a command line parameter providing the location of the
OpenSSL dlls.

NOTES FOR LAZARUS/FPC

Please try out the test programs before following the instructions below.

Before trying out the update with your own programs, you should run the script:

  • getindy4lazfpc.sh (Linux)
  • getindy4lazfpc.bat (Windows)

These scripts are intended to extract the files relevant to Lazarus/FPC to
another directory and to place the Lazarus package files in the destination
directory itself. By default the scripts extract the files to:

$HOME/Indy (Linux)
"%homedrive%%homepath%"\Indy (Windows)

To override the default, provide the target directory as the only command line
argument for the script. There is no need for the target directory to exist
before running the script.

After running the script, you should read the file README.lazarus-fpc for
further guidance.

Note that you must ensure that any existing Lazarus project that you test this
update with, includes the indyprotocols run time package in the requirements
list.

NOTES FOR DELPHI USERS

Integration with the Delphi IDE is tbd and needs someone to take this on. That is there is a need to update the Delphi package definitions, but otherwise, the update should work with Delphi.

@rlebeau rlebeau added Type: Enhancement Issue is proposing a new feature/enhancement Element: SSL/TLS Issues related to SSL/TLS handling, TIdSSLIOHandlerSocketBase and descendants Element: I/O Handlers Issues related to TIdIOHandler and descendants Status: Review Needed Issue needs further review to decide next status labels Apr 8, 2024
baka0815

This comment was marked as outdated.

begin
inherited;
FFailed := TStringList.Create();
FLibraryLoaded := TIdThreadSafeBoolean.Create;

Choose a reason for hiding this comment

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

You need to initialize FFailedToLoad to False in the constructor. Otherwise it might be accidentally initialized as True and TOpenSSLLoader.Load will fail (and not even try) to load the DLLs.

Copy link
Member

@rlebeau rlebeau Apr 25, 2024

Choose a reason for hiding this comment

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

@baka0815 The memory for a class object is initialized with zero bytes before the constructor is called. Thus, by default all numeric members are 0, booleans are False, strings are empty, etc.

This comment was marked as outdated.

Choose a reason for hiding this comment

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

Ok, looks like a testing mistake on my end.

The TOpenSSLLoader is only created once and after the loading failed once it won't try to load again. So you need to restart the program if the OpenSSL 3.x DLLs were not present when you first tried to load them

However I'm a fan of explicit initializations, but it then worked for me without it. So this thread can be closed.

Copy link
Author

Choose a reason for hiding this comment

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

I am not really that happy with the whole strategy for handling load failures. The existing strategy of refusing to load the SSL libraries as soon as any function fails to load follows on from the legacy version. However, it does cause a lot of problems given that different builds select different compile time options - especially with legacy functions such as MD2. I also inherited the one shot load approach.

I would really like to change the whole approach to one where only when certain core functions (such as reading version information) will cause a failure to load. Otherwise, a run time exception is called when and if a function that failed to load is invoked. That way missing functions that are not critical for the calling app do not cause a problem and an explicit error message can focus attention on the problematic function.

This is a relatively simply change to bring in as the update is to the header file generator script. Any views?

Copy link
Member

Choose a reason for hiding this comment

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

There are other areas of Indy (ie, WinSock, ZLib) that dynamically load using per-function stubs, raising load failures only at the invocation site. I didn't write the OpenSSL support code, but I think at the time it was probably not a good idea for OpenSSL to use such stubs because of the sheer number of functions being loaded, that's probably why a one-time load strategy was used instead. I'm not against switching to stubs, but I can imagine that would severely bloat the loading unit.

Copy link
Author

Choose a reason for hiding this comment

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

I have created a new branch to my proposed update. This is called "NewLibLoadStrategy". This changes the function loading strategy such if a function cannot be loaded, the call is set to a call to an exception handler unless an appropriate backwards or forwards compatibility function exists. When the call is set to an exception handler, the function name is added to the FailedToLoad list but no exception is generated. it is up to the user to check this list if they need to test if a required (optional) function is present. If they do not then when it is called a run time exception is generated given the name of the missing function.

I have made this a separate branch so that they two load strategies can be compared for userability/bloat, etc.

@baka0815
Copy link

baka0815 commented Apr 25, 2024

I'm creating my server with the following options:

  LIOHandleSSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
  LIOHandleSSL.SSLOptions.Method := sslvTLSv1_2;
  LIOHandleSSL.SSLOptions.Mode := sslmServer;

However the connection via Firefox is still established as TLS v1.3 (TLS_AES_1128_GCM_SHA256, 128-Bit-Key, TLS 1.3).

Also cURL can connect via --tlsv1.1, --tlsv1.2 and --tlsv1.3, even if I only allow TLS 1.0 or 1.1.

  LIOHandleSSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1];
  LIOHandleSSL.SSLOptions.Method := sslvTLSv1_1;
  LIOHandleSSL.SSLOptions.Mode := sslmServer;

@baka0815
Copy link

Loading the legacy OpenSSL 1.0.2 DLLs fails for me with the following function not being present: EVP_md2

Is this expected or should this be added to LegacyAllowFailed?

{$IFDEF DEBUG}
if not Result then
begin
writeln;
Copy link

@baka0815 baka0815 Apr 26, 2024

Choose a reason for hiding this comment

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

This gives me E/A error 105 if I'm running my program as a GUI application.
You might want to use OutputDebugMessage ({$IFDEF Windows}) or check for {$IFDEF CONSOLE}.

Copy link
Author

Choose a reason for hiding this comment

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

Point taken - all my testing was on console mode apps or Linux...

@MWASoftware
Copy link
Author

I'm creating my server with the following options:

  LIOHandleSSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
  LIOHandleSSL.SSLOptions.Method := sslvTLSv1_2;
  LIOHandleSSL.SSLOptions.Mode := sslmServer;

However the connection via Firefox is still established as TLS v1.3 (TLS_AES_1128_GCM_SHA256, 128-Bit-Key, TLS 1.3).

Also cURL can connect via --tlsv1.1, --tlsv1.2 and --tlsv1.3, even if I only allow TLS 1.0 or 1.1.

  LIOHandleSSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1];
  LIOHandleSSL.SSLOptions.Method := sslvTLSv1_1;
  LIOHandleSSL.SSLOptions.Mode := sslmServer;

In the README.OpenSSL.txt I have noted a typo when I said that "SSLOptions.Mode" is ignored. I meant "SLLOptions.Method" is ignored. "SSLOptions.Versions" is now the only means of control over which version is supported. The README does state that this determines in the minimum version only. This was intentional. On thinking about it, perhaps it should also determined the maximum supported. I will update.

… maximum versions

          of the TLS Protocol negotiated for OpenSSLVersions 1.1.0 and later
@MWASoftware
Copy link
Author

In the README.OpenSSL.txt I have noted a typo when I said that "SSLOptions.Mode" is ignored. I meant "SLLOptions.Method" is ignored. "SSLOptions.Versions" is now the only means of control over which version is supported. The README does state that this determines in the minimum version only. This was intentional. On thinking about it, perhaps it should also determined the maximum supported. I will update.

Both the README and the code have been updated (and tested), and pushed to github. As the update log says "SSLOptions.SSLVersions can now be used to select both the minimum and maximum versions of the TLS Protocol negotiated for OpenSSLVersions 1.1.0 and later. OpenSSL will always try to negotiate the highest version acceptable to both ends of the session."

@baka0815
Copy link

baka0815 commented Apr 26, 2024

Thanks for the change @MWASoftware and the work you're putting into this! I will take a look at the changes later on.

Loading the legacy OpenSSL 1.0.2 DLLs fails for me with the following function not being present: EVP_md2

Is this expected or should this be added to LegacyAllowFailed?

Any comments regarding this?
I'm using the pre-compiled OpenSSL library from https://indy.fulgan.com/SSL/ (1.0.2u)

@MWASoftware
Copy link
Author

Thanks for the change @MWASoftware and the work you're putting into this! I will take a look at the changes later on.

Loading the legacy OpenSSL 1.0.2 DLLs fails for me with the following function not being present: EVP_md2
Is this expected or should this be added to LegacyAllowFailed?

Any comments regarding this? I'm using the pre-compiled OpenSSL library from https://indy.fulgan.com/SSL/ (1.0.2u)

See NewLibLoadStrategy branch and response to earlier comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Element: I/O Handlers Issues related to TIdIOHandler and descendants Element: SSL/TLS Issues related to SSL/TLS handling, TIdSSLIOHandlerSocketBase and descendants Status: Review Needed Issue needs further review to decide next status Type: Enhancement Issue is proposing a new feature/enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants