Skip to content

Commit

Permalink
Add support for custom certificate stores (#1420)
Browse files Browse the repository at this point in the history
The current SDK supports only Windows and Directory certificate stores. For security reasons, it can be necessary to use custom certificate stores. 
* Add support for custom certificate stores
* Certificate Store Type UnitTests
* Start cleanup of ICertificateStore interface in the CRL section (Co-authored-by: Martin Regen <mregen@microsoft.com>)
  • Loading branch information
mheege-abb committed Jan 17, 2022
1 parent dd6f4a4 commit 61e204f
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 17 deletions.
81 changes: 81 additions & 0 deletions Libraries/Opc.Ua.Security.Certificates/X509Crl/X509Crl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
using System.IO;
using System.Security.Cryptography;
using System.Formats.Asn1;
using System.Runtime.Serialization;

namespace Opc.Ua.Security.Certificates
{
Expand Down Expand Up @@ -353,4 +354,84 @@ private void EnsureDecoded()
private X509ExtensionCollection m_crlExtensions;
#endregion
}

/// <summary>
/// A collection of X509CRL.
/// </summary>
[CollectionDataContract(Name = "ListOfX509CRL", ItemName = "X509CRL")]
public class X509CRLCollection : List<X509CRL>
{
/// <summary>
/// Gets or sets the element at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the element to get or set.</param>
/// <exception cref="ArgumentNullException"></exception>
public new X509CRL this[int index]
{
get
{
return (X509CRL)base[index];
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}

base[index] = value;
}
}

/// <summary>
/// Create an empty X509CRL collection.
/// </summary>
public X509CRLCollection()
{
}

/// <summary>
/// Create a crl collection from a single CRL.
/// </summary>
public X509CRLCollection(X509CRL crl)
{
Add(crl);
}

/// <summary>
/// Create a crl collection from a CRL collection.
/// </summary>
public X509CRLCollection(X509CRLCollection crls)
{
AddRange(crls);
}

/// <summary>
/// Create a collection from an array.
/// </summary>
public X509CRLCollection(X509CRL[] crls)
{
AddRange(crls);
}

/// <summary>
/// Converts an array to a collection.
/// </summary>
public static X509CRLCollection ToX509CRLCollection(X509CRL[] crls)
{
if (crls != null)
{
return new X509CRLCollection(crls);
}
return new X509CRLCollection();
}

/// <summary>
/// Converts an array to a collection.
/// </summary>
public static implicit operator X509CRLCollection(X509CRL[] crls)
{
return ToX509CRLCollection(crls);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -745,17 +745,17 @@ public StatusCode IsRevoked(X509Certificate2 issuer, X509Certificate2 certificat
/// <summary>
/// Returns the CRLs in the store.
/// </summary>
public List<X509CRL> EnumerateCRLs()
public X509CRLCollection EnumerateCRLs()
{
return new List<X509CRL>();
return new X509CRLCollection();
}

/// <summary>
/// Returns the CRLs for the issuer.
/// </summary>
public List<X509CRL> EnumerateCRLs(X509Certificate2 issuer, bool validateUpdateTime = true)
public X509CRLCollection EnumerateCRLs(X509Certificate2 issuer, bool validateUpdateTime = true)
{
return new List<X509CRL>();
return new X509CRLCollection();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

using System;
using System.Collections.Generic;
using System.IO;

namespace Opc.Ua
Expand Down Expand Up @@ -121,22 +122,31 @@ public static string DetermineStoreType(string storePath)
return CertificateStoreType.X509Store;
}

foreach (string storeTypeName in CertificateStoreType.RegisteredStoreTypeNames)
{
ICertificateStoreType storeType = CertificateStoreType.GetCertificateStoreTypeByName(storeTypeName);
if (storeType.SupportsStorePath(storePath))
{
return storeTypeName;
}
}

return CertificateStoreType.Directory;
}

/// <summary>
/// Returns an object that can be used to access the store.
/// </summary>
public static ICertificateStore CreateStore(string storeType)
public static ICertificateStore CreateStore(string storeTypeName)
{
ICertificateStore store = null;

if (String.IsNullOrEmpty(storeType))
if (String.IsNullOrEmpty(storeTypeName))
{
return new CertificateIdentifierCollection();
}

switch (storeType)
switch (storeTypeName)
{
case CertificateStoreType.X509Store:
{
Expand All @@ -148,9 +158,14 @@ public static ICertificateStore CreateStore(string storeType)
store = new DirectoryCertificateStore();
break;
}

default:
{
ICertificateStoreType storeType = CertificateStoreType.GetCertificateStoreTypeByName(storeTypeName);
if (storeType != null)
{
store = storeType.CreateStore();
break;
}
throw new ArgumentException($"Invalid store type name: {storeType}", nameof(storeType));
}
}
Expand Down Expand Up @@ -187,6 +202,35 @@ public static ICertificateStore OpenStore(string path)
/// </summary>
public static class CertificateStoreType
{
static CertificateStoreType()
{
s_registeredStoreTypes = new Dictionary<string, ICertificateStoreType>();
}

#region public methods
/// <summary>
/// Registers a new certificate store type that con be specified in config files.
/// </summary>
/// <param name="storeTypeName">The name of the store type.</param>
/// <param name="storeType"></param>
public static void RegisterCertificateStoreType(string storeTypeName, ICertificateStoreType storeType)
{
s_registeredStoreTypes.Add(storeTypeName, storeType);
}
#endregion public methods

#region internal methods
internal static ICertificateStoreType GetCertificateStoreTypeByName(string storeTypeName)
{
ICertificateStoreType result;
s_registeredStoreTypes.TryGetValue(storeTypeName, out result);
return result;
}

internal static IReadOnlyCollection<string> RegisteredStoreTypeNames => s_registeredStoreTypes.Keys;
#endregion internal methods

#region data members
/// <summary>
/// A windows certificate store.
/// </summary>
Expand All @@ -196,6 +240,9 @@ public static class CertificateStoreType
/// A directory certificate store.
/// </summary>
public const string Directory = "Directory";

private static readonly Dictionary<string, ICertificateStoreType> s_registeredStoreTypes;
#endregion data members
}
#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,9 @@ public StatusCode IsRevoked(X509Certificate2 issuer, X509Certificate2 certificat
/// <summary>
/// Returns the CRLs in the store.
/// </summary>
public List<X509CRL> EnumerateCRLs()
public X509CRLCollection EnumerateCRLs()
{
List<X509CRL> crls = new List<X509CRL>();
var crls = new X509CRLCollection();

// check for CRL.
DirectoryInfo info = new DirectoryInfo(this.Directory.FullName + Path.DirectorySeparatorChar + "crl");
Expand All @@ -486,15 +486,14 @@ public List<X509CRL> EnumerateCRLs()
/// <summary>
/// Returns the CRLs for the issuer.
/// </summary>
public List<X509CRL> EnumerateCRLs(X509Certificate2 issuer, bool validateUpdateTime = true)
public X509CRLCollection EnumerateCRLs(X509Certificate2 issuer, bool validateUpdateTime = true)
{
if (issuer == null)
{
throw new ArgumentNullException(nameof(issuer));
}

List<X509CRL> crls = new List<X509CRL>();

var crls = new X509CRLCollection();
foreach (X509CRL crl in EnumerateCRLs())
{
if (!X509Utils.CompareDistinguishedName(crl.Issuer, issuer.Subject))
Expand Down
4 changes: 2 additions & 2 deletions Stack/Opc.Ua.Core/Security/Certificates/ICertificateStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ public interface ICertificateStore : IDisposable
/// <summary>
/// Returns the CRLs in the store.
/// </summary>
List<X509CRL> EnumerateCRLs();
X509CRLCollection EnumerateCRLs();

/// <summary>
/// Returns the CRLs for the issuer.
/// </summary>
List<X509CRL> EnumerateCRLs(X509Certificate2 issuer, bool validateUpdateTime = true);
X509CRLCollection EnumerateCRLs(X509Certificate2 issuer, bool validateUpdateTime = true);

/// <summary>
/// Adds a CRL to the store.
Expand Down
33 changes: 33 additions & 0 deletions Stack/Opc.Ua.Core/Security/Certificates/ICertificateStoreType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
The source code in this file is covered under a dual-license scenario:
- RCL: for OPC Foundation members in good-standing
- GPL V2: everybody else
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
GNU General Public License as published by the Free Software Foundation;
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/

namespace Opc.Ua
{
/// <summary>
/// Supports implementation for custom certificate store type.
/// </summary>
public interface ICertificateStoreType
{
/// <summary>
/// Determines if the store path is supported by the store type.
/// </summary>
/// <param name="storePath">The store path to examine.</param>
/// <returns><see langword="true"/> if the store type supports the given store path, otherwise <see langword="false"/>.</returns>
bool SupportsStorePath(string storePath);

/// <summary>
/// Creates a new certificate store.
/// </summary>
/// <returns>A reference to the new certificate store object</returns>
ICertificateStore CreateStore();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,14 +208,14 @@ public StatusCode IsRevoked(X509Certificate2 issuer, X509Certificate2 certificat

/// <inheritdoc/>
/// <remarks>CRLs are not supported here.</remarks>
public List<X509CRL> EnumerateCRLs()
public X509CRLCollection EnumerateCRLs()
{
throw new ServiceResultException(StatusCodes.BadNotSupported);
}

/// <inheritdoc/>
/// <remarks>CRLs are not supported here.</remarks>
public List<X509CRL> EnumerateCRLs(X509Certificate2 issuer, bool validateUpdateTime = true)
public X509CRLCollection EnumerateCRLs(X509Certificate2 issuer, bool validateUpdateTime = true)
{
throw new ServiceResultException(StatusCodes.BadNotSupported);
}
Expand Down
6 changes: 6 additions & 0 deletions Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,11 @@
<ProjectReference Include="..\..\Stack\Opc.Ua.Bindings.Https\Opc.Ua.Bindings.Https.csproj" />
<ProjectReference Include="..\Opc.Ua.Security.Certificates.Tests\Opc.Ua.Security.Certificates.Tests.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="Security\Certificates\CertificateStoreTypeTestConfig.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>

0 comments on commit 61e204f

Please sign in to comment.