Verbinden Sie OpenSSL mit Mono

    Im vorherigen Artikel wurde der Prozess der Integration von CryptoPro-Standards in Mono beschrieben. Im gleichen Detail konzentrieren wir uns auf die Verbindung von RSA-Zertifikaten.


    Wir haben weiterhin eines unserer in C # geschriebenen Serversysteme auf Linux umgestellt, und der RSA-Teil kam an. Wenn die Verbindungsprobleme beim letzten Mal leicht durch das Zusammenwirken zweier Systeme erklärt werden konnten, die anfangs nicht miteinander verwandt waren, dann hat offensichtlich niemand einen Trick erwartet, wenn Sie "normale" RSA-Zertifikate von Mono aus angeschlossen haben.



    Die Installation des Zertifikats und des Schlüssels verursachte keine Probleme, und das System sah es sogar im regulären Speicher. Das Signieren, Verschlüsseln oder Abrufen der Daten aus der zuvor generierten Signatur war jedoch nicht mehr möglich - Mono fiel stetig mit einem Fehler. Es war wie bei CryptoPro notwendig, eine direkte Verbindung zur Verschlüsselungsbibliothek herzustellen. Für RSA-Zertifikate in Linux ist OpenSSL der Hauptkandidat für eine solche Verbindung.


    Zertifikatsinstallation


    Glücklicherweise hat Centos 7 eine integrierte Version von OpenSSL - 1.0.2k. Um keine zusätzlichen Schwierigkeiten bei der Arbeit des Systems zu verursachen, haben wir uns für eine Verbindung zu dieser Version entschieden. Mit OpenSSL können Sie jedoch spezielle Dateizertifikatspeicher erstellen:


    1. Ein solcher Speicher enthält Zertifikate und CRLs, keine privaten Schlüssel. Daher müssen sie separat gespeichert werden.
    2. Das Speichern von Zertifikaten und privaten Schlüsseln auf einer Festplatte in ungeschützter Form unter Windows ist "extrem unsicher" (die Verantwortlichen für die digitale Sicherheit beschreiben sie normalerweise knapper und weniger zensiert). Offen gesagt, ist dies unter Linux nicht sehr sicher, in der Tat jedoch üblich üben;
    3. den Speicherort ähnlicher Speicher unter Windows und Linux zu koordinieren, ist ziemlich problematisch.
    4. Im Falle der manuellen Implementierung des Speichers benötigen Sie ein Dienstprogramm zum Verwalten einer Reihe von Zertifikaten.
    5. Mono selbst verwendet Festplattenspeicher mit der OpenSSL-Struktur und speichert auch private Schlüssel in der Nähe des offenen Formulars.

    Aus diesen Gründen verwenden wir den Standard .Net- und Mono-Zertifikatsspeicher für die Verbindung von OpenSSL. In Linux müssen dazu zunächst das Zertifikat und der private Schlüssel im Mono-Repository abgelegt werden.

    Zertifikatsinstallation
    Wir verwenden zu diesem Zweck das reguläre Dienstprogramm certmgr. Am Anfang installieren wir den privaten Schlüssel von pfx:

    certmgr -importKey -c -p {password} My {pfx file}

    Dann legen wir das Zertifikat von diesem pfx ab, der private Schlüssel stellt automatisch eine Verbindung dazu her:

    certmgr -add -c My {cer file}

    Wenn Sie den Schlüssel im Speicher für die Maschine installieren möchten, müssen Sie die Option -m hinzufügen.

    Danach ist das Zertifikat im Repository zu sehen:

    certmgr -list -c -v My

    Achten Sie auf die Ausgabe. Es ist zu beachten, dass das Zertifikat für das System sichtbar ist und an den zuvor geladenen privaten Schlüssel gebunden ist. Danach können Sie im Code zur Verbindung gehen.

    Verbindung im Code


    Wie auch beim letzten Mal sollte das System trotz der Umstellung auf Linux weiterhin in der Windows-Umgebung funktionieren. Daher sollte die Arbeit mit der Kryptographie extern durch allgemeine Methoden der Form „byte [] SignData (byte [] _arData, X509Certificate2 _pCert)“ erfolgen, die in Linux und Windows gleichermaßen funktionieren sollten.


    Idealerweise sollte es Methoden geben, die wie in Windows funktionieren - unabhängig von der Art des Zertifikats (unter Linux über OpenSSL oder CryptoPro (je nach Zertifikat) und unter Windows - über Crypt32).


    Eine Analyse der OpenSSL-Bibliotheken ergab, dass die Hauptbibliothek in Windows "libeay32.dll" und in Linux "libcrypto.so.10" ist. Genau wie beim letzten Mal bilden wir zwei Klassen, WOpenSSLAPI und LOpenSSLAPI, die eine Liste von Plug-In-Bibliotheksmethoden enthalten:

    [DllImport(CTRYPTLIB, CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
    internalstaticexternvoidOPENSSL_init();
    

    Beachten Sie die Aufrufkonvention im Gegensatz zu CryptoPro - hier müssen Sie diese explizit angeben. Die Syntax zum Verbinden der einzelnen Methoden zu diesem Zeitpunkt muss unabhängig von den * .h- Quelldateien von OpenSSL gebildet werden.


    Die grundlegenden Regeln zum Bilden der Syntax eines Aufrufs in C # basierend auf den Daten aus den .h-Dateien lauten wie folgt:


    1. alle Verweise auf Strukturen, Strings usw. - IntPtr, einschließlich Verweise innerhalb der Strukturen selbst;
    2. Links zu Links - ref IntPtr, wenn diese Option nicht funktioniert, dann einfach IntPtr. In diesem Fall muss der Link selbst manuell eingefügt und entfernt werden.
    3. Arrays - Byte [];
    4. long in C (OpenSSL) ist ein int in C # (ein kleiner Fehler kann auf den ersten Blick in Stunden der Suche nach der Quelle unvorhersehbarer Fehler umgewandelt werden);

    In der Deklaration können Sie normalerweise SetLastError = true angeben, die Bibliothek wird sie jedoch ignorieren. Fehler werden nicht über Marshal.GetLastWin32Error () verfügbar sein. Für den Zugriff auf Fehler bei OpenSSL stehen die Methoden zur Verfügung.


    Und dann bilden wir die bereits bekannte statische Klasse "UOpenSSLAPI", die je nach System die Methode einer von zwei Klassen aufruft:


    privatestaticobject fpOSSection = newobject();
    /**<summary>Иницииализация библиотеки OpenSSL</summary>**/publicstaticvoidOPENSSL_init() {
        lock (pOSSection) {
            if (fIsLinux)
                LOpenSSLAPI.OPENSSL_init();
            else
                WOpenSSLAPI.OPENSSL_init();
        }
    }
    /**<summary>Критическая секция доступа в OpenSSL</summary>**/publicstaticobject pOSSection
    {
        get { return fpOSSection; }
    }
    /**<summary>Находимся в линуксе</summary>**/publicstaticbool fIsLinux {
        get {
            int iPlatform = (int) Environment.OSVersion.Platform;
            return (iPlatform == 4) || (iPlatform == 6) || (iPlatform == 128);
        }
    }
    

    Man beachte nur das Vorhandensein eines kritischen Abschnitts. OpenSSL arbeitet theoretisch in einer Multithread-Umgebung. Erstens heißt es in der Beschreibung sofort, dass dies nicht garantiert ist:


    Die meisten Objekte können jedoch immer noch nicht gleichzeitig in mehreren Threads verwendet werden.

    Und zweitens ist die Verbindungsmethode nicht die trivialste. Wenn Sie zwei Kern-VMs (Server mit einem Intel Xeon E5649-Prozessor im Hyper-Threading-Modus) verwenden, die einen solchen kritischen Abschnitt enthalten, erhalten Sie etwa 100 vollständige Zyklen (siehe Testalgorithmus des vorherigen Artikels ) oder 600 Signaturen pro Sekunde. Dies ist im Grunde ausreichend für die meisten Aufgaben ( Bei großen Lasten wird eher die Microservice- oder Knotensystem-Architektur verwendet.


    Initialisierung und Entladung von OpenSSL


    Im Gegensatz zu CryptoPro erfordert OpenSSL bestimmte Aktionen, bevor es mit der Verwendung der Bibliothek beginnt, auch wenn die Arbeit mit der Bibliothek abgeschlossen ist:

    /**<summary>Инициализация OpenSSL</summary>**/publicstaticvoidInitOpenSSL() {
        UOpenSSLAPI.OPENSSL_init();
        UOpenSSLAPI.ERR_load_crypto_strings();
        UOpenSSLAPI.ERR_load_RSA_strings();
        UOpenSSLAPI.OPENSSL_add_all_algorithms_conf();
        UOpenSSLAPI.OpenSSL_add_all_ciphers();
        UOpenSSLAPI.OpenSSL_add_all_digests();
    }
    /**<summary>Очистка OpenSSL</summary>**/publicstaticvoidCleanupOpenSSL() {
        UOpenSSLAPI.EVP_cleanup();
        UOpenSSLAPI.CRYPTO_cleanup_all_ex_data();
        UOpenSSLAPI.ERR_free_strings();
    }
    


    Fehlerinformationen


    OpenSSL speichert Informationen zu Fehlern in internen Strukturen für den Zugriff auf die in der Bibliothek spezielle Methoden vorhanden sind. Leider sind einige einfache Methoden, z. B. ERR_error_string, instabil. Daher müssen Sie komplexere Methoden verwenden:


    Fehlerinformationen abrufen
    /**<summary>Сформировать строку с ошибкой OpenSSL</summary>
    * <param name="_iErr">Код ошибки</param>
    * <param name="_iPart">Раздел</param>
    * <returns>Строка ошибки</returns>
    * **/publicstaticstringGetErrStrPart(ulong _iErr, int _iPart) {
        // 0) Определяем тип строки
        IntPtr hErrStr = IntPtr.Zero;
        switch (_iPart) {
            case0: hErrStr = UOpenSSLAPI.ERR_lib_error_string(_iErr);
                    break;
            case1: hErrStr = UOpenSSLAPI.ERR_func_error_string(_iErr);
                    break;
            case2: hErrStr = UOpenSSLAPI.ERR_reason_error_string(_iErr);
                    break;
        }
        // 1) Формируем срокуreturn PtrToFirstStr(hErrStr);
    }
    /**<summary>Сформировать строку с ошибкой OpenSSL</summary>
    * <param name="_iErr">Код ошибки</param>
    * <returns>Строка ошибки</returns>
    * **/publicstaticstringGetErrStr(ulong _iErr ) {
        return UCConsts.S_GEN_LIB_ERR_MAKRO.Frm(_iErr, GetErrStrPart(_iErr, 0),
                                                GetErrStrPart(_iErr, 1), 
                                                GetErrStrPart(_iErr, 2));
    }
    /**<summary>Сформировать строку с ошибкой OpenSSL</summary>
    * <returns>Строка ошибки</returns>
    * **/publicstaticstringGetErrStrOS() {
        return GetErrStr(UOpenSSLAPI.ERR_get_error());
    }
    

    Ein Fehler in OpenSSL enthält Informationen über die Bibliothek, in der sie aufgetreten ist, die Methode und den Grund. Nach Erhalt des Fehlercodes selbst ist es daher erforderlich, alle drei Teile separat zu extrahieren und in einer Textzeichenfolge zusammenzufassen. Die Längen der einzelnen Zeilen überschreiten laut der OpenSSL- Dokumentation nicht mehr als 120 Zeichen usw. Wir verwenden verwalteten Code und müssen die Zeile sorgfältig als Referenz extrahieren:


    Einen String von IntPtr bekommen
    /**<summary>Извлекает из указателя первый PChar из области памяти  длинной _iLen</summary>
    * <param name="_hPtr">Указетель на область неуправляемой памяти</param>
    * <param name="_iLen">Длина области памяти</param>
    * <returns>Итоговая строка</returns>
    * **/publicstaticstringPtrToFirstStr(IntPtr _hPtr, int _iLen = 256) {
        if(_hPtr == IntPtr.Zero) return"";
        try {
            byte[] arStr = newbyte[_iLen];
            Marshal.Copy(_hPtr, arStr, 0, arStr.Length);
            string[] arRes = Encoding.ASCII.GetString(arStr).Split(newchar[] { (char)0 }, 
                                                                   StringSplitOptions.RemoveEmptyEntries);
            if (arRes.Length > 0) return arRes[0];
              return"";
        }catch {
              return"";
        }
    }
    

    Fehler beim Prüfen von Zertifikaten stehen nicht in der allgemeinen Liste und müssen gemäß dem Überprüfungskontext mit einer separaten Methode extrahiert werden:


    Fehler beim Bestätigen des Zertifikats
    /**<summary>Извлечение ошибки верификации сертификата</summary>
    * <param name="_hStoreCtx">Контекст хранилища</param>
    * <returns>Строка ошибки</returns>
    * **/publicstaticstringGetCertVerifyErr(IntPtr _hStoreCtx) {
        int iErr = UOpenSSLAPI.X509_STORE_CTX_get_error(_hStoreCtx);
        return PtrToFirstStr(UOpenSSLAPI.X509_verify_cert_error_string(iErr));
    }
    

    Zertifikatsuche


    Kryptographie beginnt wie immer mit einer Zertifikatsuche. Wir verwenden regelmäßig Speicher, daher suchen wir nach regulären Methoden:


    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>
    * **/internalstaticintFindCertificateOS(string _pFindValue, out X509Certificate2 _pCert, refstring _sError, 
                                          StoreLocation _pLocation = StoreLocation.CurrentUser, 
                                          StoreName _pName = StoreName.My, 
                                          X509FindType _pFindType = X509FindType.FindByThumbprint,                                             
                                          bool _fVerify = false) {
        lock (UOpenSSLAPI.pOSSection) {
            // 0) Сздаем нативное хранилище
            _pCert = null;
            X509Store pStore = new X509Store(_pName, _pLocation);
            X509Certificate2Collection pCerts = null;
            try {
                // 1) Открытие хранилища
                pStore.Open(OpenFlags.ReadOnly);
                // 2) Поиск в хранилище (не проверяя, т.к. Verify в Linux всегда false)
                pCerts = pStore.Certificates.Find(_pFindType, _pFindValue, false);
                if (pCerts.Count == 0) return UConsts.E_NO_CERTIFICATE;
                // 3) Нет проверки возвращаем первыйif (!_fVerify) {
                    _pCert = ISDP_X509Cert.Create(pCerts[0], TCryptoPath.cpOpenSSL);
                    return UConsts.S_OK;
                }
                // 4) Проходим по сертфикатам и выбираем валидныйforeach (X509Certificate2 pCert in pCerts) {
                    ISDP_X509Cert pISDPCert = ISDP_X509Cert.Create(pCert, TCryptoPath.cpOpenSSL);
                    if (pISDPCert.ISDPVerify()) {
                        _pCert = pISDPCert;
                         return UConsts.S_OK;
                    }
                }
                return UConsts.E_NO_CERTIFICATE;
            } finally {
                if(pCerts != null) pCerts.Clear();
                pStore.Close();
            }
        }
    }
    

    Achten Sie auf den kritischen Abschnitt. Mono mit Zertifikaten funktioniert auch über OpenSSL, jedoch nicht über UOpenSSLAPI. Andernfalls können Sie unter Last Speicherlecks und unverständliche Floating-Fehler erhalten.


    Das Hauptmerkmal ist die Erstellung eines Zertifikats. Im Gegensatz zur Version für CryptoPro erhalten wir in diesem Fall aus dem Repository das Zertifikat selbst (X509Certificate2), und der Link im Handle darin zeigt bereits auf die OpenSSL-Struktur X509_st. Es scheint, dass dies das ist, was benötigt wird, aber es gibt keinen Zeiger auf EVP_PKEY (Verknüpfung zu der privaten Schlüsselstruktur in OpenSSL).

    Der private Schlüssel selbst wird, wie sich herausgestellt hat, im internen Feld des Zertifikats - impl / fallback / _cert / _rsa / rsa - in Klartext gespeichert. Dies ist eine RSAManaged-Klasse. Ein kurzer Blick auf den Code (z. B. die DecryptValue- Methode ) zeigt, wie schlecht Mono mit der Kryptographie ist. Anstatt OpenSSL-Kryptographiemethoden ehrlich zu verwenden, implementierten sie offenbar mehrere Algorithmen manuell. Diese Annahme wird durch ein leeres Suchergebnis für ihr Projekt unter Verwendung von OpenSSL-Methoden wie CMS_final, CMS_sign oder CMS_ContentInfo_new unterstützt. Und ohne sie ist die Bildung einer standardmäßigen CMS-Signaturstruktur kaum vorstellbar. Gleichzeitig wird die Arbeit mit Zertifikaten teilweise über OpenSSL durchgeführt.


    Dies legt nahe, dass der private Schlüssel von Mono entladen und per Pem in EVP_PKEY geladen werden muss. Aus diesem Grund benötigen wir wieder eine von X509Certificate abgeleitete Klasse, in der alle zusätzlichen Links gespeichert werden.


    Wie im Fall von CryptoPro führen Versuche, ein neues Zertifikat aus Handle zu erstellen, ebenfalls nicht zum Erfolg (Mono-Abstürze mit einem Fehler), und das Erstellen eines Zertifikats basierend auf dem resultierenden Zertifikat führt zu Speicherverlusten. Daher besteht die einzige Möglichkeit darin, ein Zertifikat basierend auf dem Byte-Array zu erstellen, das Pem enthält. PEM-Zertifikat erhalten Sie wie folgt:


    PEM-Zertifizierung
    /**<summary>Получить файл сертификата</summary>
    * <param name="_pCert">Сертификат</param>
    * <param name="_arData">Выходные бинарные данные</param>
    * <param name="_fBase64">Формат Base64</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/publicstaticintToCerFile(this X509Certificate2 _pCert, outbyte[] _arData, 
                                refstring _sError, bool _fBase64 = true) {
        _arData = newbyte[0];
        try {
            byte[] arData = _pCert.Export(X509ContentType.Cert);
            // 0) DERif (!_fBase64) {
                _arData = arData;
                return UConsts.S_OK;
            }
            // 1) Base64using (TextWriter pWriter = new StringWriter()) {                    
                pWriter.WriteLine(UCConsts.S_PEM_BEGIN_CERT);
                pWriter.WriteLine(Convert.ToBase64String(arData, Base64FormattingOptions.InsertLineBreaks));
                pWriter.WriteLine(UCConsts.S_PEM_END_CERT);
                // 1.2) Возвращаем итог
                _arData = Encoding.UTF8.GetBytes(pWriter.ToString());
            }                
            return UConsts.S_OK;
        } catch (Exception E) {
             _sError = UCConsts.S_TO_PEM_ERR.Frm(E.Message);
             return UConsts.E_GEN_EXCEPTION;
        }            
    }
    

    Das Zertifikat wird ohne den privaten Schlüssel abgerufen und wir verbinden es selbst und bilden ein separates Feld für die Verknüpfung zu ENV_PKEY:


    Generieren Sie ENV_PKEY basierend auf dem privaten PEM-Schlüssel
    /**<summary>Формируем OpenSSL контекст закрытого ключа (EVP_PKEY) по данным закрытого ключа</summary>
    * <remarks>Преобразование через выгрузку закрытого ключа в PEM</remarks>
    * <param name="_arData">Данные закрытого ключа сертификата</param>
    * <returns>Контекст закрытого ключа (EVP_PKEY)</returns>
    * **/internalstatic IntPtr GetENV_PKEYOS(byte[] _arData) {
        IntPtr hBIOPem = IntPtr.Zero;
        try {
            // 0)  Выгружаем в BIO
            hBIOPem = UOpenSSLAPI.BIO_new_mem_buf( _arData, _arData.Length);
            if (hBIOPem == IntPtr.Zero) return IntPtr.Zero;
            IntPtr hKey = IntPtr.Zero;
            // 1)  Формируем структуру закрытого ключа
            UOpenSSLAPI.PEM_read_bio_PrivateKey(hBIOPem, ref hKey, IntPtr.Zero, 0);
            return hKey;
        } finally {
            if(hBIOPem != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOPem);
        }
    }
    

    Hochladen einen privaten Schlüssel im PEM ist die Aufgabe viel schwieriger als PEM - Zertifikat, aber es wird bereits beschrieben hier . Beachten Sie, dass das Entladen des privaten Schlüssels ein "extrem unsicherer" Fall ist, und dies sollte in jeder Hinsicht vermieden werden. Und da dieses Entladen für die Arbeit mit OpenSSL erforderlich ist, sollten unter Windows crypt32.dll-Methoden oder reguläre .NET-Klassen verwendet werden, um diese Bibliothek zu verwenden. Unter Linux müssen Sie vorerst auf diese Weise arbeiten.


    Es ist auch erwähnenswert, dass die generierten Links auf einen nicht verwalteten Speicherbereich verweisen und freigegeben werden sollten. Weil in .Net 4.5 X509Certificate2 ist nicht verfügbar, sollte dies im Destruktor erfolgen


    Unterzeichnung


    Zum Signieren von OpenSSL können Sie die vereinfachte CMS_sign-Methode verwenden. Diese basiert jedoch auf der Konfigurationsdatei, die bei der Auswahl des Algorithmus für alle Zertifikate gleich ist. Daher ist es besser, sich auf den Code dieser Methode zu verlassen, um eine ähnliche Signaturerzeugung zu implementieren:


    Datenunterschrift
    /**<summary> Подписывает информацию</summary>
    * <param name="_arData">Данные для подписания</param>
    * <param name="_pCert">Сертификат</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <param name="_arRes">Подпись сертфиикат</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintSignDataOS(byte[] _arData, X509Certificate2 _pCert, outbyte[] _arRes, refstring _sError) {
        _arRes         = newbyte[0];
        uint iFlags    = UCConsts.CMS_DETACHED;
        IntPtr hData   = IntPtr.Zero;
        IntPtr hBIORes = IntPtr.Zero;
        IntPtr hCMS    = IntPtr.Zero;
        try {
            // 0) Формируем сертфиикат
            ISDP_X509Cert pCert = ISDP_X509Cert.Convert(_pCert, TCryptoPath.cpOpenSSL);
            // 1) Формируем BIO с даннымиint iRes = GetBIOByBytesOS(_arData, out hData, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            // 2) Создаем итоговый BIO
            hBIORes = UOpenSSLAPI.BIO_new(UOpenSSLAPI.BIO_s_mem());
            // 3) Формируем объект подписи
            hCMS = UOpenSSLAPI.CMS_ContentInfo_new();
            if (hCMS == IntPtr.Zero) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_CR_ERR);
            if (!UOpenSSLAPI.CMS_SignedData_init(hCMS)) 
                return RetErrOS(ref _sError, UCConsts.S_OS_CMS_INIT_ERR);
            // 4) Добавляем подписантаif(UOpenSSLAPI.CMS_add1_signer(hCMS, pCert.hRealHandle, pCert.hOSKey,
                                           pCert.hOSDigestAlg, iFlags) == IntPtr.Zero)
                return RetErrOS(ref _sError, UCConsts.S_OS_CMS_SET_SIGNER_ERR);
            // 5) Установка флага - отцепленная подписьif (!UOpenSSLAPI.CMS_set_detached(hCMS, 1))
                return RetErrOS(ref _sError, UCConsts.S_OS_CMS_SET_DET_ERR);
            // 6) Завершение формирования подписиif (!UOpenSSLAPI.CMS_final(hCMS, hData, IntPtr.Zero, iFlags))
                return RetErrOS(ref _sError, UCConsts.S_OS_CMS_FINAL_ERR);
            // 7) Переписываем в заготовленный BIOif (!UOpenSSLAPI.i2d_CMS_bio_stream(hBIORes, hCMS, IntPtr.Zero, iFlags)) 
                 return RetErrOS(ref _sError, UCConsts.S_OS_CMS_EXP_TO_BIO_ERR);
            // 8) Считываем полученные данные из BIOreturn ReadFromBIO_OS(hBIORes, out _arRes, ref _sError);
        } catch (Exception E) {
            _sError = UCConsts.S_SIGN_OS_GEN_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if(hBIORes != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIORes);
            if(hData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hData);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
        }
    }
    

    Der Ablauf des Algorithmus ist wie folgt. Zuerst konvertieren wir das eingehende Zertifikat (wenn es sich um X509Certificate2 handelt) in unseren Typ. Weil Wir arbeiten mit Links zum nicht verwalteten Speicherbereich, dann müssen sie genau überwacht werden. .Net Nachdem der Verweis auf das Zertifikat aus dem Bereich freigegeben wurde, startet der Destruktor nach einiger Zeit selbst. Darin haben wir die erforderlichen Methoden zur Reinigung aller damit verbundenen nicht verwalteten Speicher genau beschrieben. Dieser Ansatz ermöglicht es uns, keine Zeit zu verlieren, diese Links direkt innerhalb der Methode zu verfolgen.


    Nachdem wir uns mit dem Zertifikat befasst haben, bilden wir die BIO mit den Daten und der Signaturstruktur. Dann fügen wir die Daten des Unterzeichners hinzu, setzen das Flag für die Unterschriftsaufhebung und starten die endgültige Signaturerzeugung. Das Ergebnis ist auf BIO übertragbar. Es bleibt nur noch das Byte-Array aus BIO zu extrahieren. Das Konvertieren eines BIO in eine Menge von Bytes und zurück wird häufig verwendet. Es ist daher besser, sie in separate Methoden zu unterteilen:


    BIO in Byte [] und zurück
    /**<summary>Прочитать из BIO массив байт из OpenSSL</summary>
    * <param name="_hBIO">Контекст BIO</param>
    * <param name="_sError">Возвращаемая стркоа с ошибкой</param>
    * <param name="_arRes">Результат</param>
    * <param name="_iLen">Длина данных, если 0 - то все что есть</param>
    * <returns>Стандартный код с ошибкой, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintReadFromBIO_OS(IntPtr _hBIO, outbyte[] _arRes, refstring _sError, uint _iLen = 0) {
        _arRes = newbyte[0];
        IntPtr hRes = IntPtr.Zero;
        uint iLen = _iLen;
        if(iLen == 0) iLen = int.MaxValue;
        try {
            // 0) Определяем длину
            iLen = UOpenSSLAPI.BIO_read(_hBIO, IntPtr.Zero, int.MaxValue);
            // 1) Формируем буфер и читаем  него 
            hRes  = Marshal.AllocHGlobal((int)iLen);
            if (UOpenSSLAPI.BIO_read(_hBIO, hRes, iLen) != iLen) {
                _sError = UCConsts.S_OS_BIO_READ_LEN_ERR;
                return UConsts.E_CRYPTO_ERR;
            } 
            // 2) Итоговый массив
            _arRes = newbyte[iLen];
            Marshal.Copy(hRes, _arRes, 0, _arRes.Length);
            return UConsts.S_OK;;
        } catch (Exception E) {
            _sError = UCConsts.S_OS_BIO_READ_GEN_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally { 
            if(hRes != IntPtr.Zero) Marshal.FreeHGlobal(hRes);
        }
    }
    /**<summary>Получить BIO по набору байт</summary>
    * <param name="_arData">Данные</param>
    * <param name="_hBIO">Возвращаемый указатель на BIO</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintGetBIOByBytesOS(byte[] _arData, out IntPtr _hBIO, refstring _sError) {
        _hBIO = UOpenSSLAPI.BIO_new_mem_buf( _arData, _arData.Length);
        if (_hBIO == IntPtr.Zero) 
            return  RetErrOS(ref _sError, UCConsts.S_OS_CM_BIO_CR_ERR);
        return UConsts.S_OK;
    }
    

    Wie im Fall von CryptoPro müssen Informationen über den Signatur-Hash-Algorithmus aus dem Zertifikat extrahiert werden. Im Falle von OpenSSL wird es jedoch direkt im Zertifikat gespeichert:


    Hash-Algorithmus extrahieren
    /**<summary>Извлечение алгоритма хэширования из контекста сертфииката OpenSSL</summary>
    * <param name="_hCert">Контекст сертификта (X509)</param>
    * <returns>Контекст аглоритма</returns>
    * **/publicstatic IntPtr GetDigestAlgOS(IntPtr _hCert) {
        x509_st pCert = (x509_st)Marshal.PtrToStructure(_hCert, typeof(x509_st));
        X509_algor_st pAlgInfo = (X509_algor_st)Marshal.PtrToStructure(pCert.sig_alg, typeof(X509_algor_st));
        IntPtr hAlgSn = UOpenSSLAPI.OBJ_nid2sn(UOpenSSLAPI.OBJ_obj2nid(pAlgInfo.algorithm));
        return UOpenSSLAPI.EVP_get_digestbyname(hAlgSn);
    }
    

    Der Weg stellte sich als ziemlich schwierig heraus, aber es funktioniert. In der 1.0.2-Dokumentation finden Sie die EVP_get_digestbynid- Methode , aber die Bibliotheken der verwendeten Version exportieren sie nicht. Deshalb bilden wir zuerst eine NID und darauf basierend einen kurzen Namen. Und schon durch den Kurznamen können Sie den Algorithmus durch eine regelmäßige Suche nach Namen extrahieren.


    Signaturüberprüfung


    Die empfangene Signatur muss überprüft werden. OpenSSL überprüft die Signatur wie folgt:


    Signaturüberprüfung
    /**<summary>Проверяет подпись</summary>
    * <param name="_arData">данные, которые было подписаны</param>
    * <param name="_arSign">подпись</param>
    * <param name="_pCert">сертификат</param>
    * <param name="_sError">возвращаемая строка с ошибкой</param>
    * <param name="_pLocation">Местопложение</param>
    * <param name="_fVerifyOnlySign">Проверять только подпись</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * <remarks>Проверяется только первый подписант</remarks>
    * **/internalstaticintCheckSignOS(byte[] _arData, byte[] _arSign, out X509Certificate2 _pCert, refstring _sError,
                                    bool _fVerifyOnlySign = true, 
                                    StoreLocation _pLocation = StoreLocation.CurrentUser){
        _pCert = null;
        IntPtr hBIOData = IntPtr.Zero;
        IntPtr hCMS     = IntPtr.Zero;
        IntPtr hTrStore = IntPtr.Zero;
        try {
            // 0) Формирование BIO с данными для подписиint iRes = GetBIOByBytesOS(_arData, out hBIOData, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            // 1) Чтение структуры CMS
            iRes = GetCMSFromBytesOS(_arSign, out hCMS, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            uint iFlag =  UCConsts.CMS_DETACHED;                
            // 2) Формирование доверенного хранилищаif (!_fVerifyOnlySign) {
                iRes = GetTrustStoreOS(_pLocation, out hTrStore, ref _sError);
                if (iRes != UConsts.S_OK) return iRes;
            } else 
                iFlag |= UCConsts.CMS_NO_SIGNER_CERT_VERIFY;
            // 3) Проверка подписиif (!UOpenSSLAPI.CMS_verify(hCMS, IntPtr.Zero, hTrStore, hBIOData, IntPtr.Zero, iFlag)) 
                return RetErrOS(ref _sError, UCConsts.S_OS_CM_CHECK_ERR);
            return UConsts.S_OK;                 
        } finally {
            if(hBIOData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOData);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
            if(hTrStore != IntPtr.Zero) UOpenSSLAPI.X509_STORE_free(hTrStore);
        }
    }
    

    Zunächst werden die Signaturdaten aus dem Bytearray in die CMS-Struktur konvertiert:


    Bildung der CMS-Struktur
    /**<summary>Получить CMS из набора байт</summary>
    * <param name="_arData">Данные CMS</param>
    * <param name="_hCMS">Возвращаемый указатель  на структуру CMS</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintGetCMSFromBytesOS(byte[] _arData, out IntPtr _hCMS, refstring _sError) {
        _hCMS          = IntPtr.Zero; 
        IntPtr hBIOCMS = IntPtr.Zero;
        IntPtr hCMS    = IntPtr.Zero;
        try {
            // 0) Инициалиацзия структуры CMS
            hCMS = UOpenSSLAPI.CMS_ContentInfo_new();
            if (hCMS == IntPtr.Zero) return RetErrOS(ref _sError);
            if (!UOpenSSLAPI.CMS_SignedData_init(hCMS))
                return RetErrOS(ref _sError);
            // 1) Чтение данных в BIO
            hBIOCMS = UOpenSSLAPI.BIO_new_mem_buf(_arData, _arData.Length);
            if (hBIOCMS == IntPtr.Zero) return RetErrOS(ref _sError);
            // 2) Преобразование в CMSif (UOpenSSLAPI.d2i_CMS_bio(hBIOCMS, ref hCMS) == IntPtr.Zero)
                return RetErrOS(ref _sError);
            // 3) Все ок - перекрываем, чтобы не занулило
            _hCMS = hCMS;
            hCMS = IntPtr.Zero;
            return UConsts.S_OK;
        } finally {
            if(hBIOCMS != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOCMS);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
        }
    }
    

    Außerdem werden die Daten des in BIO signierten Dokuments geladen. Wenn Sie das Zertifikat überprüfen möchten, mit dem die Signatur erstellt wurde, erstellen wir im Speicher einen Speicher für vertrauenswürdige Zertifikate (Root und Intermediate), auf deren Grundlage eine Kette gebildet wird:


    Erstellung des Zertifikatsspeichers
    /**<summary>Получить доверенное хранилище в памяти</summary>
    * <param name="_hStore">Возвращаемая ссылка на хранилище</param>
    * <param name="_pLocation">Местоположение хранилища</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
    */internalstaticintGetTrustStoreOS(StoreLocation _pLocation, out IntPtr _hStore, refstring _sError) {
        _hStore = IntPtr.Zero;
        IntPtr hStore   = IntPtr.Zero;
        try {
            List<X509Certificate2> pCerts = GetCertList(_pLocation, StoreName.Root, TCryptoPath.cpOpenSSL);
            pCerts.AddRange(GetCertList(_pLocation, StoreName.AuthRoot, TCryptoPath.cpOpenSSL));
            // 1) Формируем хранилище
            hStore = UOpenSSLAPI.X509_STORE_new();
            foreach (X509Certificate2 pCert in pCerts) {
                // Даже если ошибка идем дальше (чаще всего ошибка дубля сертификатов)
                UOpenSSLAPI.X509_STORE_add_cert(hStore, pCert.getRealHandle());
            }
            // 2) Очистка ошибок
            UOpenSSLAPI.ERR_clear_error();
            _hStore = hStore;
            hStore = IntPtr.Zero;
            return UConsts.S_OK;
        } catch (Exception E) {
            _sError = UCConsts.S_FORM_TRUST_STORE_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if (hStore != IntPtr.Zero) UOpenSSLAPI.X509_STORE_free(hStore);
    }
    

    Wenn sich ein Teil der Zertifikate aus der Kette direkt in der Signatur befindet, müssen sie ebenfalls zum Repository hinzugefügt werden (das nächste Kapitel beschreibt, wie sie von dort aus extrahiert werden). Schließlich wird die CMS_Verify-Methode aufgerufen, die die Überprüfung durchführt.


    Wenn zusätzliche Überprüfungseinstellungen erforderlich sind (z. B. wenn Zertifikate ohne Verwendung einer CRL geprüft werden müssen), sollten die erforderlichen Kennzeichen zur Variablenvariable iFlag-Überprüfung hinzugefügt werden.


    Unterschrift Information


    In Systemen, die mit Kryptographie arbeiten, ist es notwendig, Informationen über die Signatur für den Kunden in einer bequemen Form anzuzeigen. Dies muss an verschiedenen Stellen auf unterschiedliche Weise erfolgen, so dass eine Klasse benötigt wird, in der diese Informationen in einer benutzerfreundlichen Form gespeichert werden. In .Net gibt es eine solche Klasse - SignedCms. Da dies jedoch bereits im vorherigen Artikel über CryptoPro beschrieben wurde, wird es nicht funktionieren und Sie müssen ein eigenes Pendant schreiben.


    Die Signatur enthält Informationen über den Abonnenten (es kann mehrere Unterzeichner geben, kommt jedoch sehr selten vor) und eine Liste von Zertifikaten zur Bildung der Zertifikatskette des Unterzeichners. Daher erfolgt die Extraktion der Signaturdaten in zwei Schritten: Zuerst wird die allgemeine Liste der Zertifikate extrahiert und dann die Liste der Unterzeichner.


    Analysieren von Signaturdaten
    /**<summary>Разбор данных подписи</summary>
    * <param name="_arSign">Подпись</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <param name="_arContent">Данные для подписи</param>
    * <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalintDecodeOS(byte[] _arSign, byte[] _arContent, refstring _sError) {
        IntPtr hBIOData = IntPtr.Zero;
        IntPtr hCMS     = IntPtr.Zero;
        IntPtr hCerts   = IntPtr.Zero;
        try {
            // 0) Формируем данные и СMSint iRes = UCUtils.GetCMSFromBytesOS(_arSign, out hCMS, ref _sError);
            if(iRes != UConsts.S_OK) return iRes;
            iRes = UCUtils.GetBIOByBytesOS(_arContent, out hBIOData, ref _sError);
            if(iRes != UConsts.S_OK) return iRes;
            // 1) Устанавливаем флагиuint iFlags = UCConsts.CMS_NO_SIGNER_CERT_VERIFY;
            if(_arContent.Length == 0) iFlags |= UCConsts.CMS_NO_CONTENT_VERIFY;
            // 2) Считываем CMSif (!UOpenSSLAPI.CMS_verify(hCMS, IntPtr.Zero, IntPtr.Zero, hBIOData, IntPtr.Zero,  iFlags))
                 return UCUtils.RetErrOS(ref _sError, UCConsts.S_OS_CMS_VERIFY_ERR);
            // 3) Извлекаем сертификаты
            hCerts = UOpenSSLAPI.CMS_get0_signers(hCMS);
            int iCnt = UOpenSSLAPI.sk_num(hCerts);
            for (int i = 0; i < iCnt; i++) {
                IntPtr hCert = UOpenSSLAPI.sk_value(hCerts, i);
                byte[] arData;
                iRes = UCUtils.GetCertBytesOS(hCert, out arData, ref _sError);
                if(iRes != UConsts.S_OK) return iRes;
                fpCertificates.Add(ISDP_X509Cert.Create(arData, TCryptoPath.cpOpenSSL));
            }
            // 4) Извлекаем подписантов
            IntPtr hSigners = UOpenSSLAPI.CMS_get0_SignerInfos(hCMS);                
            iCnt = UOpenSSLAPI.sk_num(hSigners);                
            for (int i = 0; i < iCnt; i++) {
                IntPtr hSignerInfo = UOpenSSLAPI.sk_value(hSigners, i);
                // 4.1) Информация о подписанте
                ISDPSignerInfo pInfo = new ISDPSignerInfo(this);
                iRes = pInfo.DecodeOS(hSignerInfo, ref _sError);
                if(iRes != UConsts.S_OK) return iRes;
                fpSignerInfos.Add(pInfo);
            }
            return UConsts.S_OK;
        } catch (Exception E) {
            _sError = UCConsts.S_OS_CMS_DECODE.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if(hCerts != IntPtr.Zero) UOpenSSLAPI.sk_free(hCerts);
            if(hBIOData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOData);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
        }
    }
    

    Wie im letzten Kapitel bilden wir zunächst BIO-Arrays aus Bytes mit den Daten für die Signatur (falls erforderlich) und der CMS-Struktur und starten dann die Prüfung. Dies mag seltsam erscheinen, aber Informationen zur Signatur werden in der Überprüfungsmethode gebildet. Daher müssen Sie diese Methode aufrufen, bevor Sie die Daten extrahieren.


    Die Liste der Zertifikate und Unterzeichner wird in Form von Stapeln (STACK_OF (X509)) ausgegeben. Wenn Sie jedoch Elemente mit der Methode sk_pop extrahieren, ist die Freigabe der extrahierten Strukturen die Aufgabe des Entwicklers. Wenn dies nicht gewünscht ist, lohnt es sich, die Schleife zu durchlaufen und den Wert mit der Methode sk_value zu extrahieren.


    Es ist zu beachten, dass Sie beim Abrufen einer Liste von Zertifikaten die Methode CMS_get0_signers anstelle von CMS_get1_certs verwenden müssen. Die erste erstellt eine Liste der Zertifikate der Unterzeichner, die zweite alle Zertifikate. Logischerweise ist es richtiger, die zweite Option zu verwenden, sie enthält jedoch einen Aufruf der Stream-Sperrmethode, die unter Last zu Speicherverlusten führt:


    CRYPTO_add(&cch->d.certificate->references, 1, CRYPTO_LOCK_X509);

    In Version 1.1.0 wird der Aufruf durch X509_up_ref ersetzt, sodass das Problem möglicherweise bereits behoben ist.
    Für Informationen zu jedem Unterzeichner erstellen wir eine separate Methode:


    Informieren Sie die Unterzeichnerinformationen
    /**<summary>Распарсить информацию о подписанте</summary>
    * <param name="_hSignerInfo">Handler информации о подписанте (OpenSSL)</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
    * **/publicintDecodeOS(IntPtr _hSignerInfo, refstring _sError) {
        try {
            // 0) Определение сертфиката подписантаint iRes = UCUtils.GetSignerInfoCertOS(_hSignerInfo, fpSignedCMS.pCertificates, 
                                                   out fpCertificate, ref _sError);
            if(iRes != UConsts.S_OK) return iRes;
            // 1) Извлечение даты подписиuint iPos =  UOpenSSLAPI.CMS_signed_get_attr_by_NID(_hSignerInfo, UCConsts.NID_pkcs9_signingTime, 0);
            IntPtr hAttr = UOpenSSLAPI.CMS_signed_get_attr(_hSignerInfo, iPos);
            IntPtr hDateTime = UOpenSSLAPI.X509_ATTRIBUTE_get0_data(hAttr, 0, UCConsts.V_ASN1_UTCTIME, IntPtr.Zero);
            asn1_string_st pDate = (asn1_string_st)Marshal.PtrToStructure(hDateTime, typeof(asn1_string_st));
            // 2) Преобрзование в Pkcs9SigningTimebyte[] arDateAttr = newbyte[pDate.iLength];
            Marshal.Copy(pDate.hData, arDateAttr, 0, (int)pDate.iLength);
            arDateAttr = newbyte[] { (byte)UCConsts.V_ASN1_UTCTIME, (byte)pDate.iLength}.Concat(arDateAttr).ToArray();
            fpSignedAttributes.Add(new Pkcs9SigningTime(arDateAttr));
            return UConsts.S_OK;
        } catch (Exception E) {
            _sError = UCConsts.S_CMS_SIGNER_DEC_OS_ER.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        }
    }
    

    Darin extrahieren wir zuerst das Zertifikat des Unterzeichners und dann das Datum der Unterzeichnung. Das Signaturdatum ist eines der Signaturattribute im ASN.1-Format. Um es abzurufen, betrachten Sie die Struktur asn1_string_st und bilden Sie basierend auf den darin enthaltenen Daten das Attribut Pkcs9SigningTime.


    Der Erhalt eines Zertifikats wird separat implementiert:


    Erhalt eines Unterzeichnerzertifikats
    /**<summary>Получить сертификата подписанта</summary>
    * <param name="_hSignerInfo">Информация об подписанте</param>
    * <param name="_pCert">Возврат сертификата</param>
    * <param name="_pCerts">Список сертификатов где искать</param>
    * <param name="_sError">Возвращаемая строкка с ошибкой</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintGetSignerInfoCertOS(IntPtr _hSignerInfo, X509Certificate2Collection _pCerts,
                                            out X509Certificate2 _pCert, refstring _sError) {
        _pCert = null;
        try {
            // 0) Получаем информацию об адресате
            IntPtr hKey    = IntPtr.Zero;
            IntPtr hIssuer = IntPtr.Zero;
            IntPtr hSNO    = IntPtr.Zero;
            if (!UOpenSSLAPI.CMS_SignerInfo_get0_signer_id(_hSignerInfo, ref hKey, ref hIssuer, ref hSNO))
                 return RetErrOS(ref _sError, UCConsts.S_GET_RECEIP_INFO_ERR);
            // 1) Извлекается серийный номерstring sSerial;
            int iRes = GetBinaryHexFromASNOS(hSNO, out sSerial, ref _sError);
            if(iRes != UConsts.S_OK) return iRes;
            X509Certificate2Collection pResCerts = _pCerts.Find(X509FindType.FindBySerialNumber, sSerial, false);
            if(pResCerts.Count == 0) return RetErrOS(ref _sError, UCConsts.S_NO_CERTIFICATE, UConsts.E_NO_CERTIFICATE);
            _pCert = pResCerts[0];
            return UConsts.S_OK;
        } catch (Exception E) {
            _sError = UCConsts.S_GET_SIGN_INFO_GEN_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        }
    }
    

    Zunächst werden Informationen zur Seriennummer und zum Herausgeber extrahiert und anschließend das Zertifikat aus der zuvor empfangenen Liste extrahiert. Die Seriennummer ist eine asn1_string_st-Struktur, aus der Binärdaten extrahiert und in das Hex-Format konvertiert werden:


    Holen Sie sich hexadezimale Binärdaten von ihrem ANS.1
    /**<summary>Получить Hex отображение бинарных данных из ASN.1</summary>
    * <param name="_hASN">Ссылка на элемент ASN.1</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <param name="_sHexData">Данные в формате Hex</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintGetBinaryHexFromASNOS(IntPtr _hASN, outstring _sHexData, refstring _sError) {
        _sHexData = "";
        try {
            asn1_string_st pSerial = (asn1_string_st)Marshal.PtrToStructure(_hASN, typeof(asn1_string_st));
            byte[] arStr = newbyte[pSerial.iLength];
            Marshal.Copy(pSerial.hData, arStr, 0, (int)pSerial.iLength);
            _sHexData = arStr.ToHex().ToUpper();
            return UConsts.S_OK;
        } catch (Exception E) {
            _sError = UCConsts.S_HEX_ASN_BINARY_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        }
    }
    

    Wie bei einer CryptoPro-Signatur reicht in der Regel eine Seriennummernsuche aus, um das Zertifikat eindeutig zu identifizieren.


    Verschlüsselung


    Die Verschlüsselung in OpenSSL ist ziemlich logisch:


    Datenverschlüsselung
    /**<summary>Зашифрованные данные</summary>
    * <param name="_arInput">Данные для расшифровки</param>
    * <param name="_pReceipients">Список сертфиикатов адресатов</param>
    * <param name="_arRes">Результат</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартный код с ошибкой, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintEncryptDataOS(byte[] _arInput, List<X509Certificate2> _pReceipients, outbyte[] _arRes, 
                                      refstring _sError) {
        _arRes           = newbyte[0];
        uint iFlags      = UCConsts.CMS_BINARY;
        IntPtr hData     = IntPtr.Zero;
        IntPtr hReceipts = IntPtr.Zero;
        IntPtr hBIORes   = IntPtr.Zero;
        IntPtr hCMS      = IntPtr.Zero;
        try {
            // 0) Сформировать BIO с данными для кодированияint iRes = GetBIOByBytesOS(_arInput, out hData, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            // 1) Формирование стека сертификатов адресатов
            iRes = GetCertsStackOS(_pReceipients, out hReceipts, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            // 2) Формирование CMS
            hCMS = UOpenSSLAPI.CMS_encrypt(hReceipts, hData, UOpenSSLAPI.EVP_des_ede3_cbc(), iFlags);
            if (hCMS == IntPtr.Zero) return RetErrOS(ref _sError, UCConsts.S_ENC_CMS_ERR);
            // 3) Запись CMS в BIO
            hBIORes = UOpenSSLAPI.BIO_new(UOpenSSLAPI.BIO_s_mem());
            if (!UOpenSSLAPI.i2d_CMS_bio_stream(hBIORes, hCMS, IntPtr.Zero, iFlags))
                 return RetErrOS(ref _sError, UCConsts.S_OS_CMS_EXP_TO_BIO_ERR);
            // 4) Преобразование из BIO в набор байтreturn ReadFromBIO_OS(hBIORes, out _arRes, ref _sError);
        } catch (Exception E) {
            _sError = UCConsts.S_ENC_OS_GEN_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if(hBIORes != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIORes);
            if(hData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hData);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
            if(hReceipts != IntPtr.Zero) UOpenSSLAPI.sk_free(hReceipts);
        }
    }
    

    Am Anfang des BIO wird mit den Daten und einer Liste der Zertifikate - Empfänger gebildet. Es soll die Adresse dieser Zertifikate verschlüsselt werden. Dann erfolgt die Verschlüsselung direkt und danach werden die Daten vom BIO in das Bytearray exportiert. OpenSSL verfügt über eine relativ große Anzahl von Verschlüsselungsalgorithmen, so dass eine eindeutige Auswahl unwahrscheinlich ist, wie dies bei CryptoPro der Fall war. Aus diesen Gründen sollten Sie entweder das häufig verwendete EVP_des_ede3_cbc verwenden oder diese Auswahl auf den Aufrufer verschieben.


    Um einen Stapel von Zertifikaten von Empfängern zu bilden, sollte eine separate Methode unterschieden werden, da der Parameter mit dem Stapel von Zertifikaten häufig in anderen OpenSSL-Methoden enthalten ist:


    Bildung eines Stapels von Zertifikaten
    /**<summary>Получить стек сертификатов</summary>* <param name="_hStack">Возвращаемый стек</param>
    * <param name="_pCerts">Список сертификатов</param>
    * <param name="_sError">Возвращаемая строка с ошибки</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/publicstaticintGetCertsStackOS(List<X509Certificate2> _pCerts, out IntPtr _hStack, refstring _sError) {
        _hStack       = IntPtr.Zero;
        IntPtr hStack = IntPtr.Zero;
        try {
            hStack = UOpenSSLAPI.sk_new_null();
            foreach (X509Certificate2 pCert in _pCerts) {
                // 0) Формируем класс, чтобы не было утечек
                ISDP_X509Cert pLocCert = ISDP_X509Cert.Convert(pCert, TCryptoPath.cpOpenSSL);
                // 1) Добавляем
                UOpenSSLAPI.sk_push(hStack, pLocCert.hRealHandle);
            }
            _hStack = hStack;
            hStack = IntPtr.Zero;
            return UConsts.S_OK;
        } catch (Exception E) { 
            _sError = UCConsts.S_GEN_CERT_STACK_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if(hStack != IntPtr.Zero) UOpenSSLAPI.sk_free(hStack);
        }
    }
    

    Entschlüsselung


    Die Datenentschlüsselung erfolgt auf dem Computer, auf dem der private Schlüssel eines der Empfänger installiert ist. Daher erfolgt der Entschlüsselungsvorgang wie folgt:


    1. eine Verschlüsselungsdatenstruktur wird basierend auf einem Datenfeld gebildet;
    2. ruft die Liste der Empfänger ab;
    3. Für jeden Adressaten versuchen wir, sein Zertifikat auf der Maschine zu finden und zu entschlüsseln.
    4. Wenn erfolgreich, führen Sie eine allgemeine Konvertierung durch.
    5. Entladen Sie dann die Daten aus dem generierten BIO in das Byte-Array.

    Datenentschlüsselung
    /**<summary>Дешифровывает данные</summary>
    * <param name="_arInput">Данные для расшифровки</param>
    * <param name="_arRes">Результат</param>
    * <param name="_pLocation">Местоположение хранилища, где искать</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <param name="_pCert">Сертификат</param>
    * <returns>Стандартный код ошибки, если UCOnsts.S_OK то все ок</returns>
    * **/internalstaticintDecryptDataOS(byte[] _arInput, out X509Certificate2 _pCert, outbyte[] _arRes, refstring _sError, 
                                      StoreLocation _pLocation = StoreLocation.CurrentUser ) {             
        _arRes = newbyte[0];
        _pCert = null;
        uint   iFlag    = UCConsts.CMS_BINARY;
        IntPtr hBIORes  = IntPtr.Zero;
        IntPtr hCMS     = IntPtr.Zero;
        X509Certificate2 pCert;
        try {
            // 0) Чтение структуры CMSint iRes = GetCMSFromBytesOS(_arInput, out hCMS, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            // 1) Прохоим по списку подписантов
            IntPtr hReceipts =  UOpenSSLAPI.CMS_get0_RecipientInfos(hCMS);
            int iCnt = UOpenSSLAPI.sk_num(hReceipts);                
            for(int i = 0; i < iCnt; i++) {                 
                IntPtr hRecep    = UOpenSSLAPI.sk_value(hReceipts, i);                    
                iRes = GetRecepInfoCertOS(hRecep, _pLocation, out pCert, ref _sError);
                if (iRes != UConsts.S_OK && iRes != UConsts.E_NO_CERTIFICATE) return iRes;
                // 1.1) Нет сертфиката if (iRes == UConsts.E_NO_CERTIFICATE)  continue;
                ISDP_X509Cert pLocCert = ISDP_X509Cert.Convert(pCert);
                // 1.2) Нет зарытого ключаif (pLocCert.hOSKey == IntPtr.Zero)  continue;
                // 1.3) Установка ключаif (!UOpenSSLAPI.CMS_RecipientInfo_set0_pkey(hRecep, pLocCert.hOSKey))
                     return RetErrOS(ref _sError, UCConsts.S_OS_CMS_SET_DEC_KEY_ERR);
                try {
                    // 1.4) Декодированиеif (!UOpenSSLAPI.CMS_RecipientInfo_decrypt(hCMS, hRecep))
                        return RetErrOS(ref _sError, UCConsts.S_OS_CMS_REC_DEC_ERR);
                } finally {
                    // !! Иначе два освобождения и ошибка
                    UOpenSSLAPI.CMS_RecipientInfo_set0_pkey(hRecep, IntPtr.Zero);
                }
                // 1.5) Общее декодирование
                hBIORes = UOpenSSLAPI.BIO_new(UOpenSSLAPI.BIO_s_mem());
                if (!UOpenSSLAPI.CMS_decrypt(hCMS, IntPtr.Zero, pLocCert.hRealHandle, IntPtr.Zero, hBIORes, iFlag))
                    return RetErrOS(ref _sError, UCConsts.S_OS_CMS_FULL_DEC_ERR);
                _pCert = pLocCert;
                // 2) Считываем полученные данные из BIO                    return ReadFromBIO_OS(hBIORes, out _arRes, ref _sError);
            }
            _sError = UCConsts.S_DEC_NO_CERT_ERR;
            return UConsts.E_NO_CERTIFICATE;
        } catch (Exception E) {
            _sError = UCConsts.S_DEC_GEN_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if(hBIORes != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIORes);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
        }
    }
    

    Ein wichtiges Merkmal ist die Notwendigkeit, den privaten Schlüssel zu verfolgen. Die Tatsache ist, dass, wenn die Verknüpfung zu ihr durch die Methode CMS_RecipientInfo_set0_pkey im Empfänger fixiert wird, diese zusammen mit der CMS-Struktur freigegeben wird. Wenn Sie versuchen, sie zusammen mit dem Zertifikat freizugeben, fällt das System mit einem Fehler aus.


    Um die Daten zu entschlüsseln, müssen die Empfängerinformationen in ein Zertifikat umgewandelt werden. Wie beim Unterzeichner werden die Seriennummer und der Aussteller des Zertifikats in den Daten gespeichert. Zur Vereinfachung können Sie die zu suchende Seriennummer einschränken:


    Erhalt des Zertifikats des Empfängers
    /**<summary>Получение сертификата адресата</summary>
    * <param name="_hRecep">Информация об адресате</param>
    * <param name="_pCert">Возврат сертификата</param>
    * <param name="_pLocation">Расположения хранилища для поиска</param>
    * <param name="_sError">Возвращаемая строкка с ошибкой</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintGetRecepInfoCertOS(IntPtr _hRecep, StoreLocation _pLocation,
                                           out X509Certificate2 _pCert, refstring _sError) {
        _pCert = null;
        try {
             // 0) Получаем информацию об адресате
             IntPtr hKey    = IntPtr.Zero;
             IntPtr hIssuer = IntPtr.Zero;
             IntPtr hSNO    = IntPtr.Zero;
             if (!UOpenSSLAPI.CMS_RecipientInfo_ktri_get0_signer_id(_hRecep, ref hKey,
                                                                    ref hIssuer, ref hSNO))
                 return RetErrOS(ref _sError, UCConsts.S_GET_RECEIP_INFO_ERR);
             // 1) Извлекается серийный номерstring sSerial;
             int iRes = GetBinaryHexFromASNOS(hSNO, out sSerial, ref _sError);
             if(iRes != UConsts.S_OK) return iRes;
             // 2) Ищем сертификат
             iRes = FindCertificateOS(sSerial, out _pCert, ref _sError, _pLocation, 
                                      StoreName.My, X509FindType.FindBySerialNumber);
             if(iRes != UConsts.S_OK) return iRes;
             return UConsts.S_OK;
        } catch (Exception E) {
             _sError = UCConsts.S_GET_RECEIP_INFO_GEN_ERR.Frm(E.Message);
             return UConsts.E_GEN_EXCEPTION;
        }
    }
    

    Die Methode CMS_RecipientInfo_ktri_get0_signer_id ruft Informationen über das Zertifikat des Empfängers ab, und die Seriennummer selbst wird aus der Struktur extrahiert, auf die die Variable hSNO durch die bereits beschriebene Methode verweist. Es bleibt das Zertifikat anhand der Seriennummer zu finden.


    Die Struktur der Daten des Adressaten kann im Allgemeinen unterschiedlich sein . Beim Verschlüsseln von Dokumenten ist ktri jedoch der gebräuchlichste Typ - mit der Speicherung von Informationen zu öffentlichen Schlüsseln. OpenSSL hat auch mehrere Methoden für die Arbeit mit anderen Typen implementiert: CMS_RecipientInfo_kari_ *, CMS_RecipientInfo_kekri_ * und CMS_RecipientInfo_set0_password für pwri.


    Zertifikatsüberprüfung


    Ein Zertifikat ist nicht nur ein öffentlicher Schlüssel, sondern auch Informationen zu einem vom Herausgeber dieses Zertifikats zertifizierten Unterzeichner. Diese Informationen sind nur für eine begrenzte Zeit relevant und der Herausgeber kann seine Unterschrift widerrufen. All dies macht uns auf die Gültigkeit des Zertifikats aufmerksam, da es die Gültigkeit der mit seiner Hilfe durchgeführten Aktionen direkt bestimmt. In OpenSSL ist die Zertifikatsüberprüfung ziemlich gut automatisiert. In der Tat müssen wir nur einen Zertifikatsspeicher (im Speicher oder auf der Festplatte) erstellen, der verkettet wird, das Datum der Überprüfung und eine Reihe von Flags angibt.

    Das Zertifikat muss für immer vollständig geprüft werden, so dass die Prüfflaggen meistens gleich installiert werden:


    Zertifikatsüberprüfung
    /**<summary>Проверить сертификат в рамках OpenSSL</summary>
    * <param name="_iRevFlag">Флаг отзыва</param>
    * <param name="_iRevMode">Режим отзыва</param>
    * <param name="_hCert">контекст сертфиката</param>
    * <param name="_rOnDate">Дата верификацмм</param>
    * <param name="_pLocation">Местоположение проверки</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintVerifyCertificateOS(IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag,
                                            StoreLocation _pLocation, DateTime _rOnDate, refstring _sError) {
        IntPtr hStore   = IntPtr.Zero;
        IntPtr hStoreCtx = IntPtr.Zero;
        try {                                            
            // 0) Формируем хранилищеint iRes = GetTrustStoreOS(_pLocation, out hStore, ref _sError);
            if(iRes != UConsts.S_OK) return iRes;
            // 1) Формируем контекст проверки
            hStoreCtx = UOpenSSLAPI.X509_STORE_CTX_new();
            if (!UOpenSSLAPI.X509_STORE_CTX_init(hStoreCtx, hStore, _hCert, IntPtr.Zero)) {
                _sError = UCConsts.S_CRYPTO_CONTEXT_CER_ERR;
                return UConsts.E_CRYPTO_ERR;
            }
            // 2) Устанавливаем дату проверки и доп флаги
            SetStoreCtxCheckDate(hStoreCtx, _rOnDate);
            // 3) Проверкаif (!UOpenSSLAPI.X509_verify_cert(hStoreCtx)) {
                _sError = UCConsts.S_CRYPTO_CHAIN_CHECK_ERR.Frm(GetCertVerifyErr(hStoreCtx));
                return UConsts.E_CRYPTO_ERR;
            }
            return UConsts.S_OK;
        } finally {
            if (hStore != IntPtr.Zero) UOpenSSLAPI.X509_STORE_free(hStore);
            if (hStoreCtx != IntPtr.Zero) UOpenSSLAPI.X509_STORE_CTX_free(hStoreCtx);
        }
    }
    

    Im generierten Verifikationskontext (X509_STORE_CTX) müssen Sie das Datum und die Flags eingeben. Es ist bequemer, dies in einer separaten Methode zu tun:


    Datum einstellen und Flags prüfen
    /**<summary>Установить дату проверки сертификата</summary>
    * <param name="_hStoreCtx">Контекст хранилища</param>
    * <param name="_rDate">Дата</param>
    * **/publicstaticvoidSetStoreCtxCheckDate(IntPtr _hStoreCtx, DateTime _rDate) {
        uint iFlags = UCConsts.X509_V_FLAG_USE_CHECK_TIME | UCConsts.X509_V_FLAG_X509_STRICT | 
                      UCConsts.X509_V_FLAG_CRL_CHECK_ALL;
        // Установка флагов
        UOpenSSLAPI.X509_STORE_CTX_set_flags(_hStoreCtx, iFlags);
        // Установка времени
        UOpenSSLAPI.X509_STORE_CTX_set_time(_hStoreCtx, iFlags, (uint)_rDate.ToUnix());
        // Установка обязательств - все верифифированны
        UOpenSSLAPI.X509_STORE_CTX_set_trust(_hStoreCtx, UCConsts.X509_TRUST_TRUSTED);
    }
    

    Wie bereits beschrieben, werden Zertifikatsüberprüfungsfehler in eine separate Liste aufgenommen und von einer separaten Methode extrahiert.


    Fazit


    Diese Ausführungsform wurde gemäß dem im vorigen Artikel beschriebenen Algorithmus getestet. Nachdem das zuvor beschriebene Leck in X509Certificate2 (Mono) beseitigt wurde, funktioniert es stabil ohne Speicherverluste. Die Geschwindigkeit ist vergleichbar mit der Arbeit der CryptoPro-Bibliothek.


    Im Allgemeinen ist diese Bibliothek für Windows aufgrund der Besonderheiten des Speicherns und Arbeitens mit privaten Schlüsseln nicht besonders geeignet. Daher ist es wahrscheinlicher, Standard-Kryptographieverfahren zu verwenden. Wenn Sie unter Linux die Notwendigkeit zum Entladen privater Schlüssel für die Nicht-GOST-Verschlüsselung schließen, ist dies eine durchaus funktionierende Option.


    Zur gleichen Zeit hat CryptoPro bereits CSP 5.0 veröffentlicht , das die Unterstützung für RSA-Zertifikate versprach. Den Informationen auf ihrer Website nach zu urteilen, obwohl sie noch nicht zertifiziert sind, aber wenn dies der Fall ist, verwendet der Systembetrieb gleichzeitig GOST-Schlüssel und RSA. Es ist wahrscheinlich logischer, darauf aufzubauen.


    Links


    1. OpenSSL 1.0.2 ManPages ;
    2. Multithreading in OpenSSL 1 und 2 ;
    3. OpenSSL- Quellen :
      1. cms_smime.c;
    4. OpenSSL Wiki ;
    5. Mono-Quellen:
      1. Klasse RSAManaged ;


    Jetzt auch beliebt: