CryptoPro mit Mono verbinden

Im Zusammenhang mit der Umstellung auf Linux wurde es notwendig, eines unserer in C # geschriebenen Serversysteme nach Mono zu portieren. Das System arbeitet mit erweiterten digitalen Signaturen. Eine der Aufgaben, mit denen wir konfrontiert waren, bestand darin, die Leistung von GOST-Zertifikaten von CryptoPro in Mono zu testen. CryptoPro selbst hat CSP für Linux schon seit einiger Zeit implementiert, aber der erste Versuch, es zu verwenden, zeigte, dass die nativen Mono-Kryptografieklassen (ähnlich denen in der Basis .Net - X509Store, X509Certificate2 usw.) nicht nur nicht mit Gastschlüsseln funktionieren, sondern sogar sehe sie nicht in ihren Gewölben. Aus diesem Grund musste die Arbeit mit Kryptografie direkt über die CryptoPro-Bibliotheken verbunden werden.



Installation des Zertifikats


Bevor Sie den Code implementieren, müssen Sie das Zertifikat installieren und sicherstellen, dass es ordnungsgemäß funktioniert.


Installation des Zertifikats

Die CryptoPro CSP-Komponente Version 3.9 wurde auf Centos 7 im Ordner / opt / cprocsp installiert. Um Konflikte zwischen den gleichnamigen Dienstprogrammen mono und CryptoPro (z. B. certmgr) zu vermeiden, wurde der Pfad zum Ordner nicht in die Umgebungsvariablen eingegeben, und alle Dienstprogramme wurden im vollständigen Pfad aufgerufen.


Zunächst definieren wir eine Liste von Lesern:
/opt/cprocsp/bin/amd64/csptest -enum -info -type PP_ENUMREADERS | iconv -f cp1251


Wenn sich kein Leser aus dem Ordner auf der Festplatte (HDIMAGE) in der Liste befindet, fügen Sie Folgendes ein:
/opt/cprocsp/sbin/amd64/cpconfig -hardware reader -add HDIMAGE store


Anschließend können Sie Container der Form '\\. \ HDIMAGE \ {Containername}' erstellen, indem Sie entweder einen neuen Container mit Schlüsseln erstellen:
/opt/cprocsp/bin/amd64/csptest -keyset -provtype 75 -newkeyset -cont '\\.\HDIMAGE\test'


oder indem Sie den Ordner / var / opt / cprocsp / keys / root / {Containername} .000 erstellen, der den Standardsatz von CryptoPro-Containerdateien (* .key, * .mask usw.) enthält.


Danach kann das Zertifikat aus dem Container im Zertifikatspeicher installiert werden:
/opt/cprocsp/bin/amd64/certmgr -inst mMy -cont '\\.\HDIMAGE\{ }'


Das installierte Zertifikat kann mit dem folgenden Befehl angezeigt werden:
/opt/cprocsp/bin/amd64/certmgr -list mMy


Der Betrieb des Zertifikats kann wie folgt überprüft werden:
/opt/cprocsp/bin/amd64/cryptcp – sign -norev -thumbprint {} {} { }
/opt/cprocsp/bin/amd64/cryptcp – verify -norev { }


Wenn mit dem Zertifikat alles in Ordnung ist, können Sie mit der Verbindung im Code fortfahren.



Verbindung im Code


Trotz des Portierungsprozesses nach Linux sollte das System weiterhin in der Windows-Umgebung funktionieren. Daher musste die Arbeit mit Kryptografie äußerlich mit allgemeinen Methoden der Form „Byte [] SignData (Byte [] _arData, X509Certificate2 _pCert)“ durchgeführt werden, die genauso funktionieren sollten unter Linux sowie unter Windows.


Die Analyse der Methoden von Kryptografiebibliotheken erwies sich als erfolgreich, da CryptoPro die Bibliothek "libcapi20.so" implementierte, die die Standardverschlüsselungsbibliotheken Windows - "crypt32.dll" und "advapi32.dll" vollständig nachahmt. Vielleicht natürlich nicht ganz, aber dort stehen alle notwendigen Methoden für die Arbeit mit Kryptographie zur Verfügung, und fast alle funktionieren.


Daher bilden wir zwei statische Klassen "WCryptoAPI" und "LCryptoAPI", von denen jede die erforderlichen Methoden wie folgt importiert:


 [DllImport(LIBCAPI20, SetLastError = true)] internal static extern bool CertCloseStore(IntPtr _hCertStore, uint _iFlags); 

Die Verbindungssyntax für jede der Methoden kann entweder unabhängig erstellt oder über die Pinvoke- Website erstellt oder aus .NET-Quellen ( CAPISafe- Klasse) kopiert werden . Aus demselben Modul können Sie Konstanten und Strukturen zeichnen, die mit der Kryptografie verbunden sind und deren Vorhandensein das Leben bei der Arbeit mit externen Bibliotheken immer erleichtert.


Und dann bilden wir die statische Klasse „UCryptoAPI“, die je nach System die Methode einer von zwei Klassen aufruft:


 /**<summary> </summary> * <param name="_iFlags"> (  0)</param> * <param name="_hCertStore">   </param> * <returns>   </returns> * **/ internal static bool CertCloseStore(IntPtr _hCertStore, uint _iFlags) { if (fIsLinux) return LCryptoAPI.CertCloseStore(_hCertStore, _iFlags); else return WCryptoAPI.CertCloseStore(_hCertStore, _iFlags); } /**<summary>  </summary>**/ public static bool fIsLinux { get { int iPlatform = (int) Environment.OSVersion.Platform; return (iPlatform == 4) || (iPlatform == 6) || (iPlatform == 128); } } 

Mit den Methoden der UCryptoAPI-Klasse können Sie somit für beide Systeme nahezu einheitlichen Code implementieren.


Zertifikatsuche


Die Arbeit mit Kryptografie beginnt normalerweise mit einer Zertifikatsuche. Dazu gibt es in crypt32.dll zwei CertOpenStore-Methoden (öffnet den angegebenen Zertifikatspeicher) und einen einfachen CertOpenSystemStore (öffnet die persönlichen Zertifikate des Benutzers). Da die Arbeit mit Zertifikaten nicht auf persönliche Benutzerzertifikate beschränkt ist, verbinden wir das erste:


Zertifikatsuche
 /**<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> * **/ 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)   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); } } 


Die Suche erfolgt in mehreren Schritten:
  1. Lageröffnung;
  2. Bildung der Datenstruktur, nach der wir suchen;
  3. Zertifikatsuche;
  4. falls erforderlich, dann Zertifikatsüberprüfung (in einem separaten Abschnitt beschrieben);
  5. Schließen des Repositorys und Freigeben der Struktur ab Punkt 2 (da überall mit nicht verwaltetem .NET-Speicher gearbeitet wird, wird nichts unternommen, um ihn zu bereinigen);

Bei der Suche nach Zertifikaten gibt es einige subtile Punkte.


CryptoPro unter Linux funktioniert mit ANSI-Zeichenfolgen und unter Windows mit UTF8:


  1. Beim Verbinden der Methode zum Öffnen des Speichers unter Linux muss die Art des Marshallings explizit angegeben werden [In, MarshalAs (UnmanagedType.LPStr)].
  2. Wenn Sie die Suchzeichenfolge übergeben (z. B. mit dem Namen Subject), muss sie in eine Reihe von Bytes mit unterschiedlichen Codierungen konvertiert werden.
  3. Für alle kryptografischen Konstanten, die je nach Zeichenfolgentyp variieren (z. B. CERT_FIND_SUBJECT_STR_A und CERT_FIND_SUBJECT_STR_W), müssen Sie unter Windows * _W und unter Linux * _A auswählen.

Die MapX509StoreFlags- Methode kann ohne Änderungen direkt aus Microsoft-Quellen übernommen werden. Sie bildet einfach eine endgültige Maske basierend auf .NET-Flags.


Der Wert, nach dem die Suche stattfindet, hängt von der Art der Suche ab (überprüfen Sie mit MSDN nach CertFindCertificateInStore ). Das Beispiel zeigt die beiden am häufigsten verwendeten Optionen - für das Zeichenfolgenformat (Namen Betreff, Aussteller usw.) und binär (Fingerabdruck, Seriennummer).


Das Erstellen eines Zertifikats aus IntPtr unter Windows und Linux ist sehr unterschiedlich. Windows erstellt das Zertifikat auf einfache Weise:

  new X509Certificate2(hCert); 


Unter Linux müssen Sie in zwei Schritten ein Zertifikat erstellen:

 X509Certificate2(new X509Certificate(hCert)); 


In Zukunft benötigen wir Zugriff auf hCert für die Arbeit, und es müsste im Zertifikatobjekt gespeichert werden. Unter Windows kann es später aus der Handle-Eigenschaft abgerufen werden. Linux konvertiert jedoch die CERT_CONTEXT-Struktur, die dem hCert-Link folgt, in einen Link zur x509_st-Struktur (OpenSSL) und registriert sie im Handle. Daher lohnt es sich, aus X509Certificate2 (im Beispiel ISDP_X509Cert) einen Erben zu erstellen, der hCert in beiden Systemen in einem separaten Feld speichert.


Vergessen Sie nicht, dass dies eine Verknüpfung zu einem Bereich mit nicht verwaltetem Speicher ist und nach Beendigung der Arbeit freigegeben werden muss. Weil in .Net 4.5 ist X509Certificate2 nicht verfügbar - die Reinigung mit der CertFreeCertificateContext-Methode muss im Destruktor durchgeführt werden.


Signaturbildung


Bei der Arbeit mit GOST-Zertifikaten werden fast immer getrennte Signaturen mit einem Unterzeichner verwendet. Um eine solche Signatur zu erstellen, ist ein ziemlich einfacher Codeblock erforderlich:


Signaturbildung
 /**<summary>  </summary> * <param name="_arData">  </param> * <param name="_pCert"></param> * <param name="_sError">   </param> * <param name="_arRes"> </param> * <returns>  ,  UConsts.S_OK   </returns> * **/ public static int SignDataCP(byte[] _arData, X509Certificate2 _pCert, out byte[] _arRes, ref string _sError) { _arRes = new byte[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 = new byte[iLen]; // 1)   if (!UCryptoAPI.CryptSignMessage(ref pParams, true, 1, 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); } } 

Während der Arbeit der Methode wird eine Struktur mit Parametern gebildet und die Signaturmethode aufgerufen. Mit der Parameterstruktur können Sie Zertifikate in der Signatur speichern, um eine vollständige Kette zu bilden (die Felder cMsgCert und rgpMsgCert, das erste speichert die Anzahl der Zertifikate, die zweite Liste der Links zu den Strukturen dieser Zertifikate).


Die Signaturmethode kann ein oder mehrere Dokumente zum gleichzeitigen Signieren mit einer Signatur empfangen. Dies widerspricht übrigens nicht dem Bundesgesetz 63 und ist sehr praktisch, da es unwahrscheinlich ist, dass sich der Benutzer über die Notwendigkeit freut, mehrmals auf die Schaltfläche „Zeichen“ zu klicken.


Die größte Kuriosität dieser Methode ist, dass sie nicht im Zwei-Aufruf-Modus funktioniert, was typisch für die meisten Bibliotheksmethoden ist, die mit großen Speicherblöcken arbeiten (die erste mit null - gibt die erforderliche Pufferlänge zurück, die zweite füllt den Puffer). Daher ist es notwendig, einen großen Puffer zu erstellen und ihn dann auf seine tatsächliche Länge zu kürzen.


Das einzige ernsthafte Problem ist die Suche nach der OID des beim Signieren verwendeten Hash-Algorithmus (Digest) - in expliziter Form nicht im Zertifikat (es gibt nur den Algorithmus der Signatur selbst). Und wenn Sie es unter Windows mit einer leeren Zeichenfolge angeben können, wird es automatisch erkannt, aber Linux weigert sich zu signieren, wenn der Algorithmus nicht identisch ist.


Es gibt jedoch einen Trick: In den Informationen zum Signaturalgorithmus (Struktur CRYPT_OID_INFO) wird die Signatur-OID in pszOID und die Hash-Algorithmus-ID in Algid gespeichert. Die Umstellung von Algid auf OID ist bereits eine technische Angelegenheit:


Abrufen der OID des Hash-Algorithmus
 /**<summary> OID   </summary> * <param name="_hCertHandle"> </param> * <param name="_sOID">  OID</param> * <param name="_sError">   </param> * <returns>  ,  UConsts.S_OK   </returns> * **/ 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)); //  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); } } 

Nach sorgfältigem Lesen des Codes können Sie überrascht sein, dass die Algorithmus-ID auf einfache Weise abgerufen wird (CertOIDToAlgId) und die darin enthaltene Oid kompliziert ist (CryptFindOIDInfo). Es wäre logisch anzunehmen, dass entweder beide komplexe oder beide einfache Methoden verwendet werden, und unter Linux funktionieren beide Optionen erfolgreich. Unter Windows ist die schwierige Option, eine Kennung zu erhalten und einfach eine OID zu erhalten, jedoch instabil, sodass ein solch seltsamer Hybrid eine stabile Lösung wäre.


Überprüfung der Unterschrift


Die Überprüfung der Signatur erfolgt in zwei Schritten. Zu Beginn wird die Signatur selbst überprüft und anschließend das Zertifikat, mit dem sie erstellt wurde (Kette, Datum der Unterzeichnung usw.).
Neben der Signatur müssen Sie den zu signierenden Datensatz, die Signaturparameter und die Signatur selbst angeben:


Überprüfung der Unterschrift
 /**<summary>      </summary> * <returns></returns> * **/ internal static 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> * **/ 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, 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 }, //   new int[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(); } } 

Der Einfachheit halber wurde der Prozess zum Bilden einer Struktur mit Parametern in eine separate Methode (GetStdSignVerifyPar) verschoben. Danach wird die Unterschrift selbst überprüft und der erste Unterzeichner extrahiert (für immer wäre es notwendig, alle zu extrahieren, aber eine Unterschrift mit mehreren Unterzeichnern ist immer noch exotisch).


Nach dem Extrahieren des Zertifikats des Unterzeichners konvertieren wir es in unsere Klasse und überprüfen es (falls in den Methodenparametern angegeben). Zur Überprüfung wird das Unterzeichnungsdatum des ersten Unterzeichners verwendet (siehe Abschnitt zum Extrahieren von Informationen aus der Unterschrift und Abschnitt zum Überprüfen des Zertifikats).


Signaturinformationen extrahieren


Kryptografische Systeme erfordern häufig eine gedruckte Darstellung der Signatur. In jedem Fall ist es anders, daher ist es besser, eine Klasse von Informationen über die Signatur zu erstellen, die Informationen in einer bequemen Form zur Verwendung enthält und mit ihrer Hilfe eine gedruckte Präsentation bereitstellt. In .Net gibt es eine solche Klasse - SignedCms. Das Analogon in Mono mit den Signaturen von CritiPro Pro funktioniert jedoch nicht. Erstens enthält es den versiegelten Modifikator und drittens sind fast alle Eigenschaften schreibgeschützt, sodass Sie Ihr eigenes Analogon erstellen müssen.


Die Unterschrift selbst enthält zwei Hauptelemente - eine Liste der Zertifikate und eine Liste der Unterzeichner. Die Liste der Zertifikate kann leer sein oder alle zu überprüfenden Zertifikate enthalten, einschließlich vollständiger Ketten. Die Liste der Unterzeichner gibt die Anzahl der tatsächlichen Unterschriften an. Die Kommunikation zwischen ihnen erfolgt über die Seriennummer und den Verlag (Emittent). Theoretisch kann es in einer Signatur zwei Zertifikate von verschiedenen Herausgebern mit derselben Seriennummer geben, in der Praxis kann dies jedoch vernachlässigt und nur anhand der Seriennummer durchsucht werden.


Das Lesen der Signatur lautet wie folgt:


Signaturinformationen extrahieren
 /**<summary></summary> * <param name="_arSign"></param> * <param name="_sError">   </param> * <returns>  ,  UConsts.S_OK   </returns> * **/ public int Decode(byte[] _arSign, ref string _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); } } 

Die Signatur wird in mehreren Schritten analysiert. Zuerst wird die Nachrichtenstruktur (CryptMsgOpenToDecode) gebildet und anschließend werden echte Signaturdaten (CryptMsgUpdate) eingegeben. Es bleibt zu überprüfen, ob dies eine echte Signatur ist, und zuerst eine Liste der Zertifikate und dann eine Liste der Unterzeichner zu erhalten. Die Liste der Zertifikate wird nacheinander abgerufen:


Eine Liste der Zertifikate erhalten
 /**<summary>     </summary> * <param name="_hMsg">Handle </param> * <returns> </returns> * **/ internal static 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; } 

Zuerst wird die Anzahl der Zertifikate aus dem Parameter CMSG_CERT_COUNT_PARAM bestimmt, und dann werden Informationen zu jedem Zertifikat nacheinander abgerufen. Der Prozess des Erstellens des Kontexts des Zertifikats und auf der Grundlage des Zertifikats selbst schließt den Erstellungsprozess ab.


Das Abrufen von Unterzeichnerdaten ist schwieriger. Sie enthalten eine Angabe des Zertifikats und eine Liste der Signaturparameter (z. B. das Datum der Signatur). Der Datenextraktionsprozess ist wie folgt:


Unterzeichnerinformationen abrufen
 /**<summary>   </summary> * <param name="_hMsg">Handler </param> * <param name="_iIndex"> </param> * <param name="_pSignedCms"> </param> * <param name="_sError">   </param> * <returns>  ,  UConsts.S_OK   </returns> * **/ public int Decode(IntPtr _hMsg, int _iIndex, ISDPSignedCms _pSignedCms, ref string _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 = new byte[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); } } 

, CMSG_SIGNER_INFO. . , .


, — ( , ).


 /**<summary>   </summary> * <param name="_pAttrs"> </param> * <returns> </returns> * **/ 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; } 

Oid – ( ASN.1). :


 /**<summary>      </summary> * <param name="_sName"></param> * <returns> </returns> * **/ 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(); ->>  Mono  // case UConsts.S_MESS_DIGEST_OID : return new Pkcs9MessageDigest(); default: return new Pkcs9AttributeObject(); } } /**<summary>  ASN</summary> * <param name="_pAttr"></param> * <returns></returns> * **/ 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); attribute = customAttribute; } pRes.Add(attribute); } } return pRes; } 

Pkcs9AttributeObject. , mono . Mono .


— — SignedCms, .



, , . (, , ).


 /**<summary> </summary> * <param name="_arInput">  </param> * <param name="_pCert"></param> * <param name="_arRes"></param> * <param name="_sError">   </param> * <returns>   ,  UConsts.S_OK   </returns> * **/ public static int EncryptDataCP(byte[] _arInput, X509Certificate2 _pCert, out byte[] _arRes, ref string _sError) { _arRes = new byte[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 = new byte[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; } } 

— , . , , .


, , .


. , ( ). :


 /**<summary> OID   </summary> * <param name="_hCertHandle"> </param> * <param name="_sOID">  OID</param> * <param name="_sError">   </param> * <returns>  ,  UConsts.S_OK   </returns> * **/ 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)    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 = new byte[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); } } 

. ( , , , .), . (UCConsts.CRYPT_ENCRYPT_ALG_OID_GROUP_ID). — .


( ).



, , . . — , :


 /**<summary> </summary> * <param name="_arInput">  </param> * <param name="_arRes"></param> * <param name="_sError">   </param> * <param name="_pCert"></param> * <returns>  ,  UCOnsts.S_OK   </returns> * **/ 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)   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 = 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 (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); } } 

, . , ( Linux ).



, , , , . , . :


  1. ( , , . .);
  2. — ;
  3. — ;
  4. , , (CRL);

, .


Wie bereits aus der Einführung hervorgeht, ist die Überprüfung des Zertifikats auf Gültigkeit eine der schwierigsten Aufgaben. Aus diesem Grund verfügt die Bibliothek über viele Methoden, um jedes Element einzeln zu implementieren. Wenden wir uns daher zur Vereinfachung den .Net-Quellen für die X509Certificate2.Verify () -Methode zu und nehmen Sie sie als Grundlage.


Die Überprüfung besteht aus zwei Schritten:
  1. Bilden Sie eine Kette von Zertifikaten bis zur Wurzel.
  2. Überprüfen Sie jedes der darin enthaltenen Zertifikate (auf Widerruf, Zeit usw.).

Diese Überprüfung muss vor dem Signieren und Verschlüsseln am aktuellen Datum und zum Zeitpunkt der Überprüfung der Signatur am Datum des Signierens durchgeführt werden. Die Überprüfungsmethode selbst ist klein:


Zertifikatsüberprüfung
 /**<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> * **/ 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)   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); } } 

Zuerst wird eine Kette mit der BuildChain-Methode gebildet und dann überprüft. Während der Kettenbildung werden die Struktur der Parameter, das Datum der Überprüfung und die Kontrollflags gebildet:


 /**<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> * **/ internal static int BuildChain (IntPtr _hChainEngine, IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag, DateTime _rOnDate, TimeSpan _rCTLTimeOut, ref IntPtr _hChain, ref string _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; } 

, Microsoft. hCertPolicy hAppPolicy OID-, , . , , .


(, ).


MapRevocationFlags — .Net — uint .


Fazit


:


  1. 10 ;
  2. ;
  3. byte[] {1, 2, 3, 4, 5};
  4. ;
  5. ;
  6. byte[] {1, 2, 3, 4, 5};
  7. ;

Windows Linux 1-, 10- 50- , Linux . Linux - - ( , ), «» . (deadlock-) ( «Access Violation»).


UCryptoAPI . fpCPSection object :


 private static object fpCPSection = new object(); /**<summary> </summary> * <param name="_hCryptMsg">  </param> * **/ internal static bool CryptMsgClose(IntPtr _hCryptMsg) { lock (pCPSection) { if (fIsLinux) return LCryptoAPI.CryptMsgClose(_hCryptMsg); else return WCryptoAPI.CryptMsgClose(_hCryptMsg); } } /**<summary>     </summary>**/ public static object pCPSection { get { return fpCPSection;} } 

, Linux- .


mono Issuer Subject . , , mono X500DistinguishedName . , mono ( ), (impl.issuerName impl.subjectName). (Reflection) X500DistinguishedName, CERT_CONTEXT .


Referenzen


  1. CAPILite
  2. c #
  3. .Net:
    1. CAPIBase
    2. X509Certificate2
    3. SignedCMS
    4. SignerInfo

  4. mono:
    1. X509Certificate2
    2. X509CertificateImplBtls

Source: https://habr.com/ru/post/de423163/


All Articles