Skip to content

Commit

Permalink
Add web client with support for client cert
Browse files Browse the repository at this point in the history
  • Loading branch information
AndersAbel committed Nov 8, 2018
1 parent 7000a47 commit 4bcdcb4
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 5 deletions.
16 changes: 13 additions & 3 deletions Sustainsys.Saml2/CertificateUse.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
namespace Sustainsys.Saml2
using System;

namespace Sustainsys.Saml2
{
/// <summary>
/// How is the certificate used?
/// </summary>
[Flags]
public enum CertificateUse
{
/// <summary>
/// The certificate is used for either signing or encryption, or both
/// The certificate is used for either signing or encryption, or both.
/// Equivalent to Signing | Encryption.
/// </summary>
Both = 0,

Expand All @@ -18,6 +22,12 @@ public enum CertificateUse
/// <summary>
/// The certificate is used for decrypting inbound assertions
/// </summary>
Encryption = 2
Encryption = 2,

/// <summary>
/// The certificate is used as a Tls Client certificate for outbound
/// tls requests.
/// </summary>
TlsClient = 4
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
namespace Sustainsys.Saml2.Configuration
{
/// <summary>
/// Certificates used by the service provider for signing or decryption.
/// Certificates used by the service provider for signing, decryption and
/// TLS client certificates for artifact resolve.
/// </summary>
public class ServiceCertificateCollection: Collection<ServiceCertificate>
{
Expand All @@ -21,7 +22,7 @@ public class ServiceCertificateCollection: Collection<ServiceCertificate>
public void Add(X509Certificate2 certificate)
{
if (certificate == null) throw new ArgumentNullException(nameof(certificate));
InsertItem(base.Count, new ServiceCertificate
InsertItem(Count, new ServiceCertificate
{
Certificate = certificate
});
Expand Down
43 changes: 43 additions & 0 deletions Sustainsys.Saml2/Internal/ClientCertificateWebClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace Sustainsys.Saml2.Internal
{
/// <summary>
/// A WebClient implementation that will add a list of client
/// certificates to the requests it makes.
/// </summary>
internal class ClientCertificateWebClient : WebClient
{
private readonly IEnumerable<X509Certificate2> certificates;
/// <summary>
/// Register the certificate to be used for this requets.
/// </summary>
/// <param name="certificates">Certificates to offer to server</param>
public ClientCertificateWebClient(IEnumerable<X509Certificate2> certificates)
{
this.certificates = certificates;
}
/// <summary>
/// Override the base class to add the certificate
/// to the reuqest before returning it.
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
protected override WebRequest GetWebRequest(Uri address)
{
var request = (HttpWebRequest)base.GetWebRequest(address);
if (certificates != null)
{
foreach(var c in certificates)
{
request.ClientCertificates.Add(c);
}
}
return request;
}
}
}
86 changes: 86 additions & 0 deletions Tests/Tests.Shared/Internal/ClientCertificateWebclientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using FluentAssertions;
using Sustainsys.Saml2.Configuration;
using Sustainsys.Saml2.Internal;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;

namespace Sustainsys.Saml2.Tests.Internal
{
namespace Sustainsys.Saml2.Tests.Internal
{
[TestClass]
public class ClientCertificateWebClientTests
{
[TestMethod]
public void Create_WithoutCertificate_ShouldAddNothingToRequest()
{
var client = new TestableClientCertificateWebClient(null);
var payload = "Doesn't matter";
var destination = new Uri("https://localhost/Endpoint");
try
{
client.UploadString(destination, payload);
}
catch (Exception)
{
//Destination is not listening, but we should get an exception that shows it
// at least tried to connect there.
}
client.HasCertificatesInRequest.Should().BeFalse();
}

[TestMethod]
public void Create_WithCertificate_ShouldAddCertificateToRequest()
{
var config = SustainsysSaml2Section.Current;
var options = new SPOptions(config);

var client = new TestableClientCertificateWebClient(
options.ServiceCertificates
.Where(sc => sc.Use.HasFlag(CertificateUse.TlsClient))
.Select(sc => sc.Certificate));

var payload = "Doesn't matter";
var destination = new Uri("https://localhost/Endpoint");
try
{
client.UploadString(destination, payload);
}
catch (Exception)
{
//Destination is not listening, but we should get an exception that shows it
// at least tried to connect there.
}
client.HasCertificatesInRequest.Should().BeTrue();
}

private class TestableClientCertificateWebClient : ClientCertificateWebClient
{
public bool HasCertificatesInRequest;
private readonly IList<X509Certificate2> expectedCertificates;
public TestableClientCertificateWebClient(IEnumerable<X509Certificate2> certificates)
: base(certificates)
{
expectedCertificates = certificates?.ToList(); // Copy to not rely on shared ref that can be altered.
}
protected override WebRequest GetWebRequest(Uri address)
{
var request = base.GetWebRequest(address);
var httpWebRequest = (HttpWebRequest)request;
if (httpWebRequest != null)
{
HasCertificatesInRequest =
expectedCertificates != null
&& expectedCertificates.Count == httpWebRequest.ClientCertificates.Count
&& expectedCertificates.All(ec => httpWebRequest.ClientCertificates.Contains(ec));
}
return request;
}
}
}
}
}
1 change: 1 addition & 0 deletions Tests/Tests.Shared/Tests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Helpers\StubServer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\XmlElementExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IdentityProviderTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\ClientCertificateWebclientTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\CryptographyExtensionsTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\DateTimeExtensionsTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Internal\DateTimeHelperTests.cs" />
Expand Down

0 comments on commit 4bcdcb4

Please sign in to comment.