Connecting CryptoPro to 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 enhanced EDS, so one of the tasks assigned to us was to check the performance of GOST certificates from CryptoPro in mono. CryptoPro itself has already implemented CSP for Linux for quite some time , but the very first attempt to use showed that Mono's native cryptography classes (similar to those in the base .Net - X509Store, X509Certificate2, etc.) do not only work with GOST keys, they even do not see them in their vaults. Because of this, work with cryptography had to be connected directly through the CryptoPro libraries.
Certificate installation
Before you implement the code, you must install the certificate and make sure that it works normally.
ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ ΠΡΠΈΠΏΡΠΎΠΡΠΎ CSP Π²Π΅ΡΡΠΈΠΈ 3.9 Π±ΡΠ» ΡΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½ Π² Centos 7 Π² ΠΏΠ°ΠΏΠΊΡ /opt/cprocsp. ΠΠ»Ρ ΡΠΎΠ³ΠΎ, ΡΡΠΎΠ±Ρ Π½Π΅ Π±ΡΠ»ΠΎ ΠΊΠΎΠ½ΡΠ»ΠΈΠΊΡΠΎΠ² ΠΌΠ΅ΠΆΠ΄Ρ ΡΡΠΈΠ»ΠΈΡΠ°ΠΌΠΈ mono ΠΈ ΠΡΠΈΠΏΡΠΎΠΡΠΎ, ΠΈΠΌΠ΅ΡΡΠΈΡ ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²ΡΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΡ (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, certmgr), Π² ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ Π½Π΅ ΡΡΠ°Π»ΠΈ Π²Π½ΠΎΡΠΈΡΡ ΠΏΡΡΡ Π΄ΠΎ ΠΏΠ°ΠΏΠΊΠΈ ΠΈ Π²ΡΠ΅ ΡΡΠΈΠ»ΠΈΡΡ Π²ΡΠ·ΡΠ²Π°Π»ΠΈΡΡ ΠΏΠΎ ΠΏΠΎΠ»Π½ΠΎΠΌΡ ΠΏΡΡΠΈ.
ΠΠ»Ρ Π½Π°ΡΠ°Π»Π° ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅ΠΌ ΡΠΏΠΈΡΠΎΠΊ ΡΡΠΈΡΡΠ²Π°ΡΠ΅Π»Π΅ΠΉ:/opt/cprocsp/bin/amd64/csptest -enum -info -type PP_ENUMREADERS | iconv -f cp1251
ΠΡΠ»ΠΈ ΡΡΠ΅Π΄ΠΈ ΡΠΏΠΈΡΠΊΠ° Π½Π΅Ρ ΡΡΠΈΡΡΠ²Π°ΡΠ΅Π»Ρ Ρ ΠΏΠ°ΠΏΠΊΠΈ Π½Π° Π΄ΠΈΡΠΊΠ΅ (HDIMAGE) ΡΡΠ°Π²ΠΈΠΌ Π΅Π³ΠΎ:/opt/cprocsp/sbin/amd64/cpconfig -hardware reader -add HDIMAGE store
ΠΠΎΡΠ»Π΅ ΡΠ΅Π³ΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΎΠ·Π΄Π°Π²Π°ΡΡ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΡ Π²ΠΈΠ΄Π° '\\.\HDIMAGE\{ΠΈΠΌΡ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ°}' ΠΏΡΡΠ΅ΠΌ Π»ΠΈΠ±ΠΎ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ Π½ΠΎΠ²ΠΎΠ³ΠΎ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ° Ρ ΠΊΠ»ΡΡΠ°ΠΌΠΈ:/opt/cprocsp/bin/amd64/csptest -keyset -provtype 75 -newkeyset -cont '\\.\HDIMAGE\test'
Π»ΠΈΠ±ΠΎ ΡΠΎΡΠΌΠΈΡΡΡ ΠΏΠ°ΠΏΠΊΡ /var/opt/cprocsp/keys/root/{ΠΈΠΌΡ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ°}.000, Π² ΠΊΠΎΡΠΎΡΠΎΠΉ ΡΠ°ΡΠΏΠΎΠ»Π°Π³Π°Π΅ΡΡΡ ΡΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ Π½Π°Π±ΠΎΡ ΡΠ°ΠΉΠ»ΠΎΠ² ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ° ΠΡΠΈΠΏΡΠΎΠΡΠΎ (*.key, *.mask, ΠΈ ΠΏΡΠΎΡ.).
ΠΠΎΡΠ»Π΅ ΡΡΠΎΠ³ΠΎ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°Ρ ΠΈΠ· ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ° ΠΌΠΎΠΆΠ½ΠΎ ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ Π² Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠΎΠ²:/opt/cprocsp/bin/amd64/certmgr -inst mMy -cont '\\.\HDIMAGE\{ΠΈΠΌΡ ΠΊΠΎΠ½ΡΠ΅ΠΉΠ½Π΅ΡΠ°}'
Π£ΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½Π½ΡΠΉ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°Ρ ΠΌΠΎΠΆΠ½ΠΎ ΡΠ²ΠΈΠ΄Π΅ΡΡ Ρ ΠΏΠΎΠΌΠΎΡΡΡ ΡΠ»Π΅Π΄ΡΡΡΠ΅ΠΉ ΠΊΠΎΠΌΠ°Π½Π΄Ρ:/opt/cprocsp/bin/amd64/certmgr -list mMy
Π Π°Π±ΠΎΡΡ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠ° ΠΌΠΎΠΆΠ½ΠΎ ΠΏΡΠΎΠ²Π΅ΡΠΈΡΡ ΡΠ»Π΅Π΄ΡΡΡΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ:/opt/cprocsp/bin/amd64/cryptcp β sign -norev -thumbprint {ΠΎΡΠΏΠ΅ΡΠ°ΡΠΎΠΊ} {ΡΠ°ΠΉΠ»} {ΡΠ°ΠΉΠ» ΠΏΠΎΠ΄ΠΏΠΈΡΠΈ}/opt/cprocsp/bin/amd64/cryptcp β verify -norev {ΡΠ°ΠΉΠ» ΠΏΠΎΠ΄ΠΏΠΈΡΠΈ}
ΠΡΠ»ΠΈ Ρ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠΎΠΌ Π²ΡΠ΅ Π½ΠΎΡΠΌΠ°Π»ΡΠ½ΠΎ, ΡΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅Ρ ΠΎΠ΄ΠΈΡΡ ΠΊ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΡ Π² ΠΊΠΎΠ΄Π΅.
Connection in code
Despite the process of porting to Linux, the system had to continue to function in the Windows environment, therefore, externally, work with cryptography had to be carried out through general methods of the form βbyte [] SignData (byte [] _arData, X509Certificate2 _pCert)β, which should work the same way in Linux and in Windows.
The analysis of cryptography library methods was successful, since CryptoPro implemented the library βlibcapi20.soβ, which completely mimics the standard Windows encryption libraries - βcrypt32.dllβ and βadvapi32.dllβ. Perhaps, of course, not entirely, but all the necessary methods for working with cryptography are available there, and almost everyone works.
Therefore, we form two static classes βWCryptoAPIβ and βLCryptoAPIβ each of which will import the necessary set of methods as follows:
[DllImport(LIBCAPI20, SetLastError = true)]
internalstaticexternboolCertCloseStore(IntPtr _hCertStore, uint _iFlags);
The connection syntax for each of the methods can either be formed independently, or you can use the pinvoke website , or copy it from the .Net sources (the CAPISafe class ). From the same module, one can draw constants and structures related to cryptography, the presence of which 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 the two classes:
/**<summary>ΠΠ°ΠΊΡΡΡΡ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅</summary>
* <param name="_iFlags">Π€Π»Π°Π³ΠΈ (Π½ΡΠΆΠ½ΠΎ ΡΡΠ°Π²ΠΈΡΡ 0)</param>
* <param name="_hCertStore">Π‘ΡΡΠ»ΠΊΠ° Π½Π° Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠΎΠ²</param>
* <returns>Π€Π»Π°Π³ ΡΡΠΏΠ΅ΡΠ½ΠΎΡΡΠΈ Π·Π°ΠΊΡΡΡΠΈΡ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ°</returns>
* **/internalstaticboolCertCloseStore(IntPtr _hCertStore, uint _iFlags) {
if (fIsLinux)
return LCryptoAPI.CertCloseStore(_hCertStore, _iFlags);
elsereturn WCryptoAPI.CertCloseStore(_hCertStore, _iFlags);
}
/**<summary>ΠΠ°Ρ
ΠΎΠ΄ΠΈΠΌΡΡ Π² Π»ΠΈΠ½ΡΠΊΡΠ΅</summary>**/publicstaticbool fIsLinux {
get {
int iPlatform = (int) Environment.OSVersion.Platform;
return (iPlatform == 4) || (iPlatform == 6) || (iPlatform == 128);
}
}
Thus, using the methods of the UCryptoAPI class, you can implement almost a single code for both systems.
Certificate search
Working with cryptography usually begins with a certificate search, for this crypt32.dll has two methods CertOpenStore (opens the specified certificate store) and simple CertOpenSystemStore (opens the user's personal certificates). Due to the fact that working with certificates is not limited to personal user certificates, we connect the first one:
/**<summary>ΠΠΎΠΈΡΠΊ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠ° (ΠΏΠ΅ΡΠ²ΠΎΠ³ΠΎ ΡΠ΄ΠΎΠ²Π»Π΅ΡΠ²ΠΎΡΡΡΡΠ΅Π³ΠΎ ΠΊΡΠΈΡΠ΅ΡΠΈΡΠΌ ΠΏΠΎΠΈΡΠΊΠ°)</summary>
* <param name="_pFindType">Π’ΠΈΠΏ ΠΏΠΎΠΈΡΠΊΠ°</param>
* <param name="_pFindValue">ΠΠ½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎΠΈΡΠΊΠ°</param>
* <param name="_pLocation">ΠΠ΅ΡΡΠΎ </param>
* <param name="_pName">ΠΠΌΡ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ°</param>
* <param name="_pCert">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΡΠΉ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°Ρ</param>
* <param name="_sError">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <param name="_fVerify">ΠΡΠΎΠ²Π΅ΡΠΈΡΡ ΡΠ΅ΡΡΡΠΈΠΈΠΊΠ°Ρ</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΡΠΉ ΠΊΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ UConsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* **/publicstaticintFindCertificateCP(string _pFindValue, out X509Certificate2 _pCert, refstring _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) ΠΡΠΊΡΡΠ²Π°Π΅ΠΌ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅
hSysStore = UCryptoAPI.CertOpenStore(UCConsts.AR_CERT_STORE_PROV_SYSTEM[fIsLinux.ToByte()],
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) Π€ΠΎΡΠΌΠΈΡΡΠ΅ΠΌ Π΄Π°Π½Π½ΡΠ΅ Π² ΠΏΠ°ΠΊΠ΅ΡΠ΅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) ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ
IntPtr hPrev = IntPtr.Zero;
do {
hCert = UCryptoAPI.CertFindCertificateInStore(hSysStore,
UCConsts.PKCS_7_OR_X509_ASN_ENCODING, 0,
UCConsts.AR_CRYPT_FIND_TYPE[(int)_pFindType, fIsLinux.ToByte()],
hFull.AddrOfPinnedObject(), hPrev);
// 2.1) ΠΡΠ²ΠΎΠ±ΠΎΠΆΠ΄Π°Π΅ΠΌ ΠΏΡΠ΅Π΄ΡΠ΄ΡΡΠΈΠΉif(hPrev != IntPtr.Zero) UCryptoAPI.CertFreeCertificateContext(hPrev);
// 2.2) ΠΠΎΠ½ΡΠΈΠ»ΠΈΡΡ Π² ΡΠΏΠΈΡΠΊΠ΅if(hCert == IntPtr.Zero) return UConsts.E_NO_CERTIFICATE;
// 2.3) ΠΠ°ΡΠ»ΠΈ ΠΈ Π²Π°Π»ΠΈΠ΄Π΅Π½
X509Certificate2 pCert = new ISDP_X509Cert(hCert);
if (!_fVerify || pCert.ISDPVerify()) {
hCert = IntPtr.Zero;
_pCert = pCert;
return UConsts.S_OK;
}
hPrev = hCert;
// Π§ΡΠΎΠ±Ρ Π½Π΅ ΠΎΡΠΈΡΡΠΈΠ»ΠΎ
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 {
// ΠΡΠΈΡΠ°Π΅ΠΌ ΡΡΡΠ»ΠΊΠΈ ΠΈ Π·Π°ΠΊΡΡΠ²Π°Π΅ΠΌ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ΅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 the repository;
- formation of the data structure for which we are looking for;
- certificate search;
- if required, certificate verification (described in a separate section);
- closing the repository and freeing the structure from point 2 (since everywhere there is work with unmanaged memory. Net will not do anything for us to clean up);
There are a few subtle points in the search for certificates.
CryptoPro on Linux works with ANSI strings, and on Windows with UTF8, therefore:
- when connecting the storage discovery method in Linux, the storage code parameter must explicitly specify the marshaling type [In, MarshalAs (UnmanagedType.LPStr)];
- passing a string to search for (for example, by the name of Subject) must be converted to a set of bytes in various encodings;
- for all encryption constants that have a variation on the type of string (for example, CERT_FIND_SUBJECT_STR_A and CERT_FIND_SUBJECT_STR_W) in Windows you must select * _W, and in Linux * _A;
The MapX509StoreFlags method can be taken directly from Microsoft sources without changes, it simply generates a final mask based on the .Net flags.
The value of the search depends on the type of search (refer to MSDN for CertFindCertificateInStore ), in the example there are two most frequently used options - for the string format (names Subject, Issuer, etc.) and binary (fingerprint, serial number).
The process of creating a certificate from IntPtr on Windows and on 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 steps:
X509Certificate2(new X509Certificate(hCert));In the future, we will need access to hCert to work, and it would be necessary to save it in the certificate object. In Windows, you can later get it out of the Handle property, but Linux converts the CERT_CONTEXT structure, which is referenced by hCert, into a reference to the x509_st structure (OpenSSL), and it is this that is written into the Handle. Therefore, it is worth creating an inheritor from X509Certificate2 (ISDP_X509Cert in the example), which will save in a separate field hCert in both systems.
Do not forget that this is a link to the area of ββunmanaged memory and it should be released after the end of the work. Because in .Net 4.5 X509Certificate2 is not Disposable - cleaning with the CertFreeCertificateContext method should be done in the destructor.
Signature generation
When working with GOST certificates, almost always used are detached signatures with one signer. In order to create such a signature, a fairly simple block of code is required:
/**<summary> ΠΠΎΠ΄ΠΏΠΈΡΡΠ²Π°Π΅Ρ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ</summary>
* <param name="_arData">ΠΠ°Π½Π½ΡΠ΅ Π΄Π»Ρ ΠΏΠΎΠ΄ΠΏΠΈΡΠ°Π½ΠΈΡ</param>
* <param name="_pCert">Π‘Π΅ΡΡΠΈΡΠΈΠΊΠ°Ρ</param>
* <param name="_sError">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <param name="_arRes">ΠΠΎΠ΄ΠΏΠΈΡΡ ΡΠ΅ΡΡΡΠΈΠΈΠΊΠ°ΡΠ°</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΡΠΉ ΠΊΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ UConsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* **/publicstaticintSignDataCP(byte[] _arData, X509Certificate2 _pCert, outbyte[] _arRes, refstring _sError)
{
_arRes = newbyte[0];
// 0) Π€ΠΎΡΠΌΠΈΡΡΠ΅ΠΌ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΡ
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, 0, pGlobData, _arData.Length);
uint iLen = 50000;
byte[] arRes = newbyte[iLen];
// 1) Π€ΠΎΡΠΌΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΠΈif (!UCryptoAPI.CryptSignMessage(ref pParams, true, 1, new IntPtr[1] { pGlobData },
newuint[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);
}
}
During the work of the method, a structure with parameters is formed and the signing method is called. The structure of the parameters can allow to save certificates for the formation of a complete chain (fields cMsgCert and rgpMsgCert, the first one stores the number of certificates, the second list of references to the structures of these certificates).
The method of signing can receive one or several documents for simultaneous signing by one signature. This, by the way, does not contradict 63 FZ and is very convenient, since the user is unlikely to be pleased to have to press the βsignβ button several times.
The main oddity of this method is that it does not work in the two-call mode, typical of most library methods working with large memory blocks (the first with null gives the required length of the buffer, the second fills the buffer). Therefore, it is necessary to create a large buffer, and then shorten it along the actual length.
The only serious problem is finding the OID of the hashing algorithm (Digest) used when signing - it is not explicitly in the certificate (there is only the signature algorithm itself). And if in Windows you can specify it with an empty string - it will pick up automatically, but Linux will refuse to sign if the algorithm is wrong.
But there is a trick - in the information about the signature algorithm (structure CRYPT_OID_INFO) the signature OID is stored in pszOID, and the identifier of the hashing algorithm is stored in Algid. And to convert Algid to OID is already a matter of technology:
/**<summary>ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ OID Π°Π»Π³ΠΎΡΠΈΡΠΌΠ° Ρ
ΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΡ</summary>
* <param name="_hCertHandle">Π₯ΡΠ½Π΄Π» ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠ°</param>
* <param name="_sOID">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΡΠΉ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡ OID</param>
* <param name="_sError">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ ΠΊΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ UConsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* **/internalstaticintGetHashAlgoritmOID(IntPtr _hCertHandle, outstring _sOID, refstring _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));
// ΠΠ·Π²Π»Π΅ΠΊΠ°Π΅ΠΌ AlgID// ΡΠ΅ΡΠ΅Π· UCryptoAPI.CertAlgIdToOID Π² Windows ΠΏΠ΅ΡΠ²ΡΠΉ ΡΠ°Π· ΡΠ°Π±ΠΎΡΠ°Π΅Ρ, Π²ΡΠΎΡΠΎΠΉ ΠΏΠ°Π΄Π°Π΅Ρbyte[] arData = BitConverter.GetBytes(UCryptoAPI.CertOIDToAlgId(pCertInfo.SignatureAlgorithm.pszObjId));
hData = Marshal.AllocHGlobal(arData.Length);
Marshal.Copy(arData, 0, hData, arData.Length);
// ΠΠΎΠΈΡΠΊ 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);
}
}
After carefully reading the code, you will be surprised that the algorithm identifier is obtained in a simple way (CertOIDToAlgId) and the Oid by it is complicated (CryptFindOIDInfo). It would be logical to assume the use of either both complex or both simple methods, and in Linux both options work successfully. However, in Windows, the complex option of obtaining an identifier and simply obtaining an OID is unstable, so this strange hybrid will be a stable solution.
Signature verification
The signature verification takes place in two stages, at the beginning the signature itself is verified, and then the certificate by which it was formed (chain, date of signature, etc.) is verified.
As with signing, you must specify a set of signed data, signature parameters and the signature itself:
/**<summary>Π€ΠΎΡΠΌΠΈΡΡΠ΅Ρ ΡΡΠ°Π½Π΄Π°ΡΡΠ½ΡΡ ΡΠΊΡΡΡΠΊΡΡΡΡ Π΄Π»Ρ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ ΠΏΠΎΠ΄ΠΏΠΈΡΠΈ </summary>
* <returns>Π‘ΡΡΡΠΊΡΡΡΡ</returns>
* **/internalstatic CRYPT_VERIFY_MESSAGE_PARA GetStdSignVerifyPar() {
CRYPT_VERIFY_MESSAGE_PARA pVerifyParams = 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;
}
/**<summary>ΠΡΠΎΠ²Π΅ΡΡΠ΅Ρ ΠΏΠΎΠ΄ΠΏΠΈΡΡ</summary>
* <param name="_arData">Π΄Π°Π½Π½ΡΠ΅, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ»ΠΎ ΠΏΠΎΠ΄ΠΏΠΈΡΠ°Π½Ρ</param>
* <param name="_pSign">ΠΏΠΎΠ΄ΠΏΠΈΡΡ</param>
* <param name="_pCert">ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°Ρ</param>
* <param name="_sError">Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <param name="_fVerifyOnlySign">ΠΡΠΎΠ²Π΅ΡΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΏΠΎΠ΄ΠΏΠΈΡΡ</param>
* <param name="_pRevMode">Π Π΅ΠΆΠΈΠΌ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠ°</param>
* <param name="_pRevFlag">Π€Π»Π°Π³ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ ΡΠ΅ΡΡΡΠΈΠΈΠΊΠ°ΡΠ°</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΡΠΉ ΠΊΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ UConsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* <remarks>ΠΡΠΎΠ²Π΅ΡΡΠ΅ΡΡΡ ΡΠΎΠ»ΡΠΊΠΎ ΠΏΠ΅ΡΠ²ΡΠΉ ΠΏΠΎΠ΄ΠΏΠΈΡΠ°Π½Ρ</remarks>
* **/publicstaticintCheckSignCP(byte[] _arData, byte[] _pSign, out X509Certificate2 _pCert, refstring _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, 0, pHData, _arData.Length);
CRYPT_VERIFY_MESSAGE_PARA pVerParam = UCUtils.GetStdSignVerifyPar();
// 0) ΠΡΠΎΠ²Π΅ΡΠΊΠ° ΠΏΠΎΠ΄ΠΏΠΈΡΠΈbool fRes = UCryptoAPI.CryptVerifyDetachedMessageSignature(
ref pVerParam, // ΠΠ°ΡΠ°ΠΌΠ΅ΡΡΡ ΠΏΠΎΠ΄ΡΠ²Π΅ΡΠΆΠ΄Π΅Π½ΠΈΡ0, // ΠΠ½Π΄Π΅ΠΊΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠ°Π½ΡΠ°
_pSign, // ΠΠΎΠ΄ΠΏΠΈΡΡ
_pSign.Length, // ΠΠ»ΠΈΠ½Π° ΠΏΠΎΠ΄ΠΏΠΈΡΠΈ1, // ΠΊΠΎΠ»-Π²ΠΎ ΡΠ°ΠΉΠ»ΠΎΠ² Π½Π° ΠΏΠΎΠ΄ΠΏΠΈΡΡnew IntPtr[1] { pHData }, // ΠΏΠΎΠ΄ΠΏΠΈΡΠ°Π½Π½ΡΠ΅ ΡΠ°ΠΉΠ»Ρnewint[1] { _arData.Length }, // ΠΠ»ΠΈΠ½Ρ ΠΏΠΎΠ΄ΠΏΠΈΡΠ°Π½Π½ΡΡ
ΡΠ°ΠΉΠ»ΠΎΠ²
pCertContext.AddrOfPinnedObject());// Π‘ΡΡΠ»ΠΊΠ° Π½Π° ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°Ρif (!fRes) {
_sError = UCConsts.S_SIGN_CHECK_ERR.Frm(Marshal.GetLastWin32Error().ToString("X"));
return UConsts.E_CRYPTO_ERR;
}
// 1) ΠΠ·Π²Π»Π΅ΡΠ΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΠΈΠΈΠΊΠ°ΡΠ°
_pCert = new ISDP_X509Cert((IntPtr)pCertContext.Target);
if (_pCert == null) {
_sError = UCConsts.S_SIGN_CHECK_CERT_ERR;
return UConsts.E_CRYPTO_ERR;
}
// 2) ΠΡΠΎΠ²Π΅ΡΠΊΠ° ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠ°if (!_fVerifyOnlySign) {
List<DateTime> pDates;
// 2.1) ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ ΡΠΏΠΈΡΠΎΠΊ Π΄Π°Ρint iRes = GetSignDateTimeCP(_pSign, out pDates, ref _sError);
// 2.2) ΠΠ΅ΡΠΈΡΠΈΡΠΈΡΡΠ΅ΠΌ ΠΏΠ΅ΡΠ²ΡΠΉ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°Ρ
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 moved to a separate method (GetStdSignVerifyPar). After that, the signature itself is checked and the first signatory is retrieved (for good it would be necessary to extract everyone, but a signature containing several signatories is still exotic).
After extracting the certificate, the signer will convert it to our class and check it (if specified in the method parameters). For verification, the date of signing of the first signatory is used (see the section on extracting information from the signature, and the section on checking the certificate).
Extracting signature information
Often in systems working with cryptography, a printed signature is required. 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 provide a printed representation with it. In .Net there is such a class - SignedCms, however, its analogue in mono with the signatures of CrytoPro, firstly refuses to work, secondly it contains the sealed modifier and in the third almost all of its properties are closed for writing, so you will have to create your own analogue.
The signature itself contains two main elements - a list of certificates and a list of signatories. The list of certificates can be empty, and it can contain all certificates for verification, including complete chains. The list of signatories indicates the number of real signatures. The connection between them is carried out by serial number and publisher (Issuer). Theoretically, in one signature there can be two certificates from different publishers with the same serial number, but in practice this can be neglected and searched only by serial number.
The signature is read as follows:
/**<summary>Π Π°ΡΡΠΈΡΡΠΎΠ²Π°ΡΡ</summary>
* <param name="_arSign">ΠΠΎΠ΄ΠΏΠΈΡΡ</param>
* <param name="_sError">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ ΠΊΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ UConsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* **/publicintDecode(byte[] _arSign, refstring _sError) {
IntPtr hMsg = IntPtr.Zero;
// 0) Π€ΠΎΡΠΌΠΈΡΡΠ΅ΠΌ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ try {
hMsg = UCryptoAPI.CryptMsgOpenToDecode(UCConsts.PKCS_7_OR_X509_ASN_ENCODING, UCConsts.CMSG_DETACHED_FLAG,
0, 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) ΠΠ½ΠΎΡΠΈΠΌ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅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) ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ ΡΠΈΠΏ (PKCS7 SignedData)uint iMessType = UCUtils.GetCryptMsgParam<uint>(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) Π€ΠΎΡΠΌΠΈΡΡΠ΅ΠΌ ΡΠΏΠΈΡΠΎΠΊ ΡΠ΅ΡΡΡΠΈΠΊΠ°ΡΠΎΠ²
fpCertificates = UCUtils.GetSignCertificates(hMsg);
// 4) Π‘ΠΏΠΈΡΠΎΠΊ ΠΏΠΎΠ΄ΠΏΠΈΡΠ°Π½ΡΠΎΠ² uint iSignerCount = UCUtils.GetCryptMsgParam<uint>(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 signature analysis takes place in several stages, first the message structure is formed (CryptMsgOpenToDecode), then the actual signature data is entered into it (CryptMsgUpdate). It remains to verify that this is a real signature and first obtain a list of certificates, and then a list of signatories. The list of certificates is retrieved sequentially:
/**<summary>ΠΠΎΠ»ΡΡΠΈΡΡ ΠΊΠΎΠ»Π»Π΅ΠΊΡΠΈΡ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠΎΠ² ΠΏΠΎ ΠΏΠΎΠ΄ΠΏΠΈΡΠΈ </summary>
* <param name="_hMsg">Handle ΠΏΠΎΠ΄ΠΏΠΈΡΠΈ</param>
* <returns>ΠΠΎΠ»Π»Π΅ΠΊΡΠΈΡ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠΎΠ²</returns>
* **/internalstatic X509Certificate2Collection GetSignCertificates(IntPtr _hMsg) {
X509Certificate2Collection certificates = new X509Certificate2Collection();
uint iCnt = GetCryptMsgParam<uint>(_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(hCert);
}
}
return certificates;
}
First, the number of certificates from the CMSG_CERT_COUNT_PARAM parameter is determined, and then information about each certificate is sequentially extracted. Completes the process of creating the formation of the context of the certificate and on the basis of the certificate itself.
Retrieving signatory data is more difficult. They contain an indication of the certificate and a list of signature parameters (for example, the date of signing). The process of extracting data is as follows:
/**<summary>Π Π°ΡΠΏΠ°ΡΡΠΈΡΡ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ ΠΈΠ· ΠΏΠΎΠ΄ΠΏΠΈΡΠΈ</summary>
* <param name="_hMsg">Handler ΠΏΠΎΠ΄ΠΏΠΈΡΠΈ</param>
* <param name="_iIndex">ΠΠ½Π΄Π΅ΠΊΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠ°Π½ΡΠ°</param>
* <param name="_pSignedCms">Π‘ΡΡΡΠΊΡΡΡΠ° ΠΏΠΎΠ΄ΠΏΠΈΡΠΈ</param>
* <param name="_sError">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ ΠΊΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ UConsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* **/publicintDecode(IntPtr _hMsg, int _iIndex, ISDPSignedCms _pSignedCms, refstring _sError) {
// 1) ΠΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅ΠΌ Π΄Π»ΠΈΠ½Ρuint iLen = 0;
// 2) Π‘ΡΠΈΡΡΠ²Π°Π΅ΠΌ
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) ΠΡΠ΅ΠΌ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°Ρbyte[] arSerial = newbyte[pSignerInfo.SerialNumber.cbData];
Marshal.Copy(pSignerInfo.SerialNumber.pbData, arSerial, 0, 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);
}
}
During it, the size of the subscriber's structure is first determined, and then the CMSG_SIGNER_INFO structure is also retrieved. It is easy to find the serial number of the certificate in it and to find the necessary certificate in it in the previously extracted list. Please note that the serial number is in reverse order.
After extracting the certificate, it is necessary to determine the signature parameters, the most important of which is the date of signing (even if itβs not verified by the date stamp server, it is very important to display).
/**<summary>ΠΠΎΠ»ΡΡΠΈΡΡ ΡΠΏΠΈΡΠΎΠΊ Π°ΡΡΠΈΠ±ΡΡΠΎΠ² ΠΏΠΎΠ΄ΠΏΠΈΡΠΈ</summary>
* <param name="_pAttrs">Π‘ΡΡΡΠΊΡΡΡΠ° Π°ΡΡΠΈΠ±ΡΡΠΎΠ²</param>
* <returns>ΠΠΎΠ»Π»Π΅ΠΊΡΠΈΡ Π°ΡΡΡΠΈΠ±ΡΡΠΎΠ²</returns>
* **/internalstatic 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 reference of the form Oid - a list of values ββ(in fact, it is a disassembled ASN.1 structure). Having passed on the first level we form the nested list:
/**<summary>Π‘ΡΠΎΡΠΌΠΈΡΠΎΠ²Π°ΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ ΠΊΠΎΠ»Π»Π΅ΠΊΡΠΈΠΈ Π½ΡΠΆΠ½ΠΎΠ³ΠΎ ΠΊΠ»Π°ΡΡΠ° ΠΏΠΎ ΠΈΠΌΠ΅Π½ΠΈ</summary>
* <param name="_sName">ΠΠΌΡ</param>
* <returns>Π‘ΠΎΠ·Π΄Π°Π½Π½ΡΠΉ ΠΎΠ±ΡΠ΅ΠΊΡ</returns>
* **/internalstatic Pkcs9AttributeObject Pkcs9AttributeFromOID(string _sName) {
switch (_sName) {
case UCConsts.S_SIGN_DATE_OID : returnnew Pkcs9SigningTime();
// case UConsts.S_CONTENT_TYPE_OID : return new Pkcs9ContentType(); ->> Π² Mono ΠΏΠ°Π΄Π°Π΅Ρ // case UConsts.S_MESS_DIGEST_OID : return new Pkcs9MessageDigest();default: returnnew Pkcs9AttributeObject();
}
}
/**<summary>Π€ΠΎΡΠΌΠΈΡΡΠ΅Ρ ΠΊΠΎΠ»Π»Π΅ΠΊΡΡΠΈΡ ASN</summary>
* <param name="_pAttr">Π‘ΡΡΡΠΊΡΡΡΠ°</param>
* <returns>ΠΠΎΠ»Π»Π΅ΠΊΡΠΈΡ</returns>
* **/internalstatic 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);
attribute = customAttribute;
}
pRes.Add(attribute);
}
}
return pRes;
}
A key feature of this process is the correct selection of the heir to Pkcs9AttributeObject. The problem is that the standard way of creating in mono does not work and you have to form the choice of a class right in the code. In addition, of the main types of Mono at the moment allows you to generate only the date.
By wrapping the above methods in two classes - information about the signature and information about the signer - we get an analogue of SignedCms, from which we extract data when forming the printed form.
Encryption
The encryption process is in many ways similar to the signing process, it is quite simple, and the main problem is also in the definition of the algorithm. Unlike a signature, encryption is most often used concatenated to the address of one or several recipients (for example, they also encrypt themselves to the address so that they can read the message with their own key).
/**<summary>ΠΠ°ΡΠΈΡΡΠΎΠ²Π°Π½Π½ΡΠ΅ Π΄Π°Π½Π½ΡΠ΅</summary>
* <param name="_arInput">ΠΠ°Π½Π½ΡΠ΅ Π΄Π»Ρ ΡΠ°ΡΡΠΈΡΡΠΎΠ²ΠΊΠΈ</param>
* <param name="_pCert">Π‘Π΅ΡΡΠΈΡΠΈΠΊΠ°Ρ</param>
* <param name="_arRes">Π Π΅Π·ΡΠ»ΡΡΠ°Ρ</param>
* <param name="_sError">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ ΠΊΠΎΠ΄ Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ, Π΅ΡΠ»ΠΈ UConsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* **/publicstaticintEncryptDataCP(byte[] _arInput, X509Certificate2 _pCert, outbyte[] _arRes, refstring _sError) {
_arRes = newbyte[0];
try {
// 0) ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠ²
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) ΠΠ·Π²Π»Π΅ΡΠ΅Π½ΠΈΠ΅ Π΄Π»ΠΈΠ½Ρint iLen = 0;
if (!UCryptoAPI.CryptEncryptMessage(ref pParams, 1, 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) ΠΡΠΎΡΠΎΠΉ Π·Π°ΠΏΡΠΎΡ ΡΠ΅Π°Π»ΡΠ½ΠΎΠ΅ ΡΠΈΡΡΠΎΠ²Π°Π½ΠΈΠ΅
_arRes = newbyte[iLen];
if (!UCryptoAPI.CryptEncryptMessage(ref pParams, 1, 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 takes place 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.
In the example, it is encrypted to the address of one addressee, but by adding additional certificates to the array and setting the total number to the method parameters, you can increase the number of recipients.
But the problem again with the algorithm. The certificate has neither it, nor even indirect values ββby 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:
/**<summary>ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ OID Π°Π»Π³ΠΎΡΠΈΡΠΌΠ° ΡΠΈΡΡΠΎΠ²Π°Π½ΠΈΡ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΡ</summary>
* <param name="_hCertHandle">Π₯ΡΠ½Π΄Π» ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠ°</param>
* <param name="_sOID">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΡΠΉ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡ OID</param>
* <param name="_sError">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ ΠΊΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ UConsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* **/internalstaticintGetEncodeAlgoritmOID(IntPtr _hCertHandle, outstring _sOID, refstring _sError) {
bool fNeedRelease = false;
_sOID = "";
uint iKeySpec = 0;
IntPtr hCrypto = IntPtr.Zero;
try {
// 0) ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ ΠΏΡΠΎΠ²Π°ΠΉΠ΄Π΅ΡΠ°if (!UCryptoAPI.CryptAcquireCertificatePrivateKey(_hCertHandle, 0, 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 = newbyte[1000];
uint iFlag = 1; // ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ// 1) ΠΡΠΎΡ
ΠΎΠ΄ΠΈΠΌ Π² ΡΠΈΠΊΠ»Π΅ ΠΏΠΎ Π°Π»Π³ΠΎΡΠΈΡΠΌΠ°ΠΌwhile (UCryptoAPI.CryptGetProvParam(hCrypto, UCConsts.PP_ENUMALGS, arData, ref iLen, iFlag)){
iFlag = 2; // Π‘Π»Π΅Π΄ΡΡΡΠΈΠΉ
PROV_ENUMALGS pInfo = ConvertBytesToStruct<PROV_ENUMALGS>(arData);
// 2) ΠΡΡΠ°Π΅ΠΌΡΡ ΠΏΠΎΠ»ΡΡΠΈΡΡ OID Π² ΡΠ°ΠΌΠΊΠ°Ρ
Π°Π»Π³ΠΎΡΠΈΠΌΡΠΎΠ² ΡΠΈΡΡΠΎΠ²Π°Π½ΠΈΡbyte[] arDataAlg = BitConverter.GetBytes(pInfo.aiAlgid);
IntPtr hDataAlg = Marshal.AllocHGlobal(arDataAlg.Length);
try {
Marshal.Copy(arDataAlg, 0, hDataAlg, arDataAlg.Length);
IntPtr hHashAlgInfo2 = UCryptoAPI.CryptFindOIDInfo(UCConsts.CRYPT_OID_INFO_ALGID_KEY,
hDataAlg,
UCConsts.CRYPT_ENCRYPT_ALG_OID_GROUP_ID);
// 2.1) ΠΠ°ΡΠ»ΠΈ - Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌif (hHashAlgInfo2 != IntPtr.Zero) {
CRYPT_OID_INFO pHashAlgInfo2 = (CRYPT_OID_INFO)Marshal.PtrToStructure(hHashAlgInfo2,
typeof(CRYPT_OID_INFO));
_sOID = pHashAlgInfo2.pszOID ;
return UConsts.S_OK;
}
} finally {
Marshal.FreeHGlobal(hDataAlg);
}
}
// 3) ΠΠ΅ Π½Π°ΡΠ»ΠΈ - ΠΎΡΠΈΠ±ΠΊΠ°
_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);
}
}
In the example, the context of the private key is retrieved and it is searched by algorithms. But this list contains all the algorithms (key exchange, hashing, signature, encryption, etc.), so only the encryption algorithms need to be filtered. We are trying to extract information for everyone by limiting it 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 there are more than one such algorithms, you can also filter by size (based on the size of the hashing algorithm).
Decryption
In order to decrypt data, the local machine must have one of the recipients in the personal certificates of the user or computer. And the private key must be tied to it. The process follows the already familiar scenario β the list of parameters, the determination of length, and the decryption process itself:
/**<summary>ΠΠ΅ΡΠΈΡΡΠΎΠ²ΡΠ²Π°Π΅Ρ Π΄Π°Π½Π½ΡΠ΅</summary>
* <param name="_arInput">ΠΠ°Π½Π½ΡΠ΅ Π΄Π»Ρ ΡΠ°ΡΡΠΈΡΡΠΎΠ²ΠΊΠΈ</param>
* <param name="_arRes">Π Π΅Π·ΡΠ»ΡΡΠ°Ρ</param>
* <param name="_sError">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <param name="_pCert">Π‘Π΅ΡΡΠΈΡΠΈΠΊΠ°Ρ</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΠ½ΡΠΉ ΠΊΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ UCOnsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* **/publicstaticintDecryptDataCP(byte[] _arInput, out X509Certificate2 _pCert, outbyte[] _arRes, refstring _sError) {
_arRes = newbyte[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) ΠΠΎΠ΄Π³ΠΎΡΠΎΠ²ΠΊΠ° ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠ²
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) ΠΠ΅ΡΠ²ΡΠΉ Π²ΡΠ·ΠΎΠ² ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ΅ΠΌ Π΄Π»ΠΈΠ½Ρ 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) ΠΡΠΎΡΠΎΠΉ Π²ΡΠ·ΠΎΠ² Π΄Π΅ΡΠΈΡΡΡΠ΅ΠΌ
_arRes = newbyte[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 (hOutCert != IntPtr.Zero) _pCert = new ISDP_X509Cert(hOutCert);
if(_pCert != null) hOutCert = IntPtr.Zero;
// ΠΡΠ΅ ΠΎΠΊ Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌ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 indicated, from which the system will try to extract the appropriate certificate with the key. As a result of the work, the system will issue the decrypted data and the certificate that was used (in Linux, the certificate is always returned empty).
Certificate Verification
A certificate is not only a public key, but also a set of different information about its owner, about who issued it and about the set of actions that can be done with its help. Also, the certificate has a period of validity and the possibility of revocation, in case of compromise. Most often, the certificate verification implies the following:
- chain integrity (certificate of publisher, certificate of publisher of certificate of publisher, etc.);
- Root Publisher Certificate - Must be in the Trusted Root Centers Store;
- the period of validity of all certificates - the moment of use of the certificate must be within the boundaries of this period;
- Each of the certificates in the chain, except for the root one, must be absent from the list of withdrawals from its publisher (CRL);
For good, you still have to check the signature rights, but in real life it is rarely done.
As is already clear from the introduction, checking the 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, let's turn to the .Net sources for the X509Certificate2.Verify () method 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 review, time, and so on.);
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 verification method itself is small:
/**<summary>ΠΡΠΎΠ²Π΅ΡΠΈΡΡ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°Ρ</summary>
* <param name="_iRevFlag">Π€Π»Π°Π³ ΠΎΡΠ·ΡΠ²Π°</param>
* <param name="_iRevMode">Π Π΅ΠΆΠΈΠΌ ΠΎΡΠ·ΡΠ²Π°</param>
* <param name="_hPolicy">Π‘ΡΡΠ»ΠΊΠ° Π½Π° ΠΏΡΠ°Π²ΠΈΠ»Π° ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ</param>
* <param name="_hCert">ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ ΡΠ΅ΡΡΡΠΈΠΊΠ°ΡΠ°</param>
* <param name="_iCTLTimeout">ΡΠ°ΠΉΠΌΠ°ΡΡ Π·Π°ΠΏΡΠΎΡΠ° ΡΠΏΠΈΡΠΊΠ° ΠΎΡΠ·ΡΠ²Π°</param>
* <param name="_rOnDate">ΠΠ°ΡΠ° Π²Π΅ΡΠΈΡΠΈΠΊΠ°ΡΠΌΠΌ</param>
* <param name="_sError">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΡΠΉ ΠΊΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ UConsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* **/internalstaticintVerifyCertificate (IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag,
DateTime _rOnDate, TimeSpan _iCTLTimeout, IntPtr _hPolicy, refstring _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) Π€ΠΎΡΠΌΠΈΡΡΠ΅ΠΌ ΡΠ΅ΠΏΠΎΡΠΊΡ
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) ΠΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ ΡΠ΅ΠΏΠΎΡΠΊΡ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);
}
}
First, a chain is formed using the BuildChain method, and then it is checked. During the formation of the chain, the structure of the parameters, the date of verification and the check flags are formed:
/**<summary>Π€ΠΎΡΠΌΠΈΡΡΠ΅Ρ ΡΠ΅ΠΏΠΎΡΠΊΡ ΡΠ΅ΡΡΡΠΈΠΊΠΈΠ°ΡΠ° Π΄Π»Ρ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ</summary>
* <param name="_hChain">ΠΠΠ½ΡΠ΅ΠΊΡΡ ΡΠ΅ΠΏΠΎΡΠΊΠΈ ΡΠ΅ΡΡΡΠΈΠΈΠΊΠ°ΡΠΎΠ²</param>
* <param name="_iRevFlag">Π€Π»Π°Π³ ΠΎΡΠ·ΡΠ²Π°</param>
* <param name="_iRevMode">Π Π΅ΠΆΠΈΠΌ ΠΎΡΠ·ΡΠ²Π°</param>
* <param name="_hChainEngine">Π’ΠΈΠΏ Ρ
ΡΠ°Π½ΠΈΠ»ΠΈΡΠ°</param>
* <param name="_hCert">ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ ΡΠ΅ΡΡΡΠΈΠΊΠ°ΡΠ°</param>
* <param name="_rCTLTimeOut">ΡΠ°ΠΉΠΌΠ°ΡΡ Π·Π°ΠΏΡΠΎΡΠ° ΡΠΏΠΈΡΠΊΠ° ΠΎΡΠ·ΡΠ²Π°</param>
* <param name="_rOnDate">ΠΠ°ΡΠ° Π²Π΅ΡΠΈΡΠΈΠΊΠ°ΡΠΌΠΌ</param>
* <param name="_sError">ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΠ°Ρ ΡΡΡΠΎΠΊΠ° Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ</param>
* <returns>Π‘ΡΠ°Π½Π΄Π°ΡΡΡΠΉ ΠΊΠΎΠ΄ ΠΎΡΠΈΠ±ΠΊΠΈ, Π΅ΡΠ»ΠΈ UConsts.S_OK ΡΠΎ Π²ΡΠ΅ ΠΎΠΊ</returns>
* **/internalstaticintBuildChain (IntPtr _hChainEngine, IntPtr _hCert, X509RevocationMode _iRevMode,
X509RevocationFlag _iRevFlag, DateTime _rOnDate, TimeSpan _rCTLTimeOut,
ref IntPtr _hChain, refstring _sError) {
// 0) ΠΡΠΎΠ²Π΅ΡΠΊΠ° Π½Π°Π»ΠΈΡΠΈΡ ΡΠ΅ΡΡΠΈΡΠΈΠΊΠ°ΡΠ°if (_hCert == IntPtr.Zero) {
_sError = UCConsts.S_CRYPTO_CERT_CHAIN_ERR;
return UConsts.E_NO_CERTIFICATE;
}
// 1) ΠΠ°ΡΠ°ΠΌΠ΅ΡΡΡ
CERT_CHAIN_PARA pChainParams = new CERT_CHAIN_PARA();
pChainParams.cbSize = (uint) Marshal.SizeOf(pChainParams);
IntPtr hAppPolicy = IntPtr.Zero;
IntPtr hCertPolicy = IntPtr.Zero;
try {
// 2) Π€ΠΎΡΠΌΠΈΡΡΠ΅ΠΌ ΠΏΡΠ°Π²ΠΈΠ»Π° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
pChainParams.dwUrlRetrievalTimeout = (uint)Math.Floor(_rCTLTimeOut.TotalMilliseconds);
// 3) ΠΡΠ΅ΠΌΡ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΈ
FILETIME pVerifyTime = new FILETIME(_rOnDate.ToFileTime());
// 4) Π€ΠΎΡΠΌΠΈΡΡΠ΅ΠΌ ΡΠ»Π°Π³uint _iFlags = MapRevocationFlags(_iRevMode, _iRevFlag);
// 5) Π€ΠΎΡΠΌΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΡΠ΅ΠΏΠΎΡΠΊΠΈ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 greatly simplified version of chaining compared to how Microsoft forms it. The hCertPolicy and hAppPolicy structures can be filled with OIDs, which display the rights to actions that are required in the certificate being checked. But in the example, we will assume that we do not check them.
You can also add additional certificate storage (for example, extracted from the signature) to the chain building parameters.
The MapRevocationFlags method can be taken directly from .Net sources without changes β it simply forms a uint based on a set of transmitted flags.
Conclusion
A set of implemented methods for working with cryptography was subjected to stress testing according to the complete work cycle scheme:
- waiting 10 ms;
- certificate retrieval;
- signing byte [] {1, 2, 3, 4, 5};
- verification of the received signature;
- extract signature parameters;
- encryption byte [] {1, 2, 3, 4, 5};
- decrypt received data;
This cycle was launched on Windows and Linux in the 1st, 10th, and 50th threads in order to test the operation of Linux in several threads at once. An application in Linux worked stably for some time in a multi-thread mode (and the more threads, the less time), and then it βgot upβ tightly. That indicates the presence of mutual blocking (deadlock) in the library (in case of violation of work with streams associated with shared access, it usually drops with Access Violation).
For this reason, to ensure stability, all methods of the UCryptoAPI class should be framed with a critical section. To do this, we add an object type fpCPSection field, after which we add the following construction to each call:
privatestaticobject fpCPSection = newobject();
/**<summary>ΠΠ°ΠΊΡΡΠ²Π°Π΅Ρ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅</summary>
* <param name="_hCryptMsg">Π£ΠΊΠ°Π·Π°ΡΠ΅Π»Ρ Π½Π° ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠ΅</param>
* **/internalstaticboolCryptMsgClose(IntPtr _hCryptMsg) {
lock (pCPSection) {
if (fIsLinux)
return LCryptoAPI.CryptMsgClose(_hCryptMsg);
elsereturn WCryptoAPI.CryptMsgClose(_hCryptMsg);
}
}
/**<summary>ΠΡΠΈΡΠΈΡΠ΅ΡΠΊΠ°Ρ ΡΠ΅ΠΊΡΠΈΡ Π΄Π»Ρ ΡΠ°Π±ΠΎΡΡ Ρ ΠΡΠΈΠΏΡΠΎΠΡΠΎ</summary>**/publicstaticobject pCPSection {
get { return fpCPSection;}
}
This slows down the work, so those who are interested can only wrap the Linux section with a critical section.
Load testing also showed memory leaks in mono when accessing the Issuer and Subject fields of the certificate. The leak is likely to occur when mono tries to generate X500DistinguishedName classes for the signer and publisher. Fortunately, mono considered this process quite resource-intensive (or they know about the leak), therefore, provided for caching the result of this formation in the internal fields of the certificate (impl.issuerName and impl.subjectName). Therefore, this leakage is treated by direct recording through reflection (Reflection) into these fields instances of class X500DistinguishedName, formed on the basis of the values ββfrom the certificate's CERT_CONTEXT structure.
Links
- CryptoPro CAPILite documentation
- resource with the declaration of standard exported functions in C #
- source .Net:
- class CAPIBase
- class X509Certificate2
- SignedCMS class
- SignerInfo class
- mono sources:
- class X509Certificate2
- class X509CertificateImplBtls