КриптоПро TSP SDK: Низкоуровневый интерфейс

Низкоуровневый интерфейс КриптоПро TSP

Низкоуровневый интерфейс из состава КриптоПро TSP SDK предназначен для работы со штампами времени. Использование данного интерфейса упрощает реализацию служб штампов и делает возможным встраивание службы в существующую систему.

Низкоуровневый интерфейс представляет собой библиотеку классов C++.

В библиотеке реализована поддержка структур данных, описанных в RFC 3161, и штампов времени, используемых Microsoft для подписи исполняемых файлов (в технологии Authenticode).

Состав

Для поддержки протокола штампов времени, описанного в RFC 3161, предоставляются следующие классы:

Для поддержки штампов времени Microsoft предоставляются классы:

Особенности

Доступ к полям структур в библиотеке осуществляется следующим способом:

Основные классы библиотеки (запросы, ответы, штампы) содержат методы encode()/decode(), выполняющие операцию кодирования в ASN.1 DER-представление и декодирования из него. Классы, работающие со структурами, которые содержат подпись (CToken, CMSStamp), обеспечивают операции подписи и проверки подписи (sign()/verify()), а также доступ к информации о подписи (SignerInfo). Используя конструктор по умолчанию, можно создать пустой объект, для последующего вызова функции decode(). Во всех остальных случаях, если существует конструктор с аргументами, использование конструктора по умолчанию не рекомендуется, так как перед вызовом encode() все обязательные поля структуры должны быть проинициализированы корректными значениями. Конструкторы с аргументами заполняют эти поля при создании объекта, а конструкторы по умолчанию - нет, что допускает вероятность того, что объект не будет правильно проинициализирован перед вызовом encode().

Представление объектов в виде ASN.1 DER, используется для их транспортировки между клиентом и сервером. Таким образом, есть два типичных сценария работы с классами библиотеки:

При возникновении ошибок методы классов данной библиотеки могут генерировать следующие виды исключений: std::exception и ATL::CAtlException.

Создание сообщения

Основные этапы создания сообщения:
  1. Создание объекта
  2. Заполнение полей в случае необходимости (добавление опциональных или изменение обязательных, заданных по умолчанию)
  3. В случае, если сообщение содержить подпись, вызов метода sign()
  4. Вызов метода encode() для получения ASN.1 DER-представления объекта в виде блока бинарных данных

Пример (создание запроса на штамп времени):

    //создание запроса на штамп времени
    
    //алгоритм хэша (параметры нулевые)
    CryptoPro::ASN1::CAlgorithmIdentifierEx hashAlgorithm;
    hashAlgorithm.put_algorithm(hashAlgorithmOID);
    
    //подсчет хэша
    CryptoPro::CBlob hashBlob = ::MakeHash(0,hashAlgorithm.get_AlgId(),data);

    // cоздается запрос
    CryptoPro::PKI::TSP::CRequest tsRequest(hashAlgorithm,hashBlob,certReq);
    
    // здесь установка опциональных параметров
    tsRequest.put_reqPolicy(policyOID);
    // 64-битное случайное число
    CryptoPro::ASN1::CBigInteger nonce = ::MakeRandomBigInteger(0,8);
    tsRequest.put_nonce(&nonce);
    // extensions;

    // кодируется запрос в блоб
    return tsRequest.encode();

Пример (создание штампа времени Microsoft):

    // содание штампа времени
    // вкладывается request.contentInfo.content и время штампа
    CryptoPro::CDateTime time = CryptoPro::CDateTime::Now();

    CryptoPro::PKI::TSP::CMSStamp timeStamp( request.get_contentInfo().get_content(), time);

    // получение дескриптора закрытого ключа, на котором будет подписан штамп
    HCRYPTPROV hProv = 0;
    DWORD dwKeySpec = 0;

    ::FindInStoreAndAcquirePrivateKey(
        tsaCert,
        tsaStore.c_str(),
        false,
        hProv,
        dwKeySpec);

    CryptoPro::ASN1::CAlgorithmIdentifierEx hashAlgorithm;
    hashAlgorithm.put_algorithm(hashOID);

    CryptoPro::ASN1::CEncodedCertificateList certs;

    certs.push_back(tsaCert);

    // вложение дополнительных сертификатов (опционально)
    timeStamp.put_certificates(&certs);

    // подпись
    timeStamp.sign(
        hProv,
        dwKeySpec,
        tsaCert,
        hashAlgorithm);

    return timeStamp.encode();

Декодирование сообщения

Основные методы декодирования сообщения:
  1. Создание объекта конструктором по умолчанию
  2. Вызов метода decode(), аргументом которого является ASN.1 DER-представление сообщения
  3. Вызов методов get_...() для получения полей сообщения.

Пример (создание штампа времени):

    // OID политики
    const char * policy;

    // серийный номер штампа
    CryptoPro::ASN1::CBigInteger serialNumber = 
        ::MakeRandomBigInteger(0/*нулевой hProv - будет открыт внутри*/,10);
    serialNumber++;
    // время создания штампа
    CryptoPro::CDateTime genTime = CryptoPro::CDateTime::Now();

    // создание штампа времени
    CryptoPro::PKI::TSP::CToken tsToken( 
        policy,
        tsRequest.get_hashAlgorithm(),
        tsRequest.get_hashedMessage(),
        serialNumber,
        genTime,
        true);

    // заполняются опциональные атрибуты штампа времени

    // переносим случайное число из запроса
    tsToken.put_nonce( tsRequest.get_nonce() );

    // обязательный атрибут SigningCertificate
    // RFC 3161 2.4.2
    CryptoPro::ASN1::CESSCertIDList ids;
    ids.push_back( ESSCertIDFromCertificate(encodedTSACertificate) );
    CryptoPro::ASN1::CAttrSigningCertificate attrSigningCertificate(ids);
    CryptoPro::ASN1::CAttribute attr(attrSigningCertificate.get_oid());
    attr.add(attrSigningCertificate);

    // атрибут OtherSigningCertificate
    CryptoPro::ASN1::CAlgorithmIdentifierEx certHashAlgorithm;
    certHashAlgorithm.put_algorithm(szOID_OIWSEC_sha1);
    CryptoPro::ASN1::COtherCertIDList otherIds;
    otherIds.push_back( OtherCertIDFromCertificate(encodedTSACertificate,certHashAlgorithm) );
    CryptoPro::ASN1::CAttrOtherSigningCertificate attrOtherSigningCertificate(otherIds);
    CryptoPro::ASN1::CAttribute attr1(attrOtherSigningCertificate.get_oid());
    attr1.add(attrOtherSigningCertificate);
    CryptoPro::ASN1::CAttributes attrs;
    attrs.push_back(attr);
    attrs.push_back(attr1);

    tsToken.put_signedAttributes(&attrs);

    // если certReq равен true добавляем сертификат
    if( tsRequest.get_certReq() )
    {
        CryptoPro::ASN1::CEncodedCertificateList certificateList;
        certificateList.push_back(encodedTSACertificate);

        tsToken.put_certificates(&certificateList);
    }

    // устанавливаем точность
    CryptoPro::PKI::TSP::CAccuracy accuracy(1,2,3);
    tsToken.put_accuracy(&accuracy);

    // subject службы штампов времени
    CryptoPro::CBlob tsaSubject(
        pCertStoreContext->pCertInfo->Subject.pbData,
        pCertStoreContext->pCertInfo->Subject.cbData);
    CryptoPro::ASN1::CGeneralName tsa;
    tsa.put_directoryName(tsaSubject);
    tsToken.put_tsa(&tsa);

    // алгоритм для хэширования сообщения
    CryptoPro::ASN1::CAlgorithmIdentifierEx msgHashAlgorithm(szOID_OIWSEC_sha1);

    // подписывание штампа
    tsToken.sign(hProv,dwKeySpec,encodedTSACertificate,msgHashAlgorithm);

//    return tsToken;

Пример (разбор полученного штампа времени Microsoft):

    CryptoPro::PKI::TSP::CMSStamp timeStamp;
    timeStamp.decode(encodedResponse);

    // получение информации о подписчике
    CryptoPro::ASN1::CSignerInfo signer = timeStamp.get_signerInfo();

    // поиск сертификата подписчика
    CCertStore store;
    HRESULT hr = store.InitSystemStore(
        L"My",
        CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER);
    if(FAILED(hr))
        ATL::AtlThrowImpl(hr);

    CryptoPro::CBlob timeStampAuthorityCertificate =
        ::FindCertificateInStoreBySigner( store, signer);

    // проверка подписи
    if(!timeStamp.verify(timeStampAuthorityCertificate))
        throw std::runtime_error("Authenticode time stamp signature is not verified.");

    // проверка на соответствие запросу
    CryptoPro::CBlob encodedRequest;
    encodedRequest.readFromFile("request.der");

    CryptoPro::PKI::TSP::CMSRequest request;
    request.decode(encodedRequest);

    if( request.get_contentInfo().get_content() != timeStamp.get_content() )
        throw std::runtime_error("Authenticode time stamp content doesn't match.");

    // получение времени штампа
    timeStamp.get_signingTime();
    // получение дополнительных сертификатов
    timeStamp.get_certificates();