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

242 respect ssl ca info #411

Merged
merged 2 commits into from
Aug 18, 2021
Merged

Conversation

vdye
Copy link
Contributor

@vdye vdye commented Aug 11, 2021

Implements #242

Changes

  • Add parsing for sslCAInfo git config parameter (and corresponding environment variable)
    • Ignored if sslBackend is schannel and schannelUseSSLCAInfo is false or not set
  • If sslCAInfo is set, custom cert validation callback is called for HTTP client
    • If there are any non-certificate chain errors with the server cert, fail (see RemoteCertificateNameMismatch and RemoteCertificateNotAvailable here)
    • If there are any SSL chain errors other than untrusted root, fail (see flags listed here)
    • Check the root of the server certificate
      • If the server cert root matches the cert pointed to by sslCAInfo, success
      • If the certs do not match, fail
  • Unit tests for the sslCAInfo setting
  • Minor whitespace fixes from my last pull request 😜

Testing

For all tests, ran the get command as described in "Development and Debugging" (changing the protocol from "http" to "https" for github.com)

  • Before starting, ran sudo dpkg-reconfigure ca-certificates to interactively (and temporarily) disable the root cert used by github.com
  • Do not set GIT_SSL_CAINFO
    • Result:
      fatal: The SSL connection could not be established, see inner exception.
      fatal: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot
      
  • Set GIT_SSL_CAINFO to path to trusted root certificate
    • Result: successful get execution
  • Set GIT_SSL_CAINFO to path to an unrelated trusted root certificate for GitHub
    • Result:
      fatal: The SSL connection could not be established, see inner exception.
      fatal: The remote certificate was rejected by the provided RemoteCertificateValidationCallback.
      
  • Set GIT_SSL_CAINFO to path to a non-existent file
    • Result: failure with the following printout (caused by uncaught exception in callback)
      fatal: Custom certificate bundle not found at path: <path to fake cert>
      

Questions

  • Should this check whether the sslCAInfo file exists, or let the program fail?
    • Answer: Check first!
  • Should the other X509ChainStatusFlags be allowed to continue on to the cert match check?
    • Answer: nope! be conservative with errors
    • Because the HTTP client first validates the cert against your local root store (and I didn't remove GitHub's actual root CA from my System Roots when testing), I wasn't able to verify that the Untrusted Root error is the only one to come through with an otherwise-valid self-signed cert

@mjcheetham mjcheetham requested review from dscho and mjcheetham August 12, 2021 09:23
Copy link
Collaborator

@mjcheetham mjcheetham left a comment

Choose a reason for hiding this comment

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

A couple of questions and changes regarding validation and what ISettings should return.

Should this check whether the sslCAInfo file exists, or let the program fail?

HttpClientFactory should probably test if the file exists before setting the callback. It should then either warn or die (throw an exception), depending on what Git's behaviour is when http.sslCAInfo points to an invalid path.

Should the other X509ChainStatusFlags be allowed to continue on to the cert match check?

We should be conservative and fail for any certificate error we encounter (and manually check the root matches the supplied one.

Because the HTTP client first validates the cert against your local root store (and I didn't remove GitHub's actual root CA from my System Roots when testing), I wasn't able to verify that the Untrusted Root error is the only one to come through with an otherwise-valid self-signed cert

This is an interesting behaviour. So setting that callback means the "normal" .NET validation still happens before us? That probably deserves a comment in the code for future readers.

src/shared/Microsoft.Git.CredentialManager/Settings.cs Outdated Show resolved Hide resolved
src/shared/Microsoft.Git.CredentialManager/Settings.cs Outdated Show resolved Hide resolved
src/shared/Microsoft.Git.CredentialManager/Settings.cs Outdated Show resolved Hide resolved
{
// First, verify that, if schannel is the HTTP backend, sslCaInfo must be explicitly enabled to be used
if (TryGetSetting(null, KnownGitCfg.Http.SectionName, KnownGitCfg.Http.SslBackend, out string backend) &&
StringComparer.OrdinalIgnoreCase.Equals(backend, "schannel") &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should these constants ("schannel" and "openssl") be const strings in Constants.cs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes for schannel, but would you also want openssl included (even though it's not used anywhere here)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd add openssl too for good measure, it doesn't hurt anyone :)

Copy link
Contributor Author

@vdye vdye Aug 12, 2021

Choose a reason for hiding this comment

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

Because this setting is just a passthrough to libcurl, there are more options than schannel and openssl (depending on platform). If schannel is the only one used here (so far, at least), I think I'd prefer to only include that for now (it keeps us from needing to keep up with mirroring all of libcurl's available options).

EDIT: I ended up creating a (nullable) enum anyway - it looks like schannel and openssl are the most commonly used, so they're included in that. I included an "Other" value as well (to handle the rest and to distinguish from the "not specified" case - if there's a better way to handle that, I'm happy to change it).

Copy link
Contributor Author

@vdye vdye Aug 17, 2021

Choose a reason for hiding this comment

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

If openssl is the default backend used by Git, would it be more appropriate to use a non-nullable TlsBackend for the setting, setting the value to openssl if it's unspecified in the user's config? I've updated the implementation to do this, but I can revert if you'd like.

@vdye
Copy link
Contributor Author

vdye commented Aug 12, 2021

Because the HTTP client first validates the cert against your local root store (and I didn't remove GitHub's actual root CA from my System Roots when testing), I wasn't able to verify that the Untrusted Root error is the only one to come through with an otherwise-valid self-signed cert

This is an interesting behaviour. So setting that callback means the "normal" .NET validation still happens before us? That probably deserves a comment in the code for future readers.

The official documentation is pretty unclear, but I think the .NET source code confirms it (assuming I traced the through it properly). I'll add a comment explicitly mentioning that in the callback.

// First, verify that, if schannel is the HTTP backend, sslCaInfo must be explicitly enabled to be used
if (TryGetSetting(null, KnownGitCfg.Http.SectionName, KnownGitCfg.Http.SslBackend, out string backend) &&
StringComparer.OrdinalIgnoreCase.Equals(backend, "schannel") &&
(!TryGetSetting(null, KnownGitCfg.Http.SectionName, KnownGitCfg.Http.SchannelUseSslCaInfo, out string schannelUseSslCaInfo) ||
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there any chance that we can call git config --type=path http.sslCAInfo? The --type=path option would ensure that we benefit from Git's own path interpolation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This would involve some changes to GitConfiguration.cs (like adding an optional isPath argument to TryGet(...) or creating a TryGetPath(...) method), but I really like the benefits of using Git's path interpolation logic (and not replicating it here). @mjcheetham what do you think?

Does this handle the environment variable version of the config as well (in this case, GIT_SSL_CAINFO)? Or is %(prefix) (and any other "magic" path interpolation) not supported with environment variables?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This does not handle the environment variables (but neither does Git interpolate prefix-relative paths in environment variables, so I think we're good).

Copy link
Collaborator

Choose a reason for hiding this comment

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

@mjcheetham what do you think?

I think this should be discussed in a separate PR/issue, so let's leave this as is today for this change.

@vdye vdye force-pushed the 242-respect-sslCAInfo branch 4 times, most recently from 8778c15 to 8a7b46e Compare August 15, 2021 00:55
@vdye vdye requested a review from mjcheetham August 15, 2021 01:09
@vdye vdye force-pushed the 242-respect-sslCAInfo branch 2 times, most recently from e87c754 to 36fd743 Compare August 16, 2021 14:22
@vdye
Copy link
Contributor Author

vdye commented Aug 16, 2021

I figured out a way to test this more accurately on Ubuntu (manually disabling the root certificate used by GitHub, then copying that cert to a custom location to use it as a "self-signed" cert) - results have been updated in the "Testing" section of the PR description.

Copy link
Collaborator

@mjcheetham mjcheetham left a comment

Choose a reason for hiding this comment

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

This looks good to me! Good job @vdye! Just one nit (if you want to fix.. don't worry if not!) and a question to @dscho about default values for the http.sslBackend.

Replicate the behavior of `http.sslCAInfo` (internally, cURL's `cainfo`/
OpenSSL's `cafile` arguments). Implemented by manually comparing the server
certificate with the config-specified CA file.
@vdye vdye force-pushed the 242-respect-sslCAInfo branch from d7f77c4 to d58bf23 Compare August 18, 2021 15:41
@vdye vdye merged commit 770eeeb into git-ecosystem:main Aug 18, 2021
@vdye vdye deleted the 242-respect-sslCAInfo branch August 18, 2021 16:41
@mjcheetham mjcheetham mentioned this pull request Oct 8, 2021
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