Skip to content
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

Make X509CertificateStore support CRLs on Windows #2571

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
4 changes: 4 additions & 0 deletions Docs/Certificates.md
Expand Up @@ -31,6 +31,10 @@ The UA .NET Standard stack supports the following certificate stores:

- The **Trusted Https** store `<root>/trustedHttps` which contains https certificates which are trusted by an application. To establish trust, the same rules apply as explained for the *Trusted* and the *Issuer* store.

### X509Store on Windows
Starting with Version 1.5.xx of the UA .NET Standard Stack the X509Store supports the storage and retrieval of CRLS, if used on the **Windows OS**.
This enables the usage of the X509Store instead of the Directory Store for stores requiring the use of crls, e.g. the issuer or the directory Store.

### Windows .NET applications
By default the self signed certificates are stored in a **X509Store** called **CurrentUser\\UA_MachineDefault**. The certificates can be viewed or deleted with the Windows Certificate Management Console (certmgr.msc). The *trusted*, *issuer* and *rejected* stores remain in a folder called **OPC Foundation\pki** with a root folder which is specified by the `SpecialFolder` variable **%CommonApplicationData%**. On Windows 7/8/8.1/10 this is usually the invisible folder **C:\ProgramData**.

Expand Down
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -29,6 +29,9 @@ More samples based on the official [Nuget](https://www.nuget.org/packages/OPCFou
* Sessions and Subscriptions.
* A [PubSub](Docs/PubSub.md) library with samples.

#### **New in 1.05.xxx**
* CRL Support for the X509Store on Windows

#### **New in 1.05.373**
* 1.05 Nodeset
* Support for [WellKnownRoles & RoleBasedUserManagement](Docs/RoleBasedUserManagement.md).
Expand Down
1 change: 1 addition & 0 deletions Stack/Opc.Ua.Core/Opc.Ua.Core.csproj
Expand Up @@ -92,6 +92,7 @@
<PropertyGroup>
<ZipTmp>$(BaseIntermediateOutputPath)/zipnodeset2</ZipTmp>
<ZipNodeSet2XML>Schema/Opc.Ua.NodeSet2.xml</ZipNodeSet2XML>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
romanett marked this conversation as resolved.
Show resolved Hide resolved

<Target Name="ZipNodeSet2" BeforeTargets="PrepareForBuild" Inputs="$(ZipNodeSet2XML)" Outputs="$(ZipNodeSet2XML).zip">
Expand Down

Large diffs are not rendered by default.

@@ -0,0 +1,222 @@
/* ========================================================================
* Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Security.Cryptography;

namespace Opc.Ua.X509StoreExtensions.Internal
{
/// <summary>
/// Helper functions to access Crls in a Windows X509 Store
/// </summary>
internal static unsafe class X509CrlHelper
{
/// <summary>
/// Gets all crls from the provided X509 Store on Windows
/// </summary>
/// <param name="storeHandle">HCERTSTORE Handle to X509 Store</param>
/// <returns>array of all found crls as byte array</returns>
public static byte[][] GetCrls(IntPtr storeHandle)
{
var crls = new List<byte[]>();

//SupportedOSPlatform("windows5.1.2600")
if (PlatformHelper.IsWindowsWithCrlSupport())
{

CRL_CONTEXT* crlContext = (CRL_CONTEXT*)IntPtr.Zero;
//read until Pointer to crlContext is NullPtr
while (true)
{
crlContext = PInvokeHelper.CertEnumCRLsInStore((HCERTSTORE)storeHandle.ToPointer(), crlContext);

if (crlContext != null)
{
byte[] crl = ReadCrlFromCrlContext(crlContext);

if (crl != null)
{
crls.Add(crl);
}
}
else
{
int error = Marshal.GetLastWin32Error();
if (error == -2146885628)
{
//No more crls found in store
}
else if (error != 0)
{
Utils.LogError("Error while enumerating Crls from X509Store, Win32Error-Code: {0}", error);

Check warning on line 81 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L81

Added line #L81 was not covered by tests
}
break;
}
}
}

return crls.ToArray();
}

/// <summary>
/// gets the crl as byte array from the provided crlcontext
/// </summary>
/// <param name="crlContext">clr context as pointer</param>
/// <returns>crl as byte array</returns>
private static byte[] ReadCrlFromCrlContext(CRL_CONTEXT* crlContext)
{
uint length = crlContext->cbCrlEncoded;
byte[] crl = new byte[length];

Marshal.Copy((IntPtr)crlContext->pbCrlEncoded, crl, 0, (int)length);

return crl;
}


/// <summary>
/// add a crl to to the provided store
/// </summary>
/// <param name="storeHandle">HCERTSTORE Handle to X509 Store</param>
/// <param name="crl">the crl as Asn1 or PKCS7 encoded byte array</param>
public static void AddCrl(IntPtr storeHandle, byte[] crl)
{
if (PlatformHelper.IsWindowsWithCrlSupport())
{
IntPtr crlPointer = Marshal.AllocHGlobal(crl.Length);

Marshal.Copy(crl, 0, crlPointer, crl.Length);//copy from managed array to unmanaged memory

/////+-------------------------------------------------------------------------
// Add certificate/CRL, encoded, context or element disposition values.
//--------------------------------------------------------------------------
//#define CERT_STORE_ADD_NEW 1
//#define CERT_STORE_ADD_USE_EXISTING 2
//#define CERT_STORE_ADD_REPLACE_EXISTING 3
//#define CERT_STORE_ADD_ALWAYS 4
//#define CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES 5
//#define CERT_STORE_ADD_NEWER 6
//#define CERT_STORE_ADD_NEWER_INHERIT_PROPERTIES 7
if (PInvokeHelper.CertAddEncodedCRLToStore(
(HCERTSTORE)storeHandle.ToPointer(),
CERT_QUERY_ENCODING_TYPE.X509_ASN_ENCODING | CERT_QUERY_ENCODING_TYPE.PKCS_7_ASN_ENCODING,
(byte*)crlPointer,
(uint)crl.Length,
3,
null))
{
//success
Marshal.FreeHGlobal(crlPointer);
return;
}
else
{
Marshal.FreeHGlobal(crlPointer);
int error = Marshal.GetLastWin32Error();

Check warning on line 145 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L144-L145

Added lines #L144 - L145 were not covered by tests
if (error == -2147024809)
{
Utils.LogError("Error while adding Crl to X509Store, Win32Error-Code: {0}: ERROR_INVALID_PARAMETER, The parameter is incorrect. ", error);

Check warning on line 148 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L148

Added line #L148 was not covered by tests
}
if (error == -2146881269)
{
Utils.LogError("Error while adding Crl to X509Store, Win32Error-Code: {0}: CRYPT_E_ASN1_BADTAG, ASN1 bad tag value met. ", error);

Check warning on line 152 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L152

Added line #L152 was not covered by tests
}
if (error == -2147024891)
{
Utils.LogError("Error while adding Crl to X509Store, Win32Error-Code: {0}: ERROR_ACCESS_DENIED, Access is denied. ", error);

Check warning on line 156 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L156

Added line #L156 was not covered by tests
}
if (error != 0)
{
Utils.LogError("Error while adding Crl to X509Store, Win32Error-Code: {0}: ", error);

Check warning on line 160 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L160

Added line #L160 was not covered by tests
}
return;

Check warning on line 162 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L162

Added line #L162 was not covered by tests
}
}
}

Check warning on line 165 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L165

Added line #L165 was not covered by tests

/// <summary>
/// deletes a crl from the provided store
/// </summary>
/// <param name="storeHandle">HCERTSTORE Handle to X509 Store</param>
/// <param name="crl">asn1 encoded crl to delete from the store</param>
/// <returns>true if delete sucessfully, false if failure</returns>
public static bool DeleteCrl(IntPtr storeHandle, byte[] crl)
{
if (PlatformHelper.IsWindowsWithCrlSupport())
{
CRL_CONTEXT* crlContext = (CRL_CONTEXT*)IntPtr.Zero;
while (true)
{
crlContext = PInvokeHelper.CertEnumCRLsInStore((HCERTSTORE)storeHandle.ToPointer(), crlContext);

if (crlContext != null)
{
byte[] storeCrl = X509CrlHelper.ReadCrlFromCrlContext(crlContext);


if (crl != null && crl.SequenceEqual(storeCrl))
{
if (!PInvokeHelper.CertDeleteCRLFromStore(crlContext))
{
var error = Marshal.GetLastWin32Error();

Check warning on line 191 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L191

Added line #L191 was not covered by tests
if (error != 0)
{
Utils.LogError("Error while deleting Crl from X509Store, Win32Error-Code: {0}", error);

Check warning on line 194 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L194

Added line #L194 was not covered by tests
}
}
else
{
return true;
}
break;
}
}
else
{
var error = Marshal.GetLastWin32Error();

Check warning on line 206 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L206

Added line #L206 was not covered by tests
if (error == -2146885628)
{
//No more crls found in store"
}
else if (error != 0)
{
Utils.LogError("Error while deleting Crl from X509Store, Win32Error-Code: {0}", error);

Check warning on line 213 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L213

Added line #L213 was not covered by tests
}
break;
}
}
}
return false;

Check warning on line 219 in Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/X509CertificateStore/Extensions/Internal/X509CrlHelper.cs#L219

Added line #L219 was not covered by tests
}
}
}
@@ -0,0 +1,54 @@
/* ========================================================================
* Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/

using System;

namespace Opc.Ua.X509StoreExtensions
{
/// <summary>
/// Static Helper class to retrieve if executed on a specific operating system
/// </summary>
internal static class PlatformHelper
{
/// <summary>
/// True if OS Windows and Version >= Windows XP
/// </summary>
/// <returns>True if Crl Support is given in the system X509 Store</returns>
public static bool IsWindowsWithCrlSupport()
{
OperatingSystem version = Environment.OSVersion;
return
version.Platform == PlatformID.Win32NT
&& (
(version.Version.Major > 5)
|| (version.Version.Major == 5 && version.Version.Minor >= 1)
);
}
}
}