Подготовить два варианта разметки подписи PDF и отклонить выход за страницу

Учебные задачи

Реальный собственный PDF получается экспортом таблицы в память. Для его первой страницы готовятся два ОписаниеПодписиPDF с разными политиками, точным публичным сертификатом и проверенной геометрией. Пакет сохраняет исходные байты и явный запрет подписания: закрытого ключа нет.

Где и как запускать

Полный серверный общий модуль своей пустой учебной конфигурации: реальный PDF в памяти и проверка разметки, без фактического подписания. В1С не запускался.

Подготовка

  1. НОВАЯ отдельная пустая конфигурация УчебнаяРазметкаПодписиPDF85 на8.5.1.1343, совместимость Не использовать, Русский. Без рабочих каталогов, документов, регистров, общих реквизитов, расширений, подписок и заданий. Только свои модуль, форма и команда этого опыта.

  2. Конфигуратор → Общие модули → УчебнаяРазметкаПодписиPDF85: Сервер=Да, Вызов сервера=Да, Клиент=Нет, Глобальный=Нет, Привилегированный=Нет. Вставьте весь основной модуль, включая защиту пустого стенда и обе полные функции экспорта/чтения PDF.

  3. Своя обработка или общая управляемая форма УчебнаяРазметкаПодписиPDF85: команда/кнопка Выполнить, действие Выполнить. Вставьте весь supporting-модуль ниже. Будущая ручная подготовка: сохранить конфигурацию, обновить только свою базу F7, открыть эту форму и нажать её собственную кнопку.

  4. Все PDF-байты, таблица Книга/3/250/750, описания и штамп создаются в памяти. Нет исходного файла с неизвестным происхождением, чужого печатного макета, сертификата пользователя ОС или произвольного ИсходныйОбъект.

  5. Публичный DER Syntax Guide Training Only встроен полностью и сверяется по точному отпечатку. Он не доверенный, не установлен в ОС, его закрытого ключа в уроке нет. Не заменяйте его рабочим PFX и не добавляйте вызов подписания.

  6. Это серверная подготовка разметки на собственном Windows-стенде, не запуск криптопровайдера. Ни BSL, ни экспорт PDF в1С, ни GUI, ни подпись, ни хранилище ОС здесь не запускались.

Процедура ПроверитьСвойПустойСтенд()
    Если Метаданные.Справочники.Количество() <> 0 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Тогда
        ВызватьИсключение "Нужен только свой новый пустой учебный стенд";
    КонецЕсли;
    Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Тогда
        ВызватьИсключение "Свой сеанс не должен изменяться расширениями";
    КонецЕсли;
    Источники = Новый Массив;
    Источники.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
    Источники.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
    Источники.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
    Для Каждого Источник Из Источники Цикл
        Расширения = РасширенияКонфигурации.Получить(, Источник);
        Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
            ВызватьИсключение "Native список расширений должен иметь тип Массив";
        КонецЕсли;
        Если Расширения.Количество() <> 0 Тогда
            ВызватьИсключение "В своём новом стенде нет расширений базы или сеанса";
        КонецЕсли;
    КонецЦикла;
    Если ТранзакцияАктивна() Тогда
        ВызватьИсключение "Чужая или вложенная активная транзакция не допускается";
    КонецЕсли;
КонецПроцедуры

Функция СоздатьСобственныеБайтыPDF()
    Таблица = Новый ТабличныйДокумент;
    Таблица.КодЯзыка = "ru";
    Таблица.Область(1, 1).Текст = "СОБСТВЕННЫЙ УЧЕБНЫЙ ОБРАЗЕЦ";
    Таблица.Область(2, 1).Текст = "Книга";
    Таблица.Область(2, 2).Текст = "3";
    Таблица.Область(2, 3).Текст = "250";
    Таблица.Область(2, 4).Текст = "750";
    Таблица.Область(1, 1, 2, 4).ЦветФона = Новый Цвет(255, 255, 210);
    Таблица.Область(, 1, , 1).ШиринаКолонки = 45;
    Поток = Новый ПотокВПамяти;
    Попытка
        Таблица.Записать(Поток, ТипФайлаТабличногоДокумента.PDF);
        Данные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
    Исключение
        Поток.Закрыть();
        ВызватьИсключение;
    КонецПопытки;
    Возврат Данные;
КонецФункции

Функция ПрочитатьСобственныйДокумент(Данные)
    Поток = Данные.ОткрытьПотокДляЧтения();
    Попытка
        Документ = Новый ДокументPDF;
        Документ.Прочитать(Поток);
        Поток.Закрыть();
    Исключение
        Поток.Закрыть();
        ВызватьИсключение;
    КонецПопытки;
    Возврат Документ;
КонецФункции

Функция СоздатьСвойШтамп()
    Штамп = Новый ТабличныйДокумент;
    Штамп.Область(1, 1).Текст = "УЧЕБНАЯ РАЗМЕТКА — НЕ ПОДПИСАНО";
    Штамп.Область(2, 1).Текст = "Книга / 3 / 250 / 750";
    Возврат Штамп;
КонецФункции

Функция СоздатьСвоёОписание(Сертификат, ВидПодписи, Имя)
    Описание = Новый ОписаниеПодписиPDF;
    Описание.Сертификат = Сертификат;
    Описание.ТипПодписи = ВидПодписи;
    Описание.Имя = Имя;
    Описание.НомерСтраницы = 1;
    Описание.Лево = 10;
    Описание.Верх = 45;
    Описание.Ширина = 70;
    Описание.Высота = 15;
    Если ВидПодписи = ТипПодписиPDF.Утверждающая Тогда
        Описание.БлокировкаДокумента = Истина;
        Описание.РазрешенияДоступаИзменения = РазрешенияДоступаИзмененияPDF.Нет;
    ИначеЕсли ВидПодписи = ТипПодписиPDF.Сертифицирующая Тогда
        Описание.БлокировкаДокумента = Ложь;
        Описание.РазрешенияДоступаИзменения = РазрешенияДоступаИзмененияPDF.ЗаполнениеПодписание;
    Иначе
        ВызватьИсключение "Урок допускает только две явно разобранные политики PDF";
    КонецЕсли;
    Возврат Описание;
КонецФункции

Процедура ПроверитьСвоюРазметку(Документ, Описание, Сертификат, Имя, ВидПодписи)
    Если ТипЗнч(Описание) <> Тип("ОписаниеПодписиPDF") Или Описание.Имя <> Имя
        Или Описание.ТипПодписи <> ВидПодписи Тогда
        ВызватьИсключение "Получены не собственные имя и политика подписи";
    КонецЕсли;
    Если ТипЗнч(Описание.Сертификат) <> Тип("СертификатКриптографии")
        Или Base64Строка(Описание.Сертификат.Отпечаток) <> Base64Строка(Сертификат.Отпечаток) Тогда
        ВызватьИсключение "Описание должно содержать только собственный публичный образец";
    КонецЕсли;
    Если Описание.НомерСтраницы <> 1 Или Описание.НомерСтраницы > Документ.КоличествоСтраниц() Тогда
        ВызватьИсключение "Нужна существующая первая страница своего документа";
    КонецЕсли;
    Страница = Документ.Страницы.Получить(Описание.НомерСтраницы - 1);
    Если Страница.Ориентация <> 0 Тогда
        ВызватьИсключение "Простая проверка урока рассчитана только на неповёрнутую собственную страницу";
    КонецЕсли;
    ШиринаРабочейОбласти = Страница.Ширина - Страница.ПолеСлева - Страница.ПолеСправа;
    ВысотаРабочейОбласти = Страница.Высота - Страница.ПолеСверху - Страница.ПолеСнизу;
    Если Описание.Лево < 0 Или Описание.Верх < 0 Или Описание.Ширина <= 0 Или Описание.Высота <= 0
        Или Описание.Лево + Описание.Ширина > ШиринаРабочейОбласти
        Или Описание.Верх + Описание.Высота > ВысотаРабочейОбласти Тогда
        ВызватьИсключение "Учебный прямоугольник не помещается в консервативную область страницы без её полей";
    КонецЕсли;
    Если ТипЗнч(Описание.БлокировкаДокумента) <> Тип("Булево") Тогда
        ВызватьИсключение "Признак блокировки должен быть Булево";
    КонецЕсли;
    Если ВидПодписи = ТипПодписиPDF.Утверждающая Тогда
        Если Не Описание.БлокировкаДокумента
            Или Описание.РазрешенияДоступаИзменения <> РазрешенияДоступаИзмененияPDF.Нет Тогда
            ВызватьИсключение "Своя утверждающая политика должна задавать блокировку, не разрешение заполнения";
        КонецЕсли;
    Иначе
        Если Описание.БлокировкаДокумента
            Или Описание.РазрешенияДоступаИзменения <> РазрешенияДоступаИзмененияPDF.ЗаполнениеПодписание Тогда
            ВызватьИсключение "Своя сертифицирующая политика должна разрешать заполнение/подписание, не блокировку";
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Процедура ВыполнитьПример() Экспорт
    ПроверитьСвойПустойСтенд();
    ДанныеPDF = СоздатьСобственныеБайтыPDF();
    Документ = ПрочитатьСобственныйДокумент(ДанныеPDF);
    Если ДанныеPDF.Размер() = 0 Или Документ.КоличествоСтраниц() = 0 Тогда
        ВызватьИсключение "Нужен реальный собственный PDF, а не пустая переменная";
    КонецЕсли;
    Сертификат = Новый СертификатКриптографии(Base64Значение("MIIC+jCCAeKgAwIBAgIBVTANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBpTeW50YXggR3VpZGUgVHJhaW5pbmcgT25seTAeFw0yMDAxMDEwMDAwMDBaFw0zNTEyMzEwMDAwMDBaMCUxIzAhBgNVBAMMGlN5bnRheCBHdWlkZSBUcmFpbmluZyBPbmx5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkraGtF6mSMHQWI0lIdDxzNjLTB0VHmtLUYEIxXFFJepQtyMGWZ9rfpbTglFp8wpsw+Wc09FQyDFuKsNz3cBcBBx7fTmJVuYzfrXjhDECNqEoKMEf+Qrim3WsVKA/Qr8IMnzq1qdnA1pLgaaKyscfCJkw9W1yoTlLV3PmGs6h6gP1/KXwe+zKN5r3Jl0IVk8RXTLAeyqN01lCuyzaeSYm/v2YS88LdaJ/nkdFqy5QjRXfm7EOCW7eCZlCBwW0EvGhUarZxWlq04lPs9qu3aC3/6gY/scE2vY1jeZV2VpV0lLQpU5gVdjVGI4YQmfgkb8jYtV5U7FQ1KQ6Poji/ort6wIDAQABozUwMzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEARoo1FJJKKkaZXyFl1tlYj8K7xvryXwhb+tCaFvdXuON27YC3/YIHLUT9sCF9o76VIT8dsVMpK0Qo+VV0FIvqRcB+u6SmyJ+Y7590mzd5P1CSt8Y/4BNV2LosYxhcdVczs26PWHL5GXyBMWRxtBjNsnTeMKh4/WIMrplAr0IOJCmjIeKhv0JbQacsNqhmpe7JdgtBYC4Rrn3ajdD99HZREmyLR/iJrh6W4IGqSN0MGT0xL/mLI0GsTp9JhHDF3o8bmC1yMDlLOoAZp7M072bzfDsKFPg4ATQp/hhbtsTdS/LXrd9cLehZ7ThH6ljL8y2JbALD+6HPnT6Sv+Mg0Wq0MA=="));
    Если Base64Строка(Сертификат.Отпечаток) <> "tRDNiwoaiFssHhQqwyjWVlPLdns=" Тогда
        ВызватьИсключение "Публичный DER-образец не совпал с зафиксированным отпечатком";
    КонецЕсли;
    Утверждающая = СоздатьСвоёОписание(Сертификат, ТипПодписиPDF.Утверждающая, "СвояРазметкаУтверждения85");
    Сертифицирующая = СоздатьСвоёОписание(Сертификат, ТипПодписиPDF.Сертифицирующая, "СвояРазметкаСертификации85");
    ПроверитьСвоюРазметку(Документ, Утверждающая, Сертификат, "СвояРазметкаУтверждения85", ТипПодписиPDF.Утверждающая);
    ПроверитьСвоюРазметку(Документ, Сертифицирующая, Сертификат, "СвояРазметкаСертификации85", ТипПодписиPDF.Сертифицирующая);
    Штамп = СоздатьСвойШтамп();
    Пакет = Новый Структура("PDF,Представление,Утверждающая,Сертифицирующая,МожноПодписывать", ДанныеPDF, Штамп, Утверждающая, Сертифицирующая, Ложь);
    Если Пакет.МожноПодписывать Или Пакет.PDF.Размер() <> ДанныеPDF.Размер()
        Или Пакет.Представление.Область(1, 1).Текст <> "УЧЕБНАЯ РАЗМЕТКА — НЕ ПОДПИСАНО"
        Или Пакет.Утверждающая.Имя = Пакет.Сертифицирующая.Имя Тогда
        ВызватьИсключение "Пакет должен сохранить свой исходный PDF, две разные альтернативы и запрет подписания";
    КонецЕсли;
    Сообщить("Подготовлены две АЛЬТЕРНАТИВНЫЕ разметки, не две подписи в документе");
    Сообщить("Свой прямоугольник, мм: " + Строка(Утверждающая.Лево) + "/" + Строка(Утверждающая.Верх)
        + "/" + Строка(Утверждающая.Ширина) + "/" + Строка(Утверждающая.Высота));
    Сообщить("МожноПодписывать=Ложь. Исходный PDF не подписан и не заблокирован");
КонецПроцедуры
УчебнаяРазметкаПодписиPDF85

Своя управляемая форма → Модуль

Полный клиентский launcher с server wrapper для собственного общего модуля; не выполнялся.

&НаКлиенте
Процедура Выполнить(Команда)
    ВыполнитьНаСервере();
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ВыполнитьНаСервере()
    УчебнаяРазметкаПодписиPDF85.ВыполнитьПример();
КонецПроцедуры

Разбор по шагам

  1. Полные функции создают свою таблицу, записывают её в ПотокВПамяти как PDF, закрывают поток с получением ДвоичныеДанные и читают настоящий ДокументPDF отдельным потоком. Оба потока закрываются также при ошибке.

  2. ДО получения страницы проверяется наличие фактических PDF-байтов и страниц. НомерСтраницы у описания начинается с1; Получить коллекции — с0. Поэтому индекс вычисляется НомерСтраницы-1, а не подставляется тот же номер.

  3. СоздатьСвоёОписание действительно конструирует ОписаниеПодписиPDF. В него записываются свой сертификат, точные разные имена, существующая первая страница и прямоугольник10/45/70/15мм: Лево/Верх — позиция, Ширина/Высота — размеры.

  4. Отпечаток встроенного DER проверяется до подготовки описаний. Проверка разметки затем повторно сверяет реальный тип и отпечаток Описание.Сертификат: похожее имя CN не заменяет принадлежность учебному образцу.

  5. Не предполагаем форматA4: читаем реальные Ширина/Высота страницы и четыре поля. Консервативная область вычисляется без полей, затем проверяется правая и нижняя граница прямоугольника. Для поворота страницы не0 алгоритм честно отказывает, а не меняет местами ширину и высоту наугад.

  6. В утверждающей альтернативе БлокировкаДокумента=Истина. РазрешенияДоступаИзменения задаются Нет как явное нейтральное значение; это свойство имеет смысл для сертифицирующего типа, не управляет утверждающей подписью.

  7. В сертифицирующей альтернативе БлокировкаДокумента=Ложь и РазрешенияДоступаИзменения=ЗаполнениеПодписание. Это разрешение заполнения форм и добавления подписей, не произвольного изменения текста/страниц. Блокировка имеет смысл для утверждающего типа.

  8. Проверяются тип описания, имя, тип подписи, собственный сертификат, реальная страница, положительные размеры, границы и согласованность каждой политики. Это прикладной preflight: платформа не обязана проверять нашу геометрию при самом присваивании свойств.

  9. Создаётся собственный ТабличныйДокумент-штамп с явным текстом НЕ ПОДПИСАНО. Пакет содержит исходные PDF-байты, представление и ДВЕ АЛЬТЕРНАТИВЫ описания. Они не добавлены в PDF; результат дополнительно требует МожноПодписывать=Ложь.

  10. Подписание требует отдельного настоящего менеджера криптографии и собственного закрытого ключа. Наличие публичного СертификатКриптографии не обеспечивает ни того, ни другого. Этот пакет предназначен для разработки/проверки разметки и не передаётся ДобавитьПодпись.

  11. Readonly Дата намеренно не назначается и не считается покрытой этим уроком: дату настоящей подписи нужно читать из описания реально подписанного PDF. Дата нового шаблона не доказательство события подписания.

Как проверить результат

  1. Будущий результат: две разметки проходят геометрию и разные проверки политики, имена различны; вывод прямо сообщает МожноПодписывать=Ложь. Исходный PDF остаётся неподписанным; штамп лежит отдельно в памяти.

  2. Будущий отрицательный контроль: в СоздатьСвоёОписание задайте Ширина=0. ПроверитьСвоюРазметку должна выбросить исключение ДО формирования итогового пакета, а не вывести сообщение об успехе.

  3. Будущий контроль страницы: в начале ПроверитьСвоюРазметку временно вставьте Описание.НомерСтраницы = Документ.КоличествоСтраниц() + 1; оба объекта — реальные параметры этой процедуры. Проверка должна остановиться до Получить. Уберите эту строку после опыта.

  4. Будущий контроль политики: у сертифицирующей альтернативы временно установите БлокировкаДокумента=Истина или разрешение Нет. Полная проверка ожидает заданную для урока комбинацию и должна отказать.

  5. runtimeVerifiedScenarios=0. Все указанные результаты — контроль будущего ручного запуска на своём стенде, не выполненный экспорт, не визуальная проверка штампа и не доказательство криптографической действительности.

Ожидаемый результат

Реальный собственный PDF и две проверяемые альтернативы разметки собраны в памяти; подписывание явно запрещено, исходный документ не изменён

На что обратить внимание

  1. Присваивание настроек подписи само по себе не подписывает, не сертифицирует и не блокирует PDF. Тип/разрешения показывают будущую политику, а не уже действующее ограничение исходного документа.

  2. Два описания — альтернативы одной задачи, не инструкция ставить две несовместимые сертифицирующие подписи подряд. Какую политику применять к реальному документу, решают отдельно до подписания.

  3. Координаты подписи — миллиметры, не пиксели и не номера строк табличного документа. Границы проверены консервативно по реальной неповёрнутой странице; внешний вид и поворот штампа требуют отдельного будущего визуального/платформенного опыта.

  4. Открытый сертификат не содержит закрытого ключа. Проверка отпечатка удостоверяет только конкретные байты учебного образца, не доверенную цепочку, юридическую силу, криптографическую пригодность или доступ к контейнеру.

  5. Не присваивайте readonly Дата, не рисуйте вместо неё текущую дату и не называйте вывод НЕ ПОДПИСАНО электронной подписью. Для Дата нужен другой полный урок с собственным ключом и чтением фактических описаний PDF.

  6. Файлы, ОС-хранилища, криптопровайдеры, PIN, OCSP/CRL, сеть, печать и рабочие данные этим примером не используются. ПотокВПамяти не является файлом на диске.

Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.