Skip to content

Commit

Permalink
tls variations example (#615)
Browse files Browse the repository at this point in the history
  • Loading branch information
scottf authored Jun 23, 2022
1 parent 897eb00 commit 44a3369
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 0 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,9 @@ The NATS .NET client supports TLS 1.2. Set the secure option, add
the certificate, and connect. Note that .NET requires both the
private key and certificate to be present in the same certificate file.

In addition to the code found here, please refer to the sample code at
[TlsVariationsExample](src/Samples/TlsVariationsExample/TlsVariationsExample.cs)

```c#
Options opts = ConnectionFactory.GetDefaultOptions();
opts.Secure = true;
Expand All @@ -565,6 +568,10 @@ private key and certificate to be present in the same certificate file.

opts.AddCertificate(cert);

// Some connections like those with OCSP
// require CheckCertificateRevocation
opts.CheckCertificateRevocation = true;

IConnection c = new ConnectionFactory().CreateConnection(opts);
```

Expand Down
7 changes: 7 additions & 0 deletions src/NATS.sln
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTestsUsingNitoAs
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTestsInternal", "Tests\IntegrationTestsInternal\IntegrationTestsInternal.csproj", "{316A3E54-365C-4BD1-B9AB-01DB7158C01A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TlsVariationsExample", "Samples\TlsVariationsExample\TlsVariationsExample.csproj", "{5EB47989-2656-4684-84A9-8935D7B99A8D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -257,6 +259,10 @@ Global
{316A3E54-365C-4BD1-B9AB-01DB7158C01A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{316A3E54-365C-4BD1-B9AB-01DB7158C01A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{316A3E54-365C-4BD1-B9AB-01DB7158C01A}.Release|Any CPU.Build.0 = Release|Any CPU
{5EB47989-2656-4684-84A9-8935D7B99A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5EB47989-2656-4684-84A9-8935D7B99A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5EB47989-2656-4684-84A9-8935D7B99A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5EB47989-2656-4684-84A9-8935D7B99A8D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -300,6 +306,7 @@ Global
{76D213F9-72F7-4B26-A468-96CAB5345119} = {A4CB6D96-6357-40DC-B198-50568A7E45C7}
{69B1924A-F759-49AD-95B2-688CF026094C} = {C8504091-0607-4391-87D7-A46837EB04A5}
{316A3E54-365C-4BD1-B9AB-01DB7158C01A} = {C8504091-0607-4391-87D7-A46837EB04A5}
{5EB47989-2656-4684-84A9-8935D7B99A8D} = {776C2E80-958B-4C0D-BCC4-67D39DB4570B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0972F03C-E4DF-483A-B767-9216F4434F11}
Expand Down
196 changes: 196 additions & 0 deletions src/Samples/TlsVariationsExample/TlsVariationsExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
using System;
using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using NATS.Client;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Collections;

namespace NATSExamples
{
/// <summary>
/// This example shows various ways to handle TLS, including manual verification and
/// OCSP revocation checking.
///
/// Revocation checking requires: opts.CheckCertificateRevocation = true
/// Manual Verification requires: opts.TLSRemoteCertificationValidationCallback = ...
///
/// If you provide a manual callback, you can do whatever you like, including checking
/// the certificate and chain.
///
/// X509 Certificate 2
/// https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2?view=net-6.0
/// X509 Extension Class:
/// https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509extension?view=net-6.0
///
/// BouncyCastle is an industry standard crypto library https://www.bouncycastle.org/index.html
/// It might provide functionality that will help handling the certificate and chain.
///
/// </summary>
internal static class TlsVariationsExample
{
static readonly string Url = "tls://localhost:4222";

public static void Main(string[] args)
{
var opts = RevocationCheckingOptions();
// var opts = ManualVerificationOptions();
try
{
using (IConnection c = new ConnectionFactory().CreateConnection(opts))
{
}
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
}
}

private static Options RevocationCheckingOptions()
{
Options opts = ConnectionFactory.GetDefaultOptions();
opts.Url = Url;
opts.Secure = true;
X509Certificate2 cert = new X509Certificate2("client.pfx", "password");
opts.AddCertificate(cert);
opts.CheckCertificateRevocation = true;
return opts;
}

private static Options ManualVerificationOptions()
{
Options opts = ConnectionFactory.GetDefaultOptions();
opts.Url = Url;
opts.Secure = true;
opts.TLSRemoteCertificationValidationCallback = ManualServerCertVerification;
return opts;
}

private static bool ManualServerCertVerification(object sender,
X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
Console.WriteLine();

if (certificate is X509Certificate2 cert2)
{
Console.WriteLine("X509Certificate2");
try
{
byte[] rawdata = cert2.RawData;
Console.WriteLine(" Content Type: " + X509Certificate2.GetCertContentType(rawdata));
Console.WriteLine(" Friendly Name: " + cert2.FriendlyName);
Console.WriteLine(" Certificate Verified?: " + cert2.Verify());
Console.WriteLine(" Simple Name: " + cert2.GetNameInfo(X509NameType.SimpleName,true));
Console.WriteLine(" Signature Algorithm: " + cert2.SignatureAlgorithm.FriendlyName);
// Console.WriteLine(" Public Key: " + cert2.PublicKey.Key.ToXmlString(false));
Console.WriteLine(" Certificate Archived?: " + cert2.Archived);
Console.WriteLine(" Length of Raw Data: " + cert2.RawData.Length);
}
catch (CryptographicException)
{
Console.WriteLine("Information could not be written out for this certificate.");
}
Console.WriteLine();

Console.WriteLine("X509Certificate2 Extensions");
foreach (X509Extension ext in cert2.Extensions)
{
Console.WriteLine(" " + ext.GetType().Name
+ "\n Oid: " + ext.Oid.FriendlyName
+ "\n Critical: " + ext.Critical
+ "\n Raw Len: " + ext.RawData.Length);

if (ext is X509BasicConstraintsExtension bcExt)
{
Console.WriteLine(" CA: " + bcExt.CertificateAuthority);
Console.WriteLine(" HPLC: " + bcExt.HasPathLengthConstraint);
Console.WriteLine(" PLC: " + bcExt.PathLengthConstraint);
}
else if (ext is X509KeyUsageExtension kuExt)
{
Console.WriteLine(" Usages: " + kuExt.KeyUsages);
}
else if (ext is X509EnhancedKeyUsageExtension ekuExt)
{
if (ekuExt.EnhancedKeyUsages.Count > 0)
{
Console.WriteLine(" Enhanced Key Usages");
foreach (Oid oid in ekuExt.EnhancedKeyUsages)
{
Console.WriteLine(" " + oid.FriendlyName);
}
}
}
else if (ext is X509SubjectKeyIdentifierExtension skiExt)
{
Console.WriteLine(" Subject Key Identifier: " + skiExt.SubjectKeyIdentifier);

}
}
Console.WriteLine();
}

Console.WriteLine("sslPolicyErrors");
Console.WriteLine(" " + sslPolicyErrors);
Console.WriteLine();

if (chain != null)
{
Console.WriteLine("X509Chain Statuses");
foreach (X509ChainStatus cs in chain.ChainStatus)
{
Console.WriteLine(" " + cs.Status + " | " + cs.StatusInformation);
}
Console.WriteLine();
}

// ------------------------------------------------------------------------------------------
// BouncyCastle
// ------------------------------------------------------------------------------------------
Org.BouncyCastle.X509.X509Certificate bcX509 = DotNetUtilities.FromX509Certificate(certificate);
Console.WriteLine("BouncyCastle X509Certificate");
Console.WriteLine(" " + bcX509.CertificateStructure);
Console.WriteLine(" " + bcX509.IsValidNow);
Console.WriteLine(" " + bcX509.NotBefore);
Console.WriteLine(" " + bcX509.NotAfter);
Console.WriteLine(" " + bcX509.SigAlgName);
Console.WriteLine(" " + bcX509.SigAlgOid);
if (bcX509.GetExtendedKeyUsage().Count > 0)
{
Console.WriteLine(" Extended Key Usage");
foreach (var eku in bcX509.GetExtendedKeyUsage())
{
Console.WriteLine(" " + eku);
}
}

void InspectExtension(ISet extSet, string label)
{
if (extSet.Count > 0)
{
Console.WriteLine(" " + label);
foreach (string oid in extSet)
{
try
{
Asn1OctetString asn = bcX509.GetExtensionValue(new DerObjectIdentifier(oid));
Console.WriteLine(" Oid: " + oid + " | Asn: " + asn);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
}

InspectExtension(bcX509.GetNonCriticalExtensionOids(), "Non Critical Extensions");
InspectExtension(bcX509.GetCriticalExtensionOids(), "Critical Extensions");

return true; // true if the cert is okay, false if it not
}
}
}
30 changes: 30 additions & 0 deletions src/Samples/TlsVariationsExample/TlsVariationsExample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<Title>NATS TLS Variations Example</Title>
<Description>NATS TLS Variations Example</Description>
<IsPackable>false</IsPackable>
<RootNamespace>NATSExamples</RootNamespace>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\NATS.Client\NATS.Client.csproj" />
<ProjectReference Include="..\JetStreamExampleUtils\JetStreamExampleUtils.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="BouncyCastle" Version="1.8.9" />
</ItemGroup>

<ItemGroup>
<None Update="client.pfx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

<PropertyGroup>
<NoWarn>$(NoWarn);NU1701</NoWarn>
</PropertyGroup>
</Project>

Binary file added src/Samples/TlsVariationsExample/client.pfx
Binary file not shown.

0 comments on commit 44a3369

Please sign in to comment.