CryptoApi is remarkably flexible and also really hard to use for a bunch of reasons:
1. It does a lot of things. It's an OS API for talking to cryptographic hardware, including the TPM and smart cards, in addition to the API for doing run of the mill cryptographic operations using the OS's implementation. That's on top of serving as a primary way to load/use certificates (through the Ncrypt API, but often you can't entirely avoid dipping into CryptoAPI).
2. It's really, really low level, meaning that you have to directly deal with the intersection of driver/hardware behaviors, feature matrices, and data structure serialization/packing.
3. It's actually two API's merged into the same set of function calls: CAPI, and CNG. Depending on which API is supported for your usage or supported by the hardware drivers, the behavior of the same API call might be subtly different.
4. It's a C API with the classic Win32 API structure packing conventions, including the really fun conventions like "allocate the buffer, pack the data structure, and set the offsets yourself without an API to do it for you" convention, and also the "call a function with a null data pointer to get the size of the buffer you need, and then allocate that buffer and call it again to actually do the work" convention.
5. Because it's so low level and nobody uses it, there's very little documentation on how to troubleshoot errors when you do things wrong, and the API is complex enough that you will do it wrong, and then you will spend a few days just trial-and-error-ing your way out of that hole. There are a lot of places in the API's where in order to know how to do something, you need to look up the behavior of the component you're talking to on the device/provider side of the API, but those providers are often under-documented.
Most developers should try to avoid using it because it's so easy to mess up. The API is extremely powerful, but it's also more of a footgun than it should be.
I know it's not usually a thing to reply to your own comment, but I wanted to add an important detail.
The reason for point #1 is that doing all of these things in one API is a useful framing of the problem. For example, if you want to just use a certificate or cryptographic key pair regardless of whether the private key is in a file or stored in a hardware module, CryptoAPI unifies that interface so that, if you do it right, you have two orthogonal pieces of code: one that locates the key to use and which adapter to use to talk to the underlying software/hardware, and another to request cryptographic operations on an abstracted key pair. The fact that you're talking to pure software or an actual HSM can be abstracted from the code that needs to sign a challenge.
1. It does a lot of things. It's an OS API for talking to cryptographic hardware, including the TPM and smart cards, in addition to the API for doing run of the mill cryptographic operations using the OS's implementation. That's on top of serving as a primary way to load/use certificates (through the Ncrypt API, but often you can't entirely avoid dipping into CryptoAPI).
2. It's really, really low level, meaning that you have to directly deal with the intersection of driver/hardware behaviors, feature matrices, and data structure serialization/packing.
3. It's actually two API's merged into the same set of function calls: CAPI, and CNG. Depending on which API is supported for your usage or supported by the hardware drivers, the behavior of the same API call might be subtly different.
4. It's a C API with the classic Win32 API structure packing conventions, including the really fun conventions like "allocate the buffer, pack the data structure, and set the offsets yourself without an API to do it for you" convention, and also the "call a function with a null data pointer to get the size of the buffer you need, and then allocate that buffer and call it again to actually do the work" convention.
5. Because it's so low level and nobody uses it, there's very little documentation on how to troubleshoot errors when you do things wrong, and the API is complex enough that you will do it wrong, and then you will spend a few days just trial-and-error-ing your way out of that hole. There are a lot of places in the API's where in order to know how to do something, you need to look up the behavior of the component you're talking to on the device/provider side of the API, but those providers are often under-documented.
Most developers should try to avoid using it because it's so easy to mess up. The API is extremely powerful, but it's also more of a footgun than it should be.