CryptoPro connection in Mono

In connection with the transition to Linux, it became necessary to transfer one of our server systems written in C # to Mono. The system works with reinforced EDS, therefore one of the tasks set before us was to test the performance of GOST certificates from CryptoPro in mono. CryptoPro itself has already implemented for a long time. CSP under Linux, but the first attempt to use it showed that the native Mono cryptography classes (similar to those in the base .Net - X509Store, X509Certificate? etc.) not only do not work with GOST keys, they do not even see them in their repositories. Because of this, work with cryptography had to be connected directly through the CryptoPro libraries.
 
pinvoke , or copy it from the .Net sources (class
? CAPISafe
). From the same module, one can draw constants and structures related to cryptography, whose presence always makes life easier when working with external libraries.
 
And then we form the static class "UCryptoAPI" which, depending on the system, will call the method of one of two classes:
 
/**
Close the repository
* Flags (you need to set 0)
* A reference to the certificate store
*
The flag of the success of closing the storage
* ** /
internal static bool CertCloseStore (IntPtr _hCertStore, uint_iFlags) {
if (fIsLinux)
return LCryptoAPI.CertCloseStore (_hCertStore, _iFlags);
else
return WCryptoAPI.CertCloseStore (_hCertStore, _iFlags);
}
/**
We are in Linux
** /
public static bool fIsLinux {
get {
int iPlatform = (int) Environment.OSVersion.Platform;
return (iPlatform == 4) || (iPlatform == 6) || (iPlatform == 128);
}
}

 
Thus, using methods of the UCryptoAPI class, you can implement almost a single code for both systems.
 

Search for the certificate


 
Working with cryptography usually starts with a certificate search, for this, in crypt32.dll there are two methods CertOpenStore (opens the specified certificate store) and a simple CertOpenSystemStore (opens the personal user certificates). Due to the fact that work with certificates is not limited only to personal certificates of the user, we connect the first one:
 
Search for the certificate [/b]
/**
Search for a certificate (the first one satisfying the search criteria)
* Search type
* The search value is
* Place
* The name of the repository is
* The returned certificate is
* Returned string with error
* Check certificate
*
Standard error code, if UConsts.S_OK then everything is ok
* ** /
public static int FindCertificateCP (string _pFindValue, out X509Certificate2 _pCert, ref string _sError,
StoreLocation _pLocation = StoreLocation.CurrentUser,
StoreName _pName = StoreName.My,
X509FindType _pFindType = X509FindType.FindByThumbprint,
bool _fVerify = false) {
_pCert = null;
IntPtr hCert = IntPtr.Zero;
GCHandle hInternal = new GCHandle ();
GCHandle hFull = new GCHandle ();
IntPtr hSysStore = IntPtr.Zero;
try {
//0) Open the store
hSysStore = UCryptoAPI.CertOpenStore (UCConsts.AR_CERT_STORE_PROV_SYSTEM w2w2w2?
UCConsts.PKCS_7_OR_X509_ASN_ENCODING,
IntPtr.Zero,
UCUtils.MapX509StoreFlags (_pLocation, OpenFlags.ReadOnly),
UCConsts.AR_CRYPTO_STORE_NAME[(int)_pName]);
if (hSysStore == IntPtr.Zero) {
_sError = UCConsts.S_ERR_STORE_OPEN.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
//1) We form the data in the package
if ((_pFindType == X509FindType.FindByThumbprint) || (_pFindType == X509FindType.FindBySerialNumber))
{
byte[]arData = _pFindValue.FromHex ();
CRYPTOAPI_BLOB cryptBlob;
cryptBlob.cbData = arData.Length;
hInternal = GCHandle.Alloc (arData, GCHandleType.Pinned);
cryptBlob.pbData = hInternal.AddrOfPinnedObject ();
hFull = GCHandle.Alloc (cryptBlob, GCHandleType.Pinned);
} else {
byte[]arData;
if (fIsLinux)
arData = Encoding.UTF8.GetBytes (_pFindValue);
else
arData = Encoding.Unicode.GetBytes (_pFindValue);
hFull = GCHandle.Alloc (arData, GCHandleType.Pinned);
}
//2) Get the
IntPtr hPrev = IntPtr.Zero;
do {
hCert = UCryptoAPI.CertFindCertificateInStore (hSysStore,
UCConsts.PKCS_7_OR_X509_ASN_ENCODING, ?
UCConsts.AR_CRYPT_FIND_TYPE[(int)_pFindType, fIsLinux.ToByte()],
hFull.AddrOfPinnedObject (), hPrev);
//2.1) Release the previous
if (hPrev! = IntPtr.Zero) UCryptoAPI.CertFreeCertificateContext (hPrev);
//2.2) Ended in the list
if (hCert == IntPtr.Zero) return UConsts.E_NO_CERTIFICATE;
//2.3) Found and valid
X509Certificate2 pCert = new ISDP_X509Cert (hCert);
if (! _fVerify || pCert.ISDPVerify ()) {
hCert = IntPtr.Zero;
_pCert = pCert;
return UConsts.S_OK;
}
hPrev = hCert;
//To not clear
hCert = IntPtr.Zero;
} while (hCert! = IntPtr.Zero);
return UConsts.E_NO_CERTIFICATE;
} catch (Exception E) {
_sError = UCConsts.S_FIND_CERT_GEN_ERR.Frm (E.Message);
return UConsts.E_GEN_EXCEPTION;
finally {
//Clear the links and close the repository
if (hInternal.IsAllocated) hInternal.Free ();
if (hFull.IsAllocated) hFull.Free ();
if (hCert! = IntPtr.Zero) UCryptoAPI.CertFreeCertificateContext (hCert);
UCryptoAPI.CertCloseStore (hSysStore, 0);
}
}

 
 
The search takes place in several stages:
 
 
opening of the repository;
 
formation of data structure for which we are looking;
 
search for a certificate;
 
if required, a certificate check (described in a separate section);
 
Closure of the storage and release of the structure from point 2 (because here everything is working with unmanaged memory. Net for us nothing to clean up will not do);
 
 
During the search for certificates there are several subtle points.
 
CryptoPro in Linux works with ANSI strings, and in Windows with UTF? therefore:
 
 
when you connect the method of opening a repository in Linux, you need to explicitly specify the marshaling type[In, MarshalAs (UnmanagedType.LPStr)]for the storage code parameter. ;
 
passing a string to search (for example, by the name of Subject), it must be converted into a set of bytes with different encodings;
 
for all cryptographic constants that have a variation on the type of the string (for example, CERT_FIND_SUBJECT_STR_A and CERT_FIND_SUBJECT_STR_W) in Windows, you must select * _W, and in Linux * _A;
 
 
Method MapX509StoreFlags can be taken directly from the source code of Microsoft without changes, it simply generates the final mask based on .Net flags.
 
The value of the search depends on the type of search (check with MSDN for ? CertFindCertificateInStore
), The example shows the two most commonly used options - for string format (names Subject, Issuer and so on) and binary (imprint, serial number).
 
The process of creating a certificate from IntPtr in Windows and Linux is very different. Windows will create a certificate in a simple way:
 
new X509Certificate2 (hCert);
 
in Linux, you have to create a certificate in two stages:
 
X509Certificate2 (new X509Certificate (hCert));
 
In the future, we will need access to hCert for work, and it should be stored in the certificate object. In Windows, it can later be obtained from the Handle property, but Linux converts the structure CERT_CONTEXT, which is located under the link hCert, into a link to the structure x509_st (OpenSSL) and it is written in Handle. Therefore, it is worthwhile to create an heir from X509Certificate2 (ISDP_X509Cert in the example), which will store in its own field hCert in both systems.
 
Do not forget that this is a reference to the region of unmanaged memory and it must be freed after the end of the work. Because in .Net 4.5 X509Certificate2 is not Disposable - it is purged by the CertFreeCertificateContext method, it must be performed in the destructor.
 

Formation of the signature


 
When working with GOST certificates almost always used uncovered signatures with one signer. In order to create such a signature, a fairly simple code block is required:
 
Formation of the signature [/b]
/**
Signs information
* The data for signing
* Certificate
* Returned string with error
* Signature of the certificate
*
Standard error code, if UConsts.S_OK then everything is ok
* ** /
public static int SignDataCP (byte[]_arData, X509Certificate2 _pCert, out byte[]_arRes, ref string _sError)
{
_arRes = new byte[0];
//0) We form the parameters
CRYPT_SIGN_MESSAGE_PARA pParams = new CRYPT_SIGN_MESSAGE_PARA ();
pParams.cbSize = Marshal.SizeOf (typeof (CRYPT_SIGN_MESSAGE_PARA));
pParams.dwMsgEncodingType = (int) (UCConsts.PKCS_7_OR_X509_ASN_ENCODING);
pParams.pSigningCert = _pCert.getRealHandle ();
pParams.cMsgCert = 1;
pParams.HashAlgorithm.pszObjId = _pCert.getHashAlgirtmOid ();
IntPtr pGlobData = Marshal.AllocHGlobal (_arData.Length);
GCHandle pGC = GCHandle.Alloc (_pCert.getRealHandle (), GCHandleType.Pinned);
try {
pParams.rgpMsgCert = pGC.AddrOfPinnedObject ();
Marshal.Copy (_arData, ? pGlobData, _arData.Length);
uint iLen = 50000;
byte[]arRes = new byte[iLen];
//1) Formation of the signature
if (! UCryptoAPI.CryptSignMessage (ref pParams, true, ? new IntPtr[1]{pGlobData},
new uint[1]{(uint) _arData.Length}, arRes, ref iLen)) {
_sError = UCConsts.S_MAKE_SIGN_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
Array.Resize (ref arRes, (int) iLen);
_arRes = arRes;
return UConsts.S_OK ;;
} catch (Exception E) {
_sError = UCConsts.S_MAKE_SIGN_ERR.Frm (E.Message);
return UConsts.E_GEN_EXCEPTION;
finally {
pGC.Free ();
Marshal.FreeHGlobal (pGlobData);
}
}

 
In the course of the method, a structure with parameters is formed and the method of signing is called. The structure of the parameters can allow you to save certificates in the signature to form a complete chain (cMsgCert and rgpMsgCert, the first stores the number of certificates, the second list of references to the structures of these certificates).
 
The signing method can receive one or more documents for simultaneous signing with one signature. This, by the way, does not contradict 63 FZ and it is very convenient, because the user is unlikely to be pleased to press the "sign" button several times.
 
The main oddity of this method is that it does not work in the two-call mode, which is typical for most library methods that work with large blocks of memory (the first with null - gives the necessary length of the buffer, the second fills the buffer). Therefore, it is necessary to create a large buffer, and then shorten it in real length.
 
The only serious problem is the search for the OID of the hash algorithm (Digest) used when signing - it is not explicitly in the certificate (there is only the algorithm of the signature itself). And if in Windows it can be specified as an empty string - it will be picked up automatically, but Linux refuses to sign if the algorithm is not the one.
 
But here there is a trick - in the information about the signature algorithm (structure CRYPT_OID_INFO), the OID of the signature is stored in pszOID, and in Algid - the identifier of the hashing algorithm is stored. And to convert Algid to OID is already a matter of technique:
 
Getting the OID of the hashing algorithm [/b]
/**
Obtaining the OID of the hash algorithm for the certificate
* The certificate handle is
* The returned parameter is OID
* Returned string with error
*
Standard error code, if UConsts.S_OK then everything is OK
* ** /
internal static int GetHashAlgoritmOID (IntPtr _hCertHandle, out string _sOID, ref string _sError) {
_sOID = "";
IntPtr hHashAlgInfo = IntPtr.Zero;
IntPtr hData = IntPtr.Zero;
try {
CERT_CONTEXT pContext = (CERT_CONTEXT) Marshal.PtrToStructure (_hCertHandle, typeof (CERT_CONTEXT));
CERT_INFO pCertInfo = (CERT_INFO) Marshal.PtrToStructure (pContext.pCertInfo, typeof (CERT_INFO));
//Extract the AlgID
//via UCryptoAPI.CertAlgIdToOID in Windows works for the first time, the second one drops
byte[]arData = BitConverter.GetBytes (UCryptoAPI.CertOIDToAlgId (pCertInfo.SignatureAlgorithm.pszObjId));
hData = Marshal.AllocHGlobal (arData.Length);
Marshal.Copy (arData, ? hData, arData.Length);
//Search OID
hHashAlgInfo = UCryptoAPI.CryptFindOIDInfo (UCConsts.CRYPT_OID_INFO_ALGID_KEY,
.hData,
UCConsts.CRYPT_HASH_ALG_OID_GROUP_ID);
if (hHashAlgInfo == IntPtr.Zero) {
_sError = UCConsts.S_NO_HASH_ALG_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_GEN_EXCEPTION;
}
CRYPT_OID_INFO pHashAlgInfo = (CRYPT_OID_INFO) Marshal.PtrToStructure (hHashAlgInfo, typeof (CRYPT_OID_INFO));
_sOID = pHashAlgInfo.pszOID;
return UConsts.S_OK;
} catch (Exception E) {
_sError = UCConsts.S_DETERM_HASH_ALG_ERR.Frm (E.Message);
return UConsts.E_GEN_EXCEPTION;
finally {
Marshal.FreeHGlobal (hData);
}
}

 
Having carefully read the code you can be surprised that the algorithm identifier is obtained in a simple way (CertOIDToAlgId) and Oid on it is complicated (CryptFindOIDInfo). It would be logical to assume the use of either complex or both simple methods, and in Linux both options work successfully. However, in Windows, the complicated version of getting the identifier and the simple receipt of the OID is unstable, so a strange hybrid will be a stable solution.
 

Verification of the signature


 
Verification of the signature occurs in two stages, at the beginning the signature is checked, and then the certificate that it was formed is verified (chain, date of signing, etc.).
 
As well as at signing it is necessary to specify a set of the signed data, parameters of the signature and the signature:
 
Verification of the signature [/b]
/**
Forms a standard structure for checking the signature
*
Structure
* ** /
internal static CRYPT_VERIFY_MESSAGE_PARA GetStdSignVerifyPar () {
PYerifyParams = new CRYPT_VERIFY_MESSAGE_PARA ();
pVerifyParams.cbSize = (int) Marshal.SizeOf (pVerifyParams);
pVerifyParams.dwMsgEncodingType = UCConsts.PKCS_7_OR_X509_ASN_ENCODING;
pVerifyParams.hCryptProv = 0;
pVerifyParams.pfnGetSignerCertificate = IntPtr.Zero;
pVerifyParams.pvGetArg = IntPtr.Zero;
return pVerifyParams;
}
/**
Verifies the signature
* data that was signed
* signature
* Certificate
* return string with error
* Check only the signature
* The certificate verification mode is
* Verification flag of certificate
*
Standard error code, if UConsts.S_OK then everything is ok
*
Only the first signer
is checked.
* ** /
public static int CheckSignCP (byte[]_arData, byte[]_pSign, out X509Certificate2 _pCert, ref string _sError,
bool _fVerifyOnlySign = true,
X509RevocationMode _pRevMode = X509RevocationMode.Online,
X509RevocationFlag _pRevFlag = X509RevocationFlag.ExcludeRoot) {
. _pCert = null;
IntPtr pHData = Marshal.AllocHGlobal (_arData.Length);
GCHandle pCertContext = GCHandle.Alloc (IntPtr.Zero, GCHandleType.Pinned);
try {
Marshal.Copy (_arData, ? pHData, _arData.Length);
CRYPT_VERIFY_MESSAGE_PARA pVerParam = UCUtils.GetStdSignVerifyPar ();
//0) Verification of the signature
bool fRes = UCryptoAPI.CryptVerifyDetachedMessageSignature (
ref pVerParam, //Confirmation parameters
? //Index of the signer
_pSign, //Signature
_pSign.Length, //Lengthof the signature
? //number of files for the signature
new IntPtr[1]{pHData}, //signed files
new int[1]{_arData.Length}, //Lengths of the signed files
pCertContext.AddrOfPinnedObject ()); //Reference to the certificate
if (! fRes) {
_sError = UCConsts.S_SIGN_CHECK_ERR.Frm (Marshal.GetLastWin32Error (). ToString ("X"));
return UConsts.E_CRYPTO_ERR;
}
//1) Extracting the certificate
_pCert = new ISDP_X509Cert ((IntPtr) pCertContext.Target);
if (_pCert == null) {
_sError = UCConsts.S_SIGN_CHECK_CERT_ERR;
return UConsts.E_CRYPTO_ERR;
}
//2) Verification of the certificate
if (! _fVerifyOnlySign) {
List
pDates;
//2.1) We get the list of dates
int iRes = GetSignDateTimeCP (_pSign, out pDates, ref _sError);
//2.2) Verify the first certificate
iRes = _pCert.ISDPVerify (ref _sError, pDates[0], _pRevMode, _pRevFlag);
if (iRes! = UConsts.S_OK) return iRes;
}
return UConsts.S_OK;
} catch (Exception E) {
_sError = UCConsts.S_SIGN_CHECK_ERR.Frm (E.Message);
return UConsts.E_GEN_EXCEPTION ;;
finally {
Marshal.FreeHGlobal (pHData);
if ((_pCert == null) && pCertContext.IsAllocated && ((IntPtr) pCertContext.Target! = IntPtr.Zero))
UCryptoAPI.CertFreeCertificateContext ((IntPtr) pCertContext.Target);
pCertContext.Free ();
}
}

 
For convenience, the process of forming a structure with parameters is made in a separate method (GetStdSignVerifyPar). After that, the signature itself is checked and the first signer is extracted (on good it would be necessary to extract all, but the signature containing several signers is all the same exotic).
 
After extracting the signer's certificate, we convert it to our class and check (if it is specified in the parameters of the method). For verification, the date of signing the first signer is used (see section extracting information from the signature, and the section checking the certificate).
 

Extract information from the signature


 
Often, cryptographic systems require a printed representation of the signature. In each case, it is different, so it is better to form a class of information about the signature, which will contain information in an easy to use form and already with its help provide a printed presentation. In .Net, this class is - SignedCms, but its analogue in mono with the signatures of CritoPro, first refuses to work, the second contains the sealed modifier and in the third almost all properties are closed for writing, so you will have to build your own analog.
 
The signature itself contains two main elements - a list of certificates and a list of signers. The list of certificates can be empty, and can contain all certificates for verification, including complete chains. The list of the signatories indicates the number of real signatures. Communication between them is carried out by serial number and publisher (Issuer). Theoretically, in one signature there can be two certificates from different publishers with one serial number, but in practice this can be neglected and searched only for the serial number.
 
Reading the signature is as follows:
 
Extraction of information from the signature [/b]
/**
Decipher
* Signature
* Returned string with error
*
Standard error code, if UConsts.S_OK then everything is OK
* ** /
public int Decode (byte[]_arSign, ref string _sError) {
IntPtr hMsg = IntPtr.Zero;
//0) We form the information
try {
hMsg = UCryptoAPI.CryptMsgOpenToDecode (UCConsts.PKCS_7_OR_X509_ASN_ENCODING, UCConsts.CMSG_DETACHED_FLAG,
? IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (hMsg == IntPtr.Zero) {
_sError = UCConsts.S_CRYP_MSG_FORM_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
//1) Enter the message
if (! UCryptoAPI.CryptMsgUpdate (hMsg, _arSign, (uint) _arSign.Length, true)) {
_sError = UCConsts.S_CRYP_MSG_SIGN_COPY_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
//2) Check the type (PKCS7 SignedData)
uint iMessType = UCUtils.GetCryptMsgParam
(hMsg, UCConsts.CMSG_TYPE_PARAM);
if (UCConsts.CMSG_SIGNED! = iMessType) {
_sError = UCConsts.S_CRYP_MSG_SIGN_TYPE_ERR.Frm (iMessType, UCConsts.CMSG_SIGNED);
return UConsts.E_CRYPTO_ERR;
}
//3) We form the list of certificates
fpCertificates = UCUtils.GetSignCertificates (hMsg);
//4) The list of signers
uint iSignerCount = UCUtils.GetCryptMsgParam
(hMsg, UCConsts.CMSG_SIGNER_COUNT_PARAM);
for (int i = 0; i < iSignerCount; i++) {
ISDPSignerInfo pInfo = new ISDPSignerInfo ();
.fpSignerInfos.Add (pInfo);
.int iRes = pInfo.Decode (hMsg, i, this, ref_sError);
if ( iRes! = UConsts.S_OK) return iRes;
}
return UConsts.S_OK;
} catch (Exception E) {
_sError = UCConsts.S_SIGN_INFO_GEN_ERR.Frm (E.Message);
return UConsts.E_GEN_EXCEPTION
} Finally {
.if (hMsg! = IntPtr.Zero) UCryptoAPI.CryptMsgClose (hMsg);
}
}
.

 
The parsing of the signature occurs in several stages, first the message structure (CryptMsgOpenToDecode) is formed, then the real signature data (CryptMsgUpdate) is entered into it. It remains to verify that this is really a signature and get a list of certificates first, and then a list of signers. The list of certificates is extracted in sequence:
 
Getting the list of certificates [/b]
/**
Obtain a collection of certificates by signing
* Handle signatures
*
Collection of certificates
* ** /
internal static X509Certificate2Collection GetSignCertificates (IntPtr _hMsg) {
X509Certificate2Collection certificates = new X509Certificate2Collection ();
uint iCnt = GetCryptMsgParam
(_hMsg, UCConsts.CMSG_CERT_COUNT_PARAM);
for (int i = 0; i < iCnt; i++) {
IntPtr hInfo = IntPtr.Zero;
IntPtr hCert = IntPtr.Zero;
try {
.uint iLen = 0;
if (! GetCryptMsgParam (_hMsg, UCConsts.CMSG_CERT_PARAM, out hInfo, out iLen)) continue;
.hCert = UCryptoAPI.CertCreateCertificateContext (UCConsts.PKCS_7_OR_X509_ASN_ENCODING, hInfo, iLen);
if (hCert! = IntPtr.Zero) {
certificates.Add (new ISDP_X509Cert (hCert))
.hCert = IntPtr.Zero;
}
} Finally {
.if (hInfo! = IntPtr.Zero) Marshal.FreeHGlobal (hInfo);
.if (hInfo! = IntPtr.Zero) Marshal.FreeHGlobal
}
}
return certificates;
}
.

 
First, the number of certificates from the CMSG_CERT_COUNT_PARAM parameter is determined, and then information on each certificate is extracted in sequence. Completes the process of creating the formation of the context of the certificate and on the basis of the certificate itself.
 
Extracting the signer's data is more difficult. They contain an indication of the certificate and the list of signature parameters (for example, the date of signing). The process of extracting data is as follows:
 
Extracting information about the signer [/b]
/**
Parse information from the signature
* Handler signatures
* The index of the signer is
* The structure of the signature is
* Returned string with error
*
Standard error code, if UConsts.S_OK then everything is OK
* ** /
public int Decode (IntPtr _hMsg, int _iIndex, ISDPSignedCms _pSignedCms, ref string _sError) {
//1) Determine the length of
uint iLen = 0;
//2) Read the
IntPtr hInfo = IntPtr.Zero;
try {
if (! UCryptoAPI.CryptMsgGetParam (_hMsg, UCConsts.CMSG_SIGNER_INFO_PARAM, (uint) _iIndex, IntPtr.Zero, ref iLen)) {
_sError = UCConsts.S_ERR_SIGNER_INFO_LEN.Frm (_iIndex, Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
hInfo = Marshal.AllocHGlobal ((int) iLen);
if (! UCryptoAPI.CryptMsgGetParam (_hMsg, UCConsts.CMSG_SIGNER_INFO_PARAM, (uint) _iIndex, hInfo, ref iLen)) {
_sError = UCConsts.S_ERR_SIGNER_INFO.Frm (_iIndex, Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
CMSG_SIGNER_INFO pSignerInfo = (CMSG_SIGNER_INFO) Marshal.PtrToStructure (hInfo, typeof (CMSG_SIGNER_INFO));
//2.1) We are looking for the certificate
byte[]arSerial = new byte[pSignerInfo.SerialNumber.cbData];
Marshal.Copy (pSignerInfo.SerialNumber.pbData, arSerial, ? arSerial.Length);
X509Certificate2Collection pLocCerts = _pSignedCms.pCertificates.Find (X509FindType.FindBySerialNumber,
.ArSerial.Reverse (). ToArray (). ToHex (), false);
if (pLocCerts.Count! = 1) {
_sError = UCConsts.S_ERR_SIGNER_INFO_CERT.Frm (_iIndex);
return UConsts.E_NO_CERTIFICATE;
}
fpCertificate = pLocCerts[0];
fpSignedAttributes = UCUtils.ReadCryptoAttrsCollection (pSignerInfo.AuthAttrs);
return UConsts.S_OK;
} catch (Exception E) {
_sError = UCConsts.S_ERR_SIGNER_INFO_READ.Frm (_iIndex, E.Message);
return UConsts.E_GEN_EXCEPTION;
finally {
if (hInfo! = IntPtr.Zero) Marshal.FreeHGlobal (hInfo);
}
}

 
In the course of it, the size of the signature structure is first determined, and then the structure CMSG_SIGNER_INFO is extracted. It is easy to find the serial number of the certificate and on it to find the required certificate in the previously extracted list. Please note that the serial number is in reverse order.
 
After retrieving the certificate, you need to define the parameters signatures, the most important of which is the date of signing (even if it is not a date stamp verified by the server, it is very important for display).
 
The list of attributes of the signature [/b]
/**
Get the list of attributes of the signature
* Attribute structure
*
Attribute Collection
* ** /
internal static CryptographicAttributeObjectCollection ReadCryptoAttrsCollection (CRYPT_ATTRIBUTES_pAttrs) {
CryptographicAttributeObjectCollection pRes = new CryptographicAttributeObjectCollection ();
for (int i = 0; i < _pAttrs.cAttr; i++) {
IntPtr hAttr = new IntPtr ((long) _pAttrs.rgAttr + (i * Marshal.SizeOf (typeof (CRYPT_ATTRIBUTE)))));
CRYPT_ATTRIBUTE pAttr = (CRYPT_ATTRIBUTE) Marshal.PtrToStructure ( hAttr, typeof (CRYPT_ATTRIBUTE));.
CryptographicAttributeObject pAttrInfo = new CryptographicAttributeObject (new Oid (pAttr.pszObjId),
GetAsnEncodedDataCollection (pAttr));
pRes.Add (pAttrInfo);.
}
return pRes;.
.}
.

 
Attributes are a nested Oid directory-a list of values ​​(in fact, it's an ASN.1 structure). Going to the first level, we form the enclosed list:
 
Disassemble the signature attribute [/b]
/**
Create a collection object of the desired class by name
* The name is
*
The created object is
* ** /
internal static Pkcs9AttributeObject Pkcs9AttributeFromOID (string _sName) {
switch (_sName) {
case UCConsts.S_SIGN_DATE_OID: return new Pkcs9SigningTime ();
//case UConsts.S_CONTENT_TYPE_OID: return new Pkcs9ContentType (); - in Mono falls
//case UConsts.S_MESS_DIGEST_OID: return new Pkcs9MessageDigest ();
default: return new Pkcs9AttributeObject ();
}
}
/**
Forms the collection of ASN
* Structure
*
Collection
* ** /
internal static AsnEncodedDataCollection GetAsnEncodedDataCollection (CRYPT_ATTRIBUTE _pAttr) {
AsnEncodedDataCollection pRes = new AsnEncodedDataCollection ();
Oid pOid = new Oid (_pAttr.pszObjId);
string sOid = pOid.Value;
for (uint i = 0; i < _pAttr.cValue; i++) {
.checked {
) IntPtr pAttributeBlob = new IntPtr ((long) _pAttr.rgValue + (i * Marshal.SizeOf (typeof (CRYPTOAPI_BLOB))));
.Pkcs9AttributeObject attribute = new Pkcs9AttributeObject pOid, BlobToByteArray (pAttributeBlob));
.Pkcs9AttributeObject customAttribute = Pkcs9AttributeFromOID (sOid);
if (customAttribute! = null) {
customAttribute.CopyFrom (attribute);
.attrib = customAttribute;
}
Add (attribute);
}
}
Return pRes;
}
.

 
The key feature of this process is the correct selection of the successor Pkcs9AttributeObject. The problem is that the standard way of creating in mono does not work and you have to configure the class selection directly in the code. In addition, of the main types of Mono at the moment allows you to create only a date.
 
By wrapping the above methods in two classes - information about the signature and information about the signer - we get the SignedCms analog, from which we extract the data when we form the printed form.
 

Encryption


 
The process of encryption is largely analogous to the signing process, it is fairly simple, and the main problem is also in determining the algorithm. Unlike the signature, encryption is most often used concatenated to one or several addresses at once (for example, they also encrypt themselves to address themselves so that they can read the message with their key).
 
Encrypt the data [/b]
/**
The encrypted data is
* The data for decoding
* Certificate
* Result
* Returned string with error
*
Standard code with an error, if UConsts.S_OK then everything is OK
* ** /
public static int EncryptDataCP (byte[]_arInput, X509Certificate2 _pCert, out byte[]_arRes, ref string _sError) {
_arRes = new byte[0];
try {
//0) Initialization of the parameters
CRYPT_ENCRYPT_MESSAGE_PARA pParams = new CRYPT_ENCRYPT_MESSAGE_PARA ();
pParams.dwMsgEncodingType = UCConsts.PKCS_7_OR_X509_ASN_ENCODING;
pParams.ContentEncryptionAlgorithm.pszObjId = _pCert.getEncodeAlgirtmOid ();
pParams.cbSize = Marshal.SizeOf (pParams);
//1) Extract the length of
int iLen = 0;
if (! UCryptoAPI.CryptEncryptMessage (ref pParams, ? new IntPtr[]{_pCert.getRealHandle ()},
_arInput, _arInput.Length, null, ref iLen)) {
_sError = UCConsts.S_CRYPT_ENCODE_LEN_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
//2) The second request is real encryption
_arRes = new byte[iLen];
if (! UCryptoAPI.CryptEncryptMessage (ref pParams, ? new IntPtr[]{_pCert.getRealHandle ()},
_arInput, _arInput.Length, _arRes, ref iLen)) {
_sError = UCConsts.S_CRYPT_ENCODE_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
return UConsts.S_OK;
} catch (Exception E) {
_sError = UCConsts.S_CRYPT_ENCODE_ERR.Frm (E.Message);
return UConsts.E_GEN_EXCEPTION;
}
}

 
The encryption process occurs in three stages - filling in the parameters, determining the length and finally encryption. Encrypted data can be large, which is probably why the method supports two-call mode.
 
The example is encrypted to the address of one destination, but by adding additional certificates to the array and setting the total number in the method parameters, you can increase the number of recipients.
 
But with the algorithm again the problem. In the certificate there is neither it nor even indirect values ​​on which it could be determined (as it was possible with the signature algorithm). Therefore it is necessary to extract the list of supported algorithms from the provider:
 
Getting the encryption algorithm [/b]
/**
Obtaining the OID of the encryption algorithm with the certificate
* The certificate handle is
* The returned parameter is OID
* Returned string with error
*
Standard error code, if UConsts.S_OK then everything is OK
* ** /
internal static int GetEncodeAlgoritmOID (IntPtr _hCertHandle, out string _sOID, ref string _sError) {
bool fNeedRelease = false;
_sOID = "";
uint iKeySpec = 0;
IntPtr hCrypto = IntPtr.Zero;
try {
//0) Get the context of the provider
if (! UCryptoAPI.CryptAcquireCertificatePrivateKey (_hCertHandle, ? IntPtr.Zero,
ref hCrypto, ref iKeySpec, ref fNeedRelease)) {
_sError = UCConsts.S_CRYPTO_PROV_INIT_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
uint iLen = 1000;
byte[]arData = new byte[1000];
uint iFlag = 1; //Initialize
//1) We pass in the loop according to the algorithms
while (UCryptoAPI.CryptGetProvParam (hCrypto, UCConsts.PP_ENUMALGS, arData, ref iLen, iFlag)) {
iFlag = 2; //Next
PROV_ENUMALGS pInfo = ConvertBytesToStruct
(arData);
//2) We are trying to get an OID within the encryption algorithms
byte[]arDataAlg = BitConverter.GetBytes (pInfo.aiAlgid);
IntPtr hDataAlg = Marshal.AllocHGlobal (arDataAlg.Length);
try {
Marshal.Copy (arDataAlg, ? hDataAlg, arDataAlg.Length);
IntPtr hHashAlgInfo2 = UCryptoAPI.CryptFindOIDInfo (UCConsts.CRYPT_OID_INFO_ALGID_KEY,
HDataAlg,
UCConsts.CRYPT_ENCRYPT_ALG_OID_GROUP_ID);
//2.1) Found - return
if (hHashAlgInfo2! = IntPtr.Zero) {
CRYPT_OID_INFO pHashAlgInfo2 = (CRYPT_OID_INFO) Marshal.PtrToStructure (hHashAlgInfo?
Typeof (CRYPT_OID_INFO));
_sOID = pHashAlgInfo2.pszOID;
return UConsts.S_OK;
}
finally {
Marshal.FreeHGlobal (hDataAlg);
}
}
//3) Did not find - error
_sError = UCConsts.S_NO_ENCODE_ALG_ERR;
return UConsts.E_CRYPTO_ERR;
} catch (Exception E) {
_sError = UCConsts.S_DETERM_ENCODE_ALG_ERR.Frm (E.Message);
return UConsts.E_GEN_EXCEPTION;
finally {
if ((hCrypto! = IntPtr.Zero) && fNeedRelease) UCryptoAPI.CryptReleaseContext (hCrypto, 0);
}
}

 
The example retrieves the context of the private key and searches for it using algorithms. But in this list are all the algorithms (key exchange, hashing, signature, encryption, etc.), so you only need to filter the encryption algorithms. We try to extract information for each of us by limiting ourselves to a group of encryption algorithms (UCConsts.CRYPT_ENCRYPT_ALG_OID_GROUP_ID). And if the information is found, then this is our algorithm.
 
If more than one of these algorithms can also be filtered in size (based on the size of the hashing algorithm).
 

Decryption


 
In order to decrypt the data, on the local machine in the personal certificates of the user or computer there must be a certificate of one of the recipients. And a private key must be attached to it. The process follows an already familiar scenario - a list of parameters, the definition of length and the decryption process itself:
 
Decryption of the data [/b]
/**
Decrypts the data
* The data for decoding
* Result
* Returned string with error
* Certificate
*
Standard error code, if UCOnsts.S_OK then everything is OK
* ** /
public static int DecryptDataCP (byte[]_arInput, out X509Certificate2 _pCert, out byte[]_arRes, ref string _sError) {
_arRes = new byte[0];
_pCert = null;
IntPtr hSysStore = UCryptoAPI.CertOpenSystemStore (IntPtr.Zero, UCConsts.AR_CRYPTO_STORE_NAME[(int)StoreName.My]);
GCHandle GC = GCHandle.Alloc (hSysStore, GCHandleType.Pinned);
IntPtr hOutCertL = IntPtr.Zero;
IntPtr hOutCert = IntPtr.Zero;
try {
//0) Preparing the parameters
CRYPT_DECRYPT_MESSAGE_PARA pParams = new CRYPT_DECRYPT_MESSAGE_PARA ();
pParams.dwMsgAndCertEncodingType = UCConsts.PKCS_7_OR_X509_ASN_ENCODING;
pParams.cCertStore = 1;
pParams.rghCertStore = GC.AddrOfPinnedObject ();
pParams.cbSize = Marshal.SizeOf (pParams);
int iLen = 0;
//1) The first call is the length of
if (! UCryptoAPI.CryptDecryptMessage (ref pParams, _arInput, _arInput.Length,
null, ref iLen, ref hOutCertL)) {
_sError = UCConsts.S_DECRYPT_LEN_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
//2) Decrypt the second call
_arRes = new byte[iLen];
if (! UCryptoAPI.CryptDecryptMessage (ref pParams, _arInput, _arInput.Length,
_arRes, ref iLen, ref hOutCert)) {
_sError = UCConsts.S_DECRYPT_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
//3) If there is a pull out certificate
if (hOutCert! = IntPtr.Zero) _pCert = new ISDP_X509Cert (hOutCert);
if (_pCert! = null) hOutCert = IntPtr.Zero;
//Return all ok to
return UConsts.S_OK;
} catch (Exception E) {
_sError = UCConsts.S_DECRYPT_ERR.Frm (E.Message);
return UConsts.E_GEN_EXCEPTION;
finally {
if (hOutCertL! = IntPtr.Zero) UCryptoAPI.CertFreeCertificateContext (hOutCertL);
if (hOutCert! = IntPtr.Zero) UCryptoAPI.CertFreeCertificateContext (hOutCert);
GC.Free ();
UCryptoAPI.CertCloseStore (hSysStore, 0);
}
}

 
When setting the parameters, the storage is specified, from which the system will try to extract the appropriate certificate with the key. As a result, the system will output the decrypted data and the certificate that was used (in Linux the certificate is always returned empty).
 

Verification of the certificate


 
A certificate is not only a public key, but also a set of different information about its owner, who issued it and on the set of actions that can be used to make it. Also, the certificate has a validity period and the possibility of revocation, in case of compromise. The most common way to verify a certificate is as follows:
 
 
chain integrity (publisher certificate, publisher certificate publisher certificate, etc.);
 
root publisher certificate - must be in the trusted root center store;
 
the validity period of all certificates - the moment of using the certificate must be within the limits of this period;
 
 
each of the certificates in the chain other than the root must be absent from the list of revoked from its publisher (CRL);
 
 
On good it is still necessary to check and the rights of the signature, but in real life it is done seldom.
 
As is already clear from the introduction, validating a certificate for validity is one of the most difficult tasks. That is why the library has a lot of methods for implementing each of the items separately. Therefore, for simplicity, we turn to the .Net source for the method. X509Certificate2.Verify () and take them as a basis.
 
The check consists of two stages:
 
 
form a chain of certificates up to the root;
 
check each of the certificates in it (for recall, time, etc.);
 
 
Such verification should be carried out before signing and encryption on the current date, and at the time of verification of the signature on the date of signing. The method of verification is small:
 
Verification of the certificate [/b]
/**
Check the certificate
* The review flag is
* The recall mode is
* Link to the validation rules
* the context of the certificate
* request list request timeout
* Date of verification
* Returned string with error
*
Standard error code, if UConsts.S_OK then everything is ok
* ** /
internal static int VerifyCertificate (IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag,
.DateTime _rOnDate, TimeSpan _iCTLTimeout, IntPtr _hPolicy, ref string _sError) {
if (_hCert == IntPtr.Zero) {
_sError = UCConsts.S_CRYPTO_CERT_CHECK_ERR;
return UConsts.E_NO_CERTIFICATE;
}
CERT_CHAIN_POLICY_PARA pPolicyParam = new CERT_CHAIN_POLICY_PARA (Marshal.SizeOf (typeof (CERT_CHAIN_POLICY_PARA))));
CERT_CHAIN_POLICY_STATUS pPolicyStatus = new CERT_CHAIN_POLICY_STATUS (Marshal.SizeOf (typeof (CERT_CHAIN_POLICY_STATUS))));
//1) We form the chain
IntPtr hChain = IntPtr.Zero;
try {
int iRes = BuildChain (new IntPtr (UCConsts.HCCE_CURRENT_USER), _hCert, __iRevMode, _iRevFlag,
_rOnDate, _iCTLTimeout, ref hChain, ref _sError);
if (iRes! = UConsts.S_OK) return iRes;
//2) Check the chain
if (UCryptoAPI.CertVerifyCertificateChainPolicy (_hPolicy, hChain, ref pPolicyParam, ref pPolicyStatus)) {
if (pPolicyStatus.dwError! = 0) {
_sError = UCConsts.S_CRYPTO_CHAIN_CHECK_ERR.Frm (pPolicyStatus.dwError);
return UConsts.E_CRYPTO_ERR;
}
} else {
_sError = UCConsts.S_CRYPTO_CHAIN_CHECK_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
return UConsts.S_OK;
} catch (Exception E) {
_sError = UCConsts.S_CRYPTO_CERT_VERIFY_GEN_ERR.Frm (E.Message);
return UConsts.E_GEN_EXCEPTION;
finally {
if (hChain! = IntPtr.Zero) UCryptoAPI.CertFreeCertificateChain (hChain);
}
}

 
The chain is first formed using the BuildChain method, and then it is checked. During the formation of the chain, the structure of the parameters is formed, the date of verification and the check flags:
 
Formation of the certificate chain [/b]
/**
Forms a chain of certificates for checking
* Context of the chain of sertifikat
* The review flag is
* The recall mode is
* Storage type
* the context of the certificate
* request list request timeout
* Date of verification
* Returned string with error
*
Standard error code, if UConsts.S_OK then everything is ok
* ** /
internal static int BuildChain (IntPtr _hChainEngine, IntPtr _hCert, X509RevocationMode _iRevMode,
X509RevocationFlag _iRevFlag, DateTime _rOnDate, TimeSpan _rCTLTimeOut,
ref IntPtr _hChain, ref string _sError) {
//0) Checking the presence of the certificate
if (_hCert == IntPtr.Zero) {
_sError = UCConsts.S_CRYPTO_CERT_CHAIN_ERR;
return UConsts.E_NO_CERTIFICATE;
}
//1) Parameters
CERT_CHAIN_PARA pChainParams = new CERT_CHAIN_PARA ();
pChainParams.cbSize = (uint) Marshal.SizeOf (pChainParams);
IntPtr hAppPolicy = IntPtr.Zero;
IntPtr hCertPolicy = IntPtr.Zero;
try {
//2) We form the rules of the application
pChainParams.dwUrlRetrievalTimeout = (uint) Math.Floor (_rCTLTimeOut.TotalMilliseconds);
//3) The verification time is
FILETIME pVerifyTime = new FILETIME (_rOnDate.ToFileTime ());
//4) We form the flag
uint _iFlags = MapRevocationFlags (_iRevMode, _iRevFlag);
//5) Forming the chain
if (! UCryptoAPI.CertGetCertificateChain (_hChainEngine, _hCert, ref pVerifyTime,
IntPtr.Zero, ref pChainParams, _iFlags,
IntPtr.Zero, ref_hChain)) {
_sError = UCConsts.S_CRYPTO_CHAIN_BUILD_ERR.Frm (Marshal.GetLastWin32Error ());
return UConsts.E_CRYPTO_ERR;
}
} catch (Exception E) {
_sError = UCConsts.S_CRYPTO_CHAIN_GEN_ERR.Frm (E.Message);
return UConsts.E_GEN_EXCEPTION;
finally {
Marshal.FreeHGlobal (hAppPolicy);
Marshal.FreeHGlobal (hCertPolicy);
}
return UConsts.S_OK;
}

 
This is a much simplified version of the formation of the chain in comparison with how it is formed by Microsoft. The hCertPolicy and hAppPolicy structures can be populated with OIDs that map the rights to the actions that are required in the certificate being scanned. But in the example, we will assume that we do not check them.
 
You can also add an additional certificate store (for example, extracted from the signature) to the chain building parameters.
 
Method MapRevocationFlags - You can take it directly from the .Net source. without changes-it just generates uint over the set of flags to be transmitted.
 

Conclusion


 
A set of implemented methods of working with cryptography was subjected to stress testing according to the scheme of the full work cycle:
 
 
waiting 10 ms;
 
Extract the certificate;
 
signing byte[]{? ? ? ? 5};
 
verification of the received signature;
 
extraction of signature parameters;
 
encryption byte[]{? ? ? ? 5};
 
decryption of received data;
 
 
This cycle was launched in Windows and Linux in the 1st, 10th and 50th threads to test the operation in Linux in several threads at once. The application in Linux worked stably for some time in a multi-threaded mode (and the more threads, the less time), and then "got up" tightly. This indicates that there is a deadlock in the library (if access to threads with shared access is broken, the Access Violation usually falls).
 
For this reason, to ensure stability of work, all methods of the UCryptoAPI class should be framed by the critical section. To do this, add the field fpCPSection of object type, then add the following construction to each call:
 
private static object fpCPSection = new object ();
/**
Closes the message
* The pointer to the message
* ** /
internal static bool CryptMsgClose (IntPtr _hCryptMsg) {
lock (pCPSection) {
if (fIsLinux)
return LCryptoAPI.CryptMsgClose (_hCryptMsg);
else
return WCryptoAPI.CryptMsgClose (_hCryptMsg);
}
}
/**
Critical section for working with CryptoPro
** /
public static object pCPSection {
get {return fpCPSection;}
}

 
This slows down the work, so those wishing can frame the critical section only by calling the Linux option.
 
Load testing also showed a memory leak in the mono when accessing the Issuer and Subject certificate fields. The leak probably occurs when you try to mono to form the X500DistinguishedName classes for the signer and the publisher. Fortunately, mono found this process rather resource-intensive (or they know about the leak), so they provided caching of the result of this formation to the internal fields of the certificate (impl.issuerName and impl.subjectName). Therefore, this leakage is treated by direct recording through reflection (reflection) in these fields of instances of class X500DistinguishedName, formed on the basis of values ​​from the structure of the CERT_CONTEXT certificate.
 

References


 
 
Documentation CryptoPro CAPILite
 
resource c declaration of standard exported functions in C #
 
.Net source files:
 
 
class CAPIBase
 
class X509Certificate2
 
class SignedCMS
 
class SignerInfo
 
 
 
the source mono:
 
 
class X509Certificate2
 
class X509CertificateImplBtls
 
 
 
+ 0 -

Add comment