Пакет Network Security Services и утилита Pretty-print

    Пакет Network Security Services (NSS) представляет собой набор библиотек, используемых при кроссплатформенной разработке защищенных клиентских и серверных приложений.

    Пакет NSS, также как и OpenSSL, предоставляет возможность использования для реализации различных функций PKI (генерация ключей, выпуск сертификатов x509v3, работа с электронной подписью, поддержка TLS и т.д.) утилиты командной строки. Одна из таких утилит, а именно Pretty-print (PP), позволяет просматривать в удобном виде содержимое как сертификата x509 v3, так и электронную подпись (pkcs#7) и т.д. При чем сертификат может быть как в DER, так и PEM-кодировках:

    bash-4.3$ pp -h 
    Usage:  pp [-t type] [-a] [-i input] [-o output] [-w] [-u]  
    Pretty prints a file containing ASN.1 data in DER or ascii format.  -t type        Specify input and display 
    type: 
                  public-key (pk), certificate (c), certificate-request (cr), 
                  certificate-identity (ci), pkcs7 (p7), crl or name (n). 
                  (Use either the long type name or the shortcut.) 
    -a             Input is in ascii encoded form (RFC1113) 
    -i input       Define an input file to use (default is stdin) 
    -o output      Define an output file to use (default is stdout) 
    -w             Don't wrap long output lines 
    -u             Use UTF-8 (default is to show non-ascii as .) 
    bash-4.3$

    Более того, наличие параметра –u (кодировка UTF-8) позволяет просматривать сертификата в русской кодировке. Но вот внимательно вглядываясь в скриншоты графического интерфейса к утилитам командной строки пакета NSS замечаешь, что часть данных сертификата просто исчезла:

    image

    Начался поиск пропавшей информации. Утилита «симпатичной печати» (а именно так переводится Pretty-print) для просмотра корневого сертификата Головного УЦ Минкомсвязи была запущена в командной строке:

    $pp – certificate –u –i ГУЦ_Минкомсвязи.cer 
    …
           Subject: "CN=Головной удостоверяющий центр 
               ,INN=007710474375,OGRN=1047702026701,O=Минкомсвязь Р� 
               �ссии,STREET="125375 г. Москва, ул. Тверская 
               , д. 7",L=Москва,ST=77 г. Москва,C=RU,E=dit@minsvya 
               z.ru"
    ….
    $

    Полученный результат подтвердил пропажу данных. Более того, на экране появились два неотображаемых символа (раносторонний ромб черного цвета со знаком вопроса? внутри). Проведенный анализ показал, что эти неотображаемые символы имеют коды 0xD0 и 0xBE соответственно:

    image

    Пропала русская буква «о» с шестнадцатеричном представлении в кодировке UTF-8 как 0xD00xBE. А коды 0xD0 и 0xBE и есть наши неотображаемые символы. А что за символы появились между этими байтами? А это и есть «симпатичная» печать – символы выравнивания печатаемого текста.

    Что же произошло? На вход «симпатичной» печати (файл /nss/cmd/lib/secutil.c, функция secu_PrintRawStringQuotesOptional) приходят данные в виде SECITEM, т.е. адреса на массив байт и его длину:

        for (i = 0; i < si->len; i++) {
            unsigned char val = si->data[i];
            unsigned char c;
              if (SECU_GetWrapEnabled() && column > 76) {
                	SECU_Newline(out);
                	SECU_Indent(out, level);
                	column = level * INDENT_MULT;
                }
    
            if (utf8DisplayEnabled) {
                if (val < 32)
                    c = '.';
                else
                    c = val;
            } else {
                c = printable[val];
            }
            fprintf(out, "%c", c);
            column++;
        }
    

    И если предусмотрена (SECU_GetWrapEnabled() == True) симпатичная печать (отсутствие параметра –w у утилиты PP) и число байт в строке превысило 76 (column > 76), то после очередного символа вставляются новая строка (SECU_Newline) и необходимые отступы (SECU_Indent). При этом никто из разработчиков не задумался над тем, что, если используется кодировка UTF-8 (utf8DisplayEnabled), то красоту можно наводить только после очередного символа, а не байта, так как понятие байта и символа в кодировке UTF-8 могут и не совпадать. Если говорить о русских буквах, то каждая из них кодируется двумя байтами. Именно такой разрыв и произошел с нашей русской буквой «о» (0xD00xBE).

    Каков выход? Все очень просто достаточно в функции secu_PrintRawStringQuotesOptional заменить строку:

    if (SECU_GetWrapEnabled() && column > 76) {

    на строку следующего вида:

    if (SECU_GetWrapEnabled() && column > 76 && (val <= 0x7F || val == 0xD0 || val == 0xD1)) {

    Если теперь пересобрать утилиту PP и установить ее в систему, то «симпатичная» печать будет оправдывать свое название и для «великого, могучего, правдивого и свободного русского языка!» (И.С. Тургенев):

    image

    Если говорить о красоте печати, то можно было бы добавить перенос не только по количеству символов в строке, а более корректный, например, по пробелу, по запятой, двоеточию и другим символам. Я уж не говорю про семантический анализ при переносе. Но это уже область искусственного интеллекта.

    И в завершение, это уже вторая обнаруженная неточность в утилитах NSS. Первая была обнаружена в утилите oidcalc.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 8
    • 0
      Т.е. костыль, чтобы поддерживать русский в UTF-8?
      Я плохо себе представляю как выглядит UTF-8 в бинарном виде, но не лучше ли прикрутить что-то типа (http://utfcpp.sourceforge.net/) и с ее помощью считать сколько выдавлено utf-8 codepoints в аутпут?
      Ваш хак, если не ошибаюсь, развалится даже от символа «евро» попадись он в нужном месте.
      • +2

        Добавлю: codepoint в utf-8 всегда начинается с байта с двумя старшими единицами, а продолжается — несколькими байтами с одной старшей единицей и потом нулем.


        Так что проверку надо было писать вот так:


        if (SECU_GetWrapEnabled() && column > 76 && (val <= 0x7F || val => 0xC0)) {

        Или вот так (эквивалентно):


        if (SECU_GetWrapEnabled() && column > 76 && (val & 0xC0) != 0x80)) {

        Но еще правильнее — увеличивать column только после окончания codepoint, а не после каждого байта (но для иероглифов вроде бы увеличивать надо не на 1, а не 2). Тут так просто код я уже не напишу.

        • –1
          Спасибо. Учту!
        • 0
          И так евро в UTF-8 кодируется тремя байтами (это ,tp признака UTF-8 — 0xEF0xBB0xBF ) — 0xE20x820xAC. И как он развалит что-либо? Никак. Все три символа не попадают в условие

          (val <= 0x7F || val == 0xD0 || val == 0xD1)

          и символ евро будет обработан корректно. С другой стороны, я рассчитывал на такой вопрос, что код не универсален. И просто думал что кто-то добавит универсальное решение. Если проблема известна, то и решение всегда будет найдено.
        • 0
          Пользуясь случаем, кто-нибудь знает готовую embedded библиотеку для NSS? Нам в приложении необходимо устанавливать сертификаты и хотелось бы использовать какие-нибудь вызовы API NSS не через командную строку.
          • 0
            Вот писалась высоуровневая библиотека на NSS. Браузер Redfox всегда собирается со свежим пакетом NSS.
            Вообще-то библиотеки есть в дистрибутивах того же Firefox.
            А дальше вопросы. Нужна вам в вашем проекте российская криптография или нет? Какая платформа, ну и т.д.
            • 0
              Спасибо за ссылку, но я думаю она нам не подойдет. Мы встраиваем в свое ПО эту библиотеку и оно будет распространятся бесплатно с открытым исходным кодом. Цитата с сайта:
              Внимание! Проходите процесс лицензирования непосредственно на том компьютере, на котором вы планируете в дальнейшем использовать программное обеспечение!

              Ну и нам не нужна российская криптография, работаем только с установкой доверенных сертификатов в браузеры. Требование к кроссплатформенности обязательное. Поэтому вопрос и возник — самим сделать из бинарей либы или найти готовое решение…
              • 0
                Установить доверенные сертификаты в хранилище можно (если у вас используется хранилище NSS) утилитой certutil. Если хотите написать свой код — посмотрите исходный код той же утилиты certutil.c Там все ясно и просто.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.