Validation of electronic signatures on C # using CRIPTO PRO

    Continuing the conversation on the topic of electronic signatures (hereinafter referred to as ES), it is necessary to say about verification. In the previous article I examined the more difficult part of the task - the creation of a signature. This article is somewhat simpler. Most of the code is an adaptation of examples from the CRYPTO PRO .NET SDK. First of all, we will check signatures according to GOST R 34.10-2001 and GOST R 34.10-2012, for which we need the CRIPTO PRO.

    The task for us is divided into 3 parts: a separated signature, a signature in PDF and a signature in MS Word.

    Separate signature verification:

    //dataFileRawBytes - массив байт подписанного файла
                ContentInfo contentInfo = new ContentInfo(dataFileRawBytes);
                SignedCms signedCms = new SignedCms(contentInfo, true);
                //signatureFileRawBytes - массив байт подписи
                signedCms.Decode(signatureFileRawBytes);
                if (signedCms.SignerInfos.Count == 0)
                {
                    //обработка в случае отсутствия подписей
                }
                foreach (SignerInfo signerInfo in signedCms.SignerInfos)
                {
                    //получаем дату подписания
                    DateTime? signDate =
                        (signerInfo.SignedAttributes
                                .Cast<CryptographicAttributeObject>()
                                .FirstOrDefault(x => x.Oid.Value == "1.2.840.113549.1.9.5")
                                ?.Values[0]
                            as Pkcs9SigningTime)?.SigningTime;
                    bool valid;
                    try
                    {
                        signerInfo.CheckSignature(true);
                        valid = true;
                    }
                    catch (CryptographicException exc)
                    {
                        valid = false;
                    }
                    //получаем сертификат для проверки. Пригодится при проверке сертификата
                    X509Certificate2 certificate = signerInfo.Certificate;
    

    Comments are all in the code, I will only turn your attention to obtaining a certificate, we will need it further, because certificate we will check separately.

    Well, do not forget to wrap everything in try-catch and other using. In the example, I deliberately do not do this to reduce the amount of

    signature validation in PDF. Here we need iTextSharp (the current version at the time of writing 5.5.13):

    using (MemoryStream fileStream = new MemoryStream(dataFileRawBytes))
                using (PdfReader pdfReader = new PdfReader(fileStream))
                {
                    AcroFields acroFields = pdfReader.AcroFields;
                    //получаем названия контейнеров подписей
                    List<string> signatureNames = acroFields.GetSignatureNames();
                    if (!signatureNames.Any())
                    {
                        //обработка отсутствия ЭП
                    }
                    foreach (string signatureName in signatureNames)
                    {
                        //далее следует магия получения подписи из контейнера
                        PdfDictionary singleSignature = acroFields.GetSignatureDictionary(signatureName);
                        PdfString asString1 = singleSignature.GetAsString(PdfName.CONTENTS);
                        byte[] signatureBytes = asString1.GetOriginalBytes();
                        RandomAccessFileOrArray safeFile = pdfReader.SafeFile;
                        PdfArray asArray = singleSignature.GetAsArray(PdfName.BYTERANGE);
                        using (
                            Stream stream =
                                new RASInputStream(
                                    new RandomAccessSourceFactory().CreateRanged(
                                        safeFile.CreateSourceView(),
                                        asArray.AsLongArray())))
                        {
                            using (MemoryStream ms = new MemoryStream((int)stream.Length))
                            {
                                stream.CopyTo(ms);
                                byte[] data = ms.GetBuffer();
                                ContentInfo contentInfo = new ContentInfo(data);
                                SignedCms signedCms = new SignedCms(contentInfo, true);
                                signedCms.Decode(signatureBytes);
                                bool checkResult;
                                //получили подпись и проверяем её, без проверки сертификатаtry
                                {
                                    signedCms.CheckSignature(true);
                                    checkResult = true;
                                }
                                catch (Exception)
                                {
                                    checkResult = false;
                                }
                                foreach (SignerInfo signerInfo in signedCms.SignerInfos)
                                {
                                    //получаем дату подписания
                                    DateTime? signDate = (signerInfo.SignedAttributes
                                            .Cast<CryptographicAttributeObject>()
                                            .FirstOrDefault(x =>
                                                x.Oid.Value == "1.2.840.113549.1.9.5")
                                            ?.Values[0]
                                        as Pkcs9SigningTime)?.SigningTime;
                                    //получаем сертификат
                                    X509Certificate2 certificate = signerInfo.Certificate;
                                }
                            }
                        }
                    }
                }
    

    There is nothing much to comment again. Unless it is necessary to say about Oid "1.2.840.113549.1.9.5" is the Oid of the date of signing.

    And the last one on our list is docx, perhaps the easiest option:

    using (MemoryStream fileStream = new MemoryStream(dataFileRawBytes))
                using (Package filePackage = Package.Open(fileStream))
                {
                    PackageDigitalSignatureManager digitalSignatureManager =
                        new PackageDigitalSignatureManager(filePackage);
                    if (!digitalSignatureManager.IsSigned)
                    {
                        //обрабатываем ситуацию отсутствия подписей
                    }
                    foreach (PackageDigitalSignature signature in
                        digitalSignatureManager.Signatures)
                    {
                        DateTime? signDate = signature.SigningTime;
                        bool checkResult = signature.Verify() == VerifyResult.Success;
                        //обратите внимание на способ получения сертификата
                        X509Certificate2 certificate =
                            new X509Certificate2(signature.Signer);
                    }
                }
    

    Now we will disassemble the certificate and validate the entire chain of certificates. Therefore, the assembly should work from a user who has access to the network.

    And here hell begins, because I do not know how to get information about the certificate owner through Oid, so I will parse the string. Laugh louder: the circus begins.

    But seriously, you are welcome to the comments of those who know how to do it through Oid-s:

    privatestaticvoidFillElectronicSignature(X509Certificate2 certificate)
            {
                foreach (KeyValuePair<string, string> item inParseCertificatesSubject(certificate.Subject))
                {
                    switch (item.Key)
                    {
                        case"C":
                            string certificatesCountryName =
                                item.Value;
                            break;
                        case"S":
                            string certificatesState =
                                item.Value;
                            break;
                        case"L":
                            string certificatesLocality =
                                item.Value;
                            break;
                        case"O":
                            string certificatesOrganizationName =
                                item.Value;
                            break;
                        case"OU":
                            string certificatesOrganizationalUnitName =
                                item.Value;
                            break;
                        case"CN":
                            string certificatesCommonName =
                                item.Value;
                            break;
                        case"E":
                            string certificatesEmail =
                                item.Value;
                            break;
                        case"STREET":
                            string certificatesStreet =
                                item.Value;
                            break;
                        //тут интересный момент, если Window русскоязычный, то КРИПТО ПРО вернёт ИНН, а если англоязычный, то INN//именно тут начиналась не пойми что после deploy на тестовый стенд//локально работает, на тестовом - нетcase"ИНН":
                        case"INN":
                        case"1.2.643.3.131.1.1":
                            string certificatesInn =
                                item.Value;
                            break;
                        //аналогично предыдущемуcase"ОГРН":
                        case"OGRN":
                        case"1.2.643.100.1":
                            string certificatesOgrn =
                                item.Value;
                            break;
                        //аналогично предыдущемуcase"СНИЛС":
                        case"SNILS":
                        case"1.2.643.100.3":
                            string certificatesSnils =
                                item.Value;
                            break;
                        case"SN":
                            string certificatesOwnerLastName =
                                item.Value;
                            break;
                        case"G":
                            string certificatesOwnerFirstName =
                                item.Value;
                            break;
                        //тут рекомендую добавить блок default и всё что не удалось определить ранее писать в лог
                    }
                }
                DateTime certificateNotBefore =
                    certificate.NotBefore;
                DateTime certificateNotAfter =
                    certificate.NotAfter;
                string certificatesSerialNumber =
                    certificate.SerialNumber;
                if (!certificate.Verify())
                {
                    //строим цепочку сертификатовusing (X509Chain x509Chain = new X509Chain())
                    {
                        x509Chain.Build(certificate);
                        //получаем все ошибки цепочки
                        X509ChainStatus[] statuses = x509Chain.ChainStatus;
                        //собираем все флаги ошибок в один int, так проще хранитьint certificatesErrorCode =
                            statuses.Aggregate(X509ChainStatusFlags.NoError,
                                (acc, chainStatus) => acc | chainStatus.Status, result => (int)result);
                    }
                }
            }
            ///<summary>/// Разобрать строку с данными о владельце сертификата///</summary>privatestatic Dictionary<string, string> ParseCertificatesSubject(string subject)
            {
                Dictionary<string, string> result = new Dictionary<string, string>();
                //количество двойных кавычек, для определения конца значенияint quotationMarksCount = 0;
                //признак что сейчас обрабатывается "ключ или значение"bool isKey = true;
                //переменная для сбора ключаstring key = string.Empty;
                //Переменная для сбора значенияstringvalue = string.Empty;
                for (int i = 0; i < subject.Length; i++)
                {
                    char c = subject[i];
                    if (isKey && c == '=')
                    {
                        isKey = false;
                        continue;
                    }
                    if (isKey)
                        key += c;
                    else
                    {
                        if (c == '"')
                            quotationMarksCount++;
                        bool isItemEnd = (c == ',' && subject.Length >= i + 1 && subject[i + 1] == ' ');
                        bool isLastChar = subject.Length == i + 1;
                        if ((isItemEnd && quotationMarksCount % 2 == 0) || isLastChar)
                        {
                            if (isItemEnd)
                                i++;
                            if (isLastChar)
                                value += c;
                            isKey = true;
                            if (value.StartsWith("\"") && value.EndsWith("\""))
                                value = value.Substring(1, value.Length - 2);
                            value = value.Replace("\"\"", "\"");
                            result.Add(key, value);
                            key = string.Empty;
                            value = string.Empty;
                            quotationMarksCount = 0;
                            continue;
                        }
                        value += c;
                    }
                }
                return result;
            }
    

    The code is shortened as much as possible, for a better understanding of the essence.

    In general, this is all, waiting for comments on obtaining Oid-s from the certificate and any reasoned criticism.

    Also popular now: