Pkcs11Interop  3.3.0
Managed .NET wrapper for unmanaged PKCS#11 libraries

Pkcs11Interop forms a bridge between the unmanaged ANSI C and managed .NET worlds. It loads unmanaged PKCS#11 library provided by the cryptographic device vendor and makes its functions accessible to .NET application. Following figure presents the typical usage of Pkcs11Interop library in .NET application (left side) and internal architecture of Pkcs11Interop library (right side):

pkcs11interop-architecture.png

Pkcs11interop uses System.Runtime.InteropServices to define platform invoke methods for unmanaged PKCS#11 API and specifies how data is marshaled between managed and unmanaged memory.

LowLevelAPIs and HighLevelAPIs: Pkcs11Interop API is logically divided into the set of LowLevelAPIs and the set of HighLevelAPIs. In order to bring the full power of PKCS#11 API to the .NET environment LowLevelAPIs follow ANSI C API defined by PKCS#11 specification as closely as possible and because of that require C-like coding style with a manual memory management. On the other hand HighLevelAPIs, which are built on top of LowLevelAPIs, use garbage collector for automatic memory management and utilize developer friendly constructs such as collections or streams.

Fours and eights in the APIs: The C 'long' type is extensively used throughout the PKCS#11 ANSI C API as CK_ULONG type and unfortunately it is one of the most difficult types to marshal since there is no equivalent type in .NET that universally matches its size. The problem is that the C 'long' type can be 4 bytes long on some platforms (Win32, Win64 and Unix32) and in the same time it can be 8 bytes long on the other platforms (Unix64). In .NET there is 'int' type which is 4 bytes long regardless of platform and there is 'long' type which is 8 bytes long regardless of platform. Neither of them can be used as a multiplatform alternative for C 'long' type and the only option is to use and marshal two different sets of functions and structures - one with 'int' .NET type for platforms where C 'long' type is 4 bytes long (LowLevelAPI40, LowLevelAPI41, HighLevelAPI40 and HighLevelAPI41) and the other with 'long' .NET type for platforms where C 'long' type is 8 bytes long (LowLevelAPI80, LowLevelAPI81, HighLevelAPI80 and HighLevelAPI81).

Zeros and ones in the APIs: PKCS#11 v2.20 specification vaguely states that "Cryptoki structures are packed to occupy as little space as is possible. In particular, on the Win32 and Win16 platforms, Cryptoki structures should be packed with 1-byte alignment. In a UNIX environment, it may or may not be necessary (or even possible) to alter the byte-alignment of structures.". One could say that packing with 1-byte alignment should be preferred on all platforms but most of the implementations for Unix platforms use the default byte alignment instead. Structure packing in .NET is controlled by the Pack field of System.Runtime.InteropServices.StructLayoutAttribute which cannot be modified in the runtime so the only option is to use and marshal two different sets of structures - one with Pack field set to 1 to indicate 1-byte alignment (LowLevelAPI41, LowLevelAPI81, HighLevelAPI41 and HighLevelAPI81) and the other with Pack field set to 0 to indicate the default byte alignment (LowLevelAPI40, LowLevelAPI80, HighLevelAPI40 and HighLevelAPI80).

Please note that Net.Pkcs11Interop.HighLevelAPI automagically uses correct set of platform dependent APIs and is recommended API for most use cases.