-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Send CA names on Linux and OSX #65195
Send CA names on Linux and OSX #65195
Conversation
Tagging subscribers to this area: @dotnet/ncl, @vcsjones Issue DetailsFixes Issue #55802
|
if (frameSize < int.MaxValue) | ||
{ | ||
_buffer.EnsureAvailableSpace(frameSize - _buffer.EncryptedLength); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When supplying store-based SslCertificateTrust
, the ServerHello can grow up substantially (well over 8 KB buffer used until now, so we may need to resize the buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is needed. If the hello is bigger than maximum frame size it should arrive in two (or more) frames. And that should be OK IMHO. If it is not, we should figure out what is happening.
This would be problem already, right? e.g. this is unrelated to sending the list since it is in receiving path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a way for the code to deterministically throw exception without this change in case Root trust cert names are sent in ServerHello (around 120 names):
- Default buffer size during handshake is 8kB
- (one of) the incoming frames is larger than 8kB, the size is known before entering the while loop below the added code
- first read fills in rest of the buffer, part of the frame remains in the inner stream
- since the frame was known, the call to
_buffer.EnsureAvailableSpace
is skipped - on second iteration, 0 bytes are read since there is no available space to read into
throw new IOException(SR.net_io_eof)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there chance this will mess up our buffer logic? I briefly look at the EnsureAvailableSpace
implementation and it can for example change _activeStart e.g. move the data within the buffer. I'm wondering if we should call EnsureAvailableSpace immediately when we determine frame size...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can mess things up here. The compacting/moving of the data was happening even before we merged the _internalBuffer
and _handshakeBuffer
I'm wondering if we should call EnsureAvailableSpace immediately when we determine frame size...?
That is what is happening here, unless I misunderstand you. The frame size is read in the HaveFullTlsFrame
call above and (if the size wasnt' known at that time) at the end of the while loop below.
The alternative place to call EnsureAvailableSpace
would be right after we finish processing the previous frame and that does not feel right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generally looks good.
X509Certificate2Collection certList = (trust._trustList ?? trust._store!.Certificates); | ||
|
||
Debug.Assert(certList != null, "certList != null"); | ||
foreach (X509Certificate2 cert in certList) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be fine. But I'm wondering if we could add all at once via some array or stackalloc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it just so we don't traverse the language boundary for each item? We can do that I guess.
{ | ||
if (!Ssl.SslAddClientCA(sslHandle, cert.Handle)) | ||
{ | ||
Debug.Fail("Failed to add issuer to trusted CA list."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would probably also add trace message so we have some evidence for release builds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should only happen when more than max. number of X509_NAME
are pushed to the stack, that is something around int.MaxValue
. I'd wager we are safe here.
I wanted to add a comment there but did not push that yet.
if (frameSize < int.MaxValue) | ||
{ | ||
_buffer.EnsureAvailableSpace(frameSize - _buffer.EncryptedLength); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this is needed. If the hello is bigger than maximum frame size it should arrive in two (or more) frames. And that should be OK IMHO. If it is not, we should figure out what is happening.
This would be problem already, right? e.g. this is unrelated to sending the list since it is in receiving path.
src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs
Outdated
Show resolved
Hide resolved
} | ||
|
||
acceptableIssuers = issuers; | ||
return clientCertificate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably does not matter but I'm wondering if we can turn this into Theory and have bool to send the client cert conditionally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this does not matter for this test, we are just checking that issuers
are populated based on the SslCertificateTrust
. There are other tests that exercise the cert-returning behavior
eab38fe
to
262f567
Compare
67274ae
to
b076d8a
Compare
Should be ready for another round of reviews |
For the macOS you can try We have test code to generate certificates and CA on demand. You can experiment with that. However, I had to disable some of the tests as there seems to be some race condition between when the certificate is generate to when it can be actually used. |
{ | ||
fixed (IntPtr* pHandles = &MemoryMarshal.GetReference(x509handles)) | ||
{ | ||
return SslAddClientCAs(ssl, pHandles, x509handles.Length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we should throw here on failure to be consistent with SslSetCertificateAuthorities
on macOS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can fail only when maximum number of STACK_OF(X509_NAME)
items is reached (something around MAX_INT
), so we would not be likely to throw anyway, I added the propagation of the return code for consistency.
X509Certificate2Collection certList = (trust._trustList ?? trust._store!.Certificates); | ||
|
||
Debug.Assert(certList != null, "certList != null"); | ||
IntPtr[] handles = new IntPtr[certList.Count]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you use same stackalloc optimization as in Linux?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try, I was trying to avoid changing CFArrayCreate
to accept Span<IntPtr>
instead of IntPtr[]
used to construct the handle passed to native code accepts array, and I
@@ -21,7 +21,7 @@ public static SslCertificateTrust CreateForX509Store(X509Store store, bool sendT | |||
throw new PlatformNotSupportedException(SR.net_ssl_trust_store); | |||
} | |||
#else | |||
if (sendTrustInHandshake) | |||
if (sendTrustInHandshake && !System.OperatingSystem.IsLinux() && !System.OperatingSystem.IsMacOS()) | |||
{ | |||
// to be removed when implemented. | |||
throw new PlatformNotSupportedException("Not supported yet."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should update the message and put it to resource file. This looks like omission from time when we expected to finish everything before 6.0 release
src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c
Outdated
Show resolved
Hide resolved
b076d8a
to
c2ce0d8
Compare
/azp run runtime-libraries-coreclr outerloop |
Azure Pipelines successfully started running 1 pipeline(s). |
BTW this should work for Linux & macOS @rzikm diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs
index eb5947be183..6ae6eec6c71 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCertificateTrustTests.cs
@@ -20,19 +20,15 @@ public class SslStreamCertificateTrustTest
[PlatformSpecific(TestPlatforms.Linux | TestPlatforms.OSX)]
public async Task SslStream_SendCertificateTrust_CertificateCollection()
{
- using X509Store store = new X509Store("Root", StoreLocation.LocalMachine);
- store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
- X509Certificate2[] certList = store.Certificates.DistinctBy(c => c.Subject).Take(2).ToArray();
+ (X509Certificate2 certificate, X509Certificate2Collection caCerts) = TestHelper.GenerateCertificates("foo");
SslCertificateTrust trust = SslCertificateTrust.CreateForX509Collection(
- new X509Certificate2Collection(certList),
+ caCerts,
sendTrustInHandshake: true);
string[] acceptableIssuers = await ConnectAndGatherAcceptableIssuers(trust);
- Assert.Equal(2, acceptableIssuers.Length);
- Assert.Contains(certList[0].Subject, acceptableIssuers);
- Assert.Contains(certList[1].Subject, acceptableIssuers);
+ Assert.Equal(caCerts.Count, acceptableIssuers.Length);
} passing CA certificates makes macOS happy. e.g. it seems to depend on constraint I'm wondering if we should check the certificate and throw consistent on all platforms. There is only DN on the wire but it does not make sense IMHO to advertise something that is not CA. If we decide not to go that path I think we should write test with "disliked" certificate to make sure we handle that and that we surface some reasonable exception. And perhaps test that purposely break the 1 TLS frame boundary so we have test coverage for that. |
It depends on what you think the callers are doing. If they do something like open a cert store and pass store.Certificates (whether it be LM\Root or they're just using the store as a way to talk about a list that's shared across processes) then the nicer thing would be to skip over anything that isn't both self-issued and has the CA:true basic constraint set. If you expect the caller instead to have a reference to like 3 certs and make the collection manually, then throwing seems more appropriate. |
I just look at I see this in my "ROOT" Keychain
Since this is combined with the |
It doesn't care. Probably doesn't care that it's self-issued, either. But the chain/trust evaluator will care at the end.
Skipping over non-CA certificates in managed code, for all OSes, seems reasonable to me. I previously said non-self-issued... but I'm now not recalling if this list is supposed to be only root authorities (self-issued) or if it's the subject name of any acceptable CA (including intermediates). So don't add that filter without checking somewhere authoritative 😄 |
Same here, it's what the RFC says anyway
|
9975564
to
045a3fc
Compare
There seem to be consistent test failures on Windows, so I will for now disable the test: #65515 |
While the intent is clear, this is more about technicalities IMHO. We have seen in past where for example people setup self-signed certs or self-hosted CA were are all the flags or best practices are not followed. After thinking about it for a while I'm inclined to defer the decision to OS. We already have platform differences in certificate validation so it seems we should follow that here as well. |
It is possible to put non-CA cert in a store on OSX, right? If so, we still need to filter these certs out, otherwise user would not be able to use |
I was thinking about it and I think that is OK as long as we surface reasonable exception. I feel that it is better than silent failure. In case of macOS is is somewhat strict in what it considers as valid (e.g. trusted) CA. Putting certificates that do not meet the expectation to root store will not make them trusted for SSL. If somebody really need to deal with this they can always give us collection. And I think sending the CA list is going be very rare. |
don't know why, but the Still don't know whats different between the VM at Helix and my WSL instance, both use OpenSSL 1.1.1. |
Could be openssl.conf. If you still have Helix repro, could you try to run just that single test to see if that still fails? |
This reverts commit 7d864a185a7150cd384f16bba5efd616f27759ca.
541957b
to
fbba399
Compare
The issue was openssl/openssl#7384. I fixed it by forcing TLS 1.2, as we do in other test (like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
[PlatformSpecific(TestPlatforms.Linux | TestPlatforms.OSX)] | ||
public async Task SslStream_SendCertificateTrust_CertificateCollection() | ||
{ | ||
(X509Certificate2 certificate, X509Certificate2Collection caCerts) = TestHelper.GenerateCertificates("foo"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you can use nameof(SslStream_SendCertificateTrust_CertificateCollection) -> using test names sometimes helps with debugging if tests are running in parallel.
CI failures are unrelated (transient failure when running System.IO.Tests) |
Changes implemented in dotnet/runtime#65195
Fixes Issue #55802
This PR adds support for
SslCertificateTrust
on Linux and OSX, both platforms are implemented by enumerating the certificates in question (either from provided collection, or enumerating provided certificate store), and passing them to respective platform APIs:SSL_add_client_CA
on LinuxSSLSetCertificateAuthorities
on OSXon OSX, there seems to some additional validation on the passed certificates. When I tried to pass certificates normally used in tests, the call failed with
secErrParam
(One or more parameters passed to the function are not valid.). However, I have not been able to find out what is the problem, so I leave it currently as is, since there is probably not much we can do about it anyway. Certs taken out from the machine Root store sem to work okay.