Today I will discuss about how to register custom object identifier on a local computer. Why you need this? .NET Oid class which can resolve many common object identifiers to their friendly names and vice versa. However, not all OIDs are registered there. For example, RDS (Remote Desktop Services, former Terminal Services) team introduces special OID for RDP-SSL enhanced key usage with OID=1.3.6.1.4.1.311.54.1.2:

image

If you have Active Directory domain and at least one Enterprise CA, you can define this OID in Active Directory (by editing certificate template). But what if you don't have Active Directory or internal Enterprise CA? Then PowerShell and CryptoAPI is the answer here!

Looking to a CryptoAPI unmanaged functions we can find this one: CryptRegisterOIDInfo. This function writes OID=Friendly Name association to system registry and allows to overwrite existing bindings (see dwFlags parameter description)! Since the first OID=Friendly Name association is returned, you can set this parameter to CRYPT_INSTALL_OID_INFO_BEFORE_FLAG (0x1) and overwrite existing bindings.

At first we need to define p/invoke signature definitions for CryptRegisterOIDInfo and CryptUnregisterOIDInfo functions (to delete OID definitions). Additionally we need to define CRYPT_OID_INFO structure definition. And they are:

$signature = @"
[SecurityCritical]
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Boolean CryptRegisterOIDInfo(
    CRYPT_OID_INFO pInfo,
    int dwFlags
);
[SecurityCritical]
[DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Boolean CryptUnregisterOIDInfo(
    CRYPT_OID_INFO pInfo
);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct CRYPT_OID_INFO {
    public int cbSize;
    [MarshalAs(UnmanagedType.LPStr)]
    public string pszOID;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string pwszName;
    public int dwGroupId;
    // actually this is a Union, but at this point we don't care about this
    public int dwValue;
    public CRYPTOAPI_BLOB ExtraInfo;
    // for compatibility purposes I'm using structure definition that is supported by
    // Windows XP/Windows Server 2003 (without CNG algorithms).
    // Uncomment the block below if necessary:
    // [MarshalAs(UnmanagedType.LPWStr)]
    // public string pwszCNGAlgid;
    // [MarshalAs(UnmanagedType.LPWStr)]
    // public string pwszCNGExtraAlgid;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CRYPTOAPI_BLOB {
    public UInt32 cbData;
    public IntPtr pbData;
}
"@
Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32 -UsingNamespace System.Security

The minimum required information for CRYPT_OID_INFO structure is:

  • cbSize — the unmanaged size of the structure. The size can be retrieved by using Marshal.SizeOf static method;
  • pszOID — an object identifier string;
  • pwszName — a friendly name associated with the OID;
  • dwGroupId — since our OID is EKU OID, we will use CRYPT_ENHKEY_USAGE_OID_GROUP_ID (0x7).

And here we go:

you must use elevated PowerShell console (with local administrator permissions).

$oidInfo = New-Object PKI.Crypt32+CRYPT_OID_INFO -Property @{
    cbSize = [Runtime.InteropServices.Marshal]::SizeOf([Type][PKI.Crypt32+CRYPT_OID_INFO]);
    pszOID = "1.3.6.1.4.1.311.54.1.2";
    pwszName = "Remote Desktop Authentication";
    dwGroupId = 7;
}
[PKI.Crypt32]::CryptRegisterOIDInfo($oidInfo,0)
# unregister OID
[PKI.Crypt32]::CryptUnregisterOIDInfo($oidInfo)
PS C:\> $oidInfo = New-Object PKI.Crypt32+CRYPT_OID_INFO -Property @{
>>     cbSize = [Runtime.InteropServices.Marshal]::SizeOf([Type][PKI.Crypt32+CRYPT_OID_INFO]);
>>     pszOID = "1.3.6.1.4.1.311.54.1.2";
>>     pwszName = "Remote Desktop Authentication";
>>     dwGroupId = 6;
>> }
>>
PS C:\> [PKI.Crypt32]::CryptRegisterOIDInfo($oidInfo,0)
True

If the function returns True, then everything is ok:

image

The only note here is that OID information may not work immediately and may require to restart PowerShell console. This is because, existing PowerShell console already has OID information in memory and new registered OIDs are not there. But for any new PS windows, a new information is available:

PS C:\> [System.Security.Cryptography.Oid]"1.3.6.1.4.1.311.54.1.2"

Value                                                       FriendlyName
-----                                                       ------------
1.3.6.1.4.1.311.54.1.2                                      Remote Desktop Authentication


PS C:\>

Use the same structure to unregister the OID:

[PKI.Crypt32]::CryptUnregisterOIDInfo($oidInfo)

In this way you can register any OIDs and OID types you need.

HTH.


Share this article:

Comments:

Claudio Latini

I have some problems to execute the script, in detail:

[Runtime.InteropServices.Marshal]::SizeOf([PKI.Crypt32+CRYPT_OID_INFO])

Exception calling "SizeOf" with "1" argument(s): "Type 'System.RuntimeType' cannot be marshaled as an unmanaged
structure; no meaningful size or offset can be computed."
At line:1 char:1
+ [Runtime.InteropServices.Marshal]::SizeOf([PKI.Crypt32+CRYPT_OID_INFO ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentException

My environment is Windows Server 2012 R2

Any ideas?

Vadims Podāns

I fixed the code. The code was orignally written in .NET 3.x era, however, .NET 4+ introduced a problem that invalidated many scripts that use .NET Marshal class. Now the code should work.

Kirill Kovalenko

Hello Vadim,

I guess I've found a bug. XCN_CRYPT_ENHKEY_USAGE_OID_GROUP_ID is 7 not 6 which is XCN_CRYPT_EXT_OR_ATTR_OID_GROUP_ID. (both from wincrypt.h)

There are also their counterparts in certenroll.h with XCN_ prefix.

Kirill

Vadims Podāns

Thanks, fixed.


Post your comment:

Please, solve this little equation and enter result below. Captcha