V
V
Vanes Ri_Lax2020-01-21 09:50:44
C++ / C#
Vanes Ri_Lax, 2020-01-21 09:50:44

How to sign a string for ESIA?

Hello!
There is a task to authorize the user on the site through ESIA.
There is a GOST 2012 certificate issued through CryptoPRO.
Installed on the computer

КриптоПРО CSP
КриптоПРО .NET
КриптоПРО .NET SDK

I took an example here
cpdn.cryptopro.ru/content/cpnet/html/ba76dcf8-4693...
Here are the sources of this example:
using System;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.IO;

namespace Samples.CMS
{
    class DetachedSignature
    {
        [STAThread]
        static void Main(string[] args)
        {
            // Проверка корректности переданных параметров.
            if (args.Length < 1)
            {
                Console.WriteLine("CMS.DetachedSignature <cert-subject>");
                return;
            }

            String signerName = args[0];

            // Исходное сообщение.
            const String msg = "Это сообщение, которое будет подписано.";

            Console.WriteLine("{0}Исходное сообщение (длина {1}): {2}  ",
                Environment.NewLine, msg.Length, msg);

            // Переводим исходное сообщение в массив байтов.
            Encoding unicode = Encoding.Unicode;
            byte[] msgBytes = unicode.GetBytes(msg);

            Console.WriteLine("{0}{0}------------------------------",
                Environment.NewLine);
            Console.WriteLine(" Поиск сертификата            ");
            Console.WriteLine("------------------------------{0}",
                Environment.NewLine);

            // Получаем сертификат ключа подписи;
            // он будет использоваться для получения 
            // секретного ключа подписи.
            X509Certificate2 signerCert = GetSignerCert(signerName);

            Console.WriteLine("{0}{0}------------------------------",
                Environment.NewLine);
            Console.WriteLine(" На стороне отправителя");
            Console.WriteLine("------------------------------{0}",
                Environment.NewLine);

            byte[] encodedSignature = SignMsg(msgBytes, signerCert);
            File.WriteAllBytes("signature.bin", encodedSignature);

            Console.WriteLine("{0}{0}------------------------------",
                Environment.NewLine);
            Console.WriteLine(" На стороне получателя  ");
            Console.WriteLine("------------------------------{0}",
                Environment.NewLine);

            // При проверка detached подписи передаем и само сообщение
            if (VerifyMsg(msgBytes, encodedSignature))
            {
                Console.WriteLine("{0}Сообщение проверено.",
                    Environment.NewLine);
            }
            else
            {
                Console.WriteLine("{0}Ошибка при проверке сообщения.",
                    Environment.NewLine);
            }
        }

        // Открываем хранилище 'My' и ищем сертификат
        // для подписи сообщения. 
        static X509Certificate2 GetSignerCert(string signerName)
        {
            // Открываем хранилище My.
            X509Store storeMy = new X509Store(StoreName.My,
                StoreLocation.CurrentUser);
            storeMy.Open(OpenFlags.ReadOnly);

            // Отображаем сертификаты для удобства работы
            // с примером.
            Console.WriteLine("Найдены сертификаты следующих субъектов " +
                "в хранилище {0}:", storeMy.Name);
            foreach (X509Certificate2 cert in storeMy.Certificates)
            {
                Console.WriteLine("\t{0}", cert.SubjectName.Name);
            }

            // Ищем сертификат для подписи.
            X509Certificate2Collection certColl =
                storeMy.Certificates.Find(X509FindType.FindBySubjectName,
                signerName, false);
            Console.WriteLine(
                "Найдено {0} сертификат(ов) в хранилище {1} для субъекта {2}",
                certColl.Count, storeMy.Name, signerName);

            // Проверяем, что нашли требуемый сертификат
            if (certColl.Count == 0)
            {
                Console.WriteLine(
                    "Сертификат для данного примера не найден " +
                    "в хранилище. Выберите другой сертификат для подписи. ");
                return null;
            }

            storeMy.Close();

            // Если найдено более одного сертификата,
            // возвращаем первый попавщийся.
            return certColl[0];
        }

        // Подписываем сообщение секретным ключем.
        static byte[] SignMsg(
            Byte[] msg,
            X509Certificate2 signerCert)
        {
            // Создаем объект ContentInfo по сообщению.
            // Это необходимо для создания объекта SignedCms.
            ContentInfo contentInfo = new ContentInfo(msg);

            // Создаем объект SignedCms по только что созданному
            // объекту ContentInfo.
            // SubjectIdentifierType установлен по умолчанию в 
            // IssuerAndSerialNumber.
            // Свойство Detached устанавливаем явно в true, таким 
            // образом сообщение будет отделено от подписи.
            SignedCms signedCms = new SignedCms(contentInfo, true);

            // Определяем подписывающего, объектом CmsSigner.
            CmsSigner cmsSigner = new CmsSigner(signerCert);

            // Подписываем CMS/PKCS #7 сообение.
            Console.Write("Вычисляем подпись сообщения для субъекта " +
                "{0} ... ", signerCert.SubjectName.Name);
            signedCms.ComputeSignature(cmsSigner);
            Console.WriteLine("Успешно.");

            // Кодируем CMS/PKCS #7 подпись сообщения.
            return signedCms.Encode();
        }

        // Проверяем SignedCms сообщение и возвращаем Boolean
        // значение определяющее результат проверки.
        static bool VerifyMsg(Byte[] msg, 
            byte[] encodedSignature)
        {
            // Создаем объект ContentInfo по сообщению.
            // Это необходимо для создания объекта SignedCms.
            ContentInfo contentInfo = new ContentInfo(msg);

            // Создаем SignedCms для декодирования и проверки.
            SignedCms signedCms = new SignedCms(contentInfo, true);

            // Декодируем подпись
            signedCms.Decode(encodedSignature);

            // Перехватываем криптографические исключения, для 
            // возврата о false значения при некорректности подписи.
            try
            {
                // Проверяем подпись. В данном примере не 
                // проверяется корректность сертификата подписавшего.
                // В рабочем коде, скорее всего потребуется построение
                // и проверка корректности цепочки сертификата.
                Console.Write("Проверка подписи сообщения ... ");
                signedCms.CheckSignature(true);
                Console.WriteLine("Успешно.");
            }
            catch (System.Security.Cryptography.CryptographicException e)
            {
                Console.WriteLine("Функция VerifyMsg возбудила исключение:  {0}",
                    e.Message);
                Console.WriteLine("Проверка PKCS #7 сообщения завершилась " +
                    "неудачно. Возможно сообщене, подпись, или " +
                    "соподписи модифицированы в процессе передачи или хранения. " +
                    "Подписавший или соподписавшие возможно не те " +
                    "за кого себя выдают. Достоверность и/или целостность " +
                    "сообщения не гарантируется. ");
                return false;
            }

            return true;
        }
    }
}

Everything seems to be simple and clear.
I imported my certificate into the MY store and started writing an application that takes the certificate string and number as input. Next, the application looks for this certificate in the storage by number and signs the string with it.
After everything encodes in Base64 URL safe.
Next, I generate the URL that the site user should go to. I go to this URL and ESIA returns me the following:
error_description: ESIA-007005: The client is not authorized to request an access token using this method.
state: 35805cc9-8b2f-4f85-b30b-8f3e88d215ca
error: unauthorized_client

What could be the problem?

Answer the question

In order to leave comments, you need to log in

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question