Skip to content

Commit

Permalink
[release/8.0-staging] use also SslCertificateTrust when constructing …
Browse files Browse the repository at this point in the history
…CertificateContext (#104541)

Backport of #103372 and #104016 to release/8.0-staging

## Customer Impact

- [X] Customer reported (#100602)
- [ ] Found internally

Customers developing Android apps are currently unable to use mutual TLS authentication in certain cases as the `SslStreamCertificateContext.Create(...)` method will fail to build an X509Chain instance if the certificate isn't trusted by the OS due to the limitations of the Android platform.

## Regression

- [ ] Yes
- [X] No

## Testing

Unit tests and manual testing on Android emulator.

## Risk

Low. The change is mostly limited to Android where this API doesn't currently work in many cases. 

---------

Co-authored-by: Tomas Weinfurt <tweinfurt@yahoo.com>
Co-authored-by: Vitek Karas <10670590+vitek-karas@users.noreply.github.com>
  • Loading branch information
3 people committed Jul 15, 2024
1 parent 09784cd commit ce2fdb4
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace System.Net.Security
public partial class SslStreamCertificateContext
{
private const bool TrimRootCertificate = true;
private const bool ChainBuildNeedsTrustedRoot = true;

private SslStreamCertificateContext(X509Certificate2 target, ReadOnlyCollection<X509Certificate2> intermediates, SslCertificateTrust? trust)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace System.Net.Security
public partial class SslStreamCertificateContext
{
private const bool TrimRootCertificate = true;
private const bool ChainBuildNeedsTrustedRoot = false;
internal readonly ConcurrentDictionary<SslProtocols, SafeSslContextHandle> SslContexts;
internal readonly SafeX509Handle CertificateHandle;
internal readonly SafeEvpPKeyHandle KeyHandle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public partial class SslStreamCertificateContext
{
// No leaf, no root.
private const bool TrimRootCertificate = true;
private const bool ChainBuildNeedsTrustedRoot = false;

private SslStreamCertificateContext(X509Certificate2 target, ReadOnlyCollection<X509Certificate2> intermediates, SslCertificateTrust? trust)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public partial class SslStreamCertificateContext
{
// No leaf, include root.
private const bool TrimRootCertificate = false;
private const bool ChainBuildNeedsTrustedRoot = false;

internal static SslStreamCertificateContext Create(X509Certificate2 target)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,24 @@ internal static SslStreamCertificateContext Create(
{
if (additionalCertificates != null)
{
foreach (X509Certificate cert in additionalCertificates)
chain.ChainPolicy.ExtraStore.AddRange(additionalCertificates);
}

if (trust != null)
{
if (trust._store != null)
{
chain.ChainPolicy.CustomTrustStore.AddRange(trust._store.Certificates);
}

if (trust._trustList != null)
{
chain.ChainPolicy.CustomTrustStore.AddRange(trust._trustList);
}

if (chain.ChainPolicy.CustomTrustStore.Count > 0)
{
chain.ChainPolicy.ExtraStore.Add(cert);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
}
}

Expand All @@ -67,6 +82,20 @@ internal static SslStreamCertificateContext Create(
NetEventSource.Error(null, $"Failed to build chain for {target.Subject}");
}

if (!chainStatus && ChainBuildNeedsTrustedRoot && additionalCertificates?.Count > 0)
{
// Some platforms like Android may not be able to build the chain unless the chain root is trusted.
// We can try to rebuild the chain with making all extra certificates trused.
// We do not try to evaluate trust here, we jsut need to construct the chain so it should not matter.
chain.ChainPolicy.CustomTrustStore.AddRange(additionalCertificates);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chainStatus = chain.Build(target);
if (!chainStatus && NetEventSource.Log.IsEnabled())
{
NetEventSource.Error(null, $"Failed to build chain for {target.Subject} while trusting additional certificates");
}
}

int count = chain.ChainElements.Count - 1;

// Some platforms (e.g. Android) can't ignore all verification and will return zero
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -914,19 +914,28 @@ public async Task SslStream_ClientCertificate_SendsChain()
}
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/68206", TestPlatforms.Android)]
public async Task SslStream_ClientCertificateContext_SendsChain()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task SslStream_ClientCertificateContext_SendsChain(bool useTrust)
{
(X509Certificate2 clientCertificate, X509Certificate2Collection clientChain) = TestHelper.GenerateCertificates(nameof(SslStream_ClientCertificateContext_SendsChain), serverCertificate: false);
TestHelper.CleanupCertificates(nameof(SslStream_ClientCertificateContext_SendsChain));

SslCertificateTrust? trust = null;
if (useTrust)
{
// This is simplification. We make all the intermediates trusted,
// normally just the root would go here.
trust = SslCertificateTrust.CreateForX509Collection(clientChain, false);
}

var clientOptions = new SslClientAuthenticationOptions()
{
TargetHost = "localhost",
};
clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
clientOptions.ClientCertificateContext = SslStreamCertificateContext.Create(clientCertificate, clientChain);
clientOptions.ClientCertificateContext = SslStreamCertificateContext.Create(clientCertificate, useTrust ? null : clientChain, offline:true, trust);

await SslStream_ClientSendsChain_Core(clientOptions, clientChain);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
Link="ProductionCode\System\Net\Security\ReadWriteAdapter.cs" />
<Compile Include="..\..\src\System\Net\SslStreamContext.cs"
Link="ProductionCode\System\Net\SslStreamContext.cs" />
<Compile Include="..\..\src\System\Net\Security\SslCertificateTrust.cs"
Link="ProductionCode\System\Net\Security\SslCertificateTrust.cs" />
<Compile Include="$(CommonPath)System\Net\SecurityProtocol.cs"
Link="ProductionCode\Common\System\Net\SecurityProtocol.cs" />
<Compile Include="..\..\src\System\Net\Security\TlsAlertType.cs"
Expand Down

0 comments on commit ce2fdb4

Please sign in to comment.