Page tree

Введение

Git поддерживает несколько механизмов цифровой подписи объектов репозитория, включая OpenPGP (GnuPG), SSH и X.509. Наиболее распространённым механизмом является OpenPGP, реализуемый с использованием GnuPG (GNU Privacy Guard). Именно этот вариант рассматривается в настоящем документе.

GnuPG не имеет встроенной поддержки интерфейса PKCS#11. Для использования аппаратных токенов, поддерживающих данный интерфейс, необходимо применять промежуточный программный компонент, обеспечивающий взаимодействие между GnuPG и PKCS#11. В рассматриваемом сценарии таким компонентом является gnupg-pkcs11-scd, который взаимодействует с библиотекой librtpkcs11ecp.

Следует учитывать, что GnuPG не поддерживает российские криптографические алгоритмы ГОСТ Р 34.10 и ГОСТ Р 34.11 в качестве алгоритмов OpenPGP. По этой причине необходимо использовать алгоритмы, одновременно поддерживаемые GnuPG и библиотекой librtpkcs11ecp, например RSA или ECDSA. В рассматриваемом примере используется ключ RSA длиной 2048 бит.

Перед выполнением приведённых ниже действий токен Рутокен ЭЦП 3.0 был предварительно отформатирован. Генерация ключевой пары и сертификата выполнялась средствами OpenSC и OpenSSL. Закрытый ключ создаётся непосредственно на токене и не покидает его на протяжении всего жизненного цикла.

Начиная с OpenSSL 3.x механизм ENGINE объявлен устаревшим (deprecated). Тем не менее библиотека engine_pkcs11, используемая для взаимодействия OpenSSL с PKCS#11, по-прежнему основана именно на механизме ENGINE. Поэтому в рассматриваемом сценарии используются параметры -engine pkcs11 и -keyform engine.

Архитектура решения

В рассматриваемом сценарии взаимодействие компонентов осуществляется по следующей схеме:

Git
 │
 ▼
GnuPG (gpg)
 │
 ▼
gpg-agent
 │
 ▼
gnupg-pkcs11-scd
 │
 ▼
PKCS#11
 │
 ▼
librtpkcs11ecp
 │
 ▼
Рутокен ЭЦП 3.0

Git использует GnuPG для создания цифровой подписи. GnuPG взаимодействует с gpg-agent, который вместо стандартного scdaemon использует демон gnupg-pkcs11-scd. Последний обеспечивает доступ к закрытому ключу, расположенному на токене, посредством библиотеки librtpkcs11ecp, реализующей интерфейс PKCS#11.

Используемый стек

Компонент

Версия

Ubuntu

24.04 LTS

Git

2.43.0

GnuPG

2.4.4

OpenSC

0.25.0~rc1-1ubuntu0.2

OpenSSL

3.0.13

librtpkcs11ecp

2.18.4.0

Предварительные требования

Перед началом работы должны быть выполнены следующие условия:

  • подключён токен Рутокен ЭЦП 3.0;
  • токен инициализирован и при необходимости отформатирован;
  • известен пользовательский PIN-код;
  • установлена библиотека librtpkcs11ecp;
  • установлен пакет OpenSC;
  • пользователь обладает правами доступа к USB-устройству.

Установка необходимых пакетов

Обновите список пакетов:

sudo apt update

Установите необходимые пакеты:

sudo apt install git gpg gnupg-pkcs11-scd opensc libengine-pkcs11-openssl -y

После этого необходимо установить библиотеку librtpkcs11ecp. Актуальный пакет следует загрузить с официального сайта Рутокен и установить стандартными средствами пакетного менеджера.

После установки рекомендуется убедиться в расположении библиотеки:

find /usr/lib -name "librtpkcs11ecp.so"

или

dpkg -L librtpkcs11ecp | grep '\.so'

В приведённых далее примерах используется путь

/usr/lib/librtpkcs11ecp.so

Если библиотека установлена в другом каталоге, соответствующие пути необходимо скорректировать.

Настройка OpenSSL

Для выполнения криптографических операций с использованием закрытого ключа, размещённого на токене, необходимо временно настроить OpenSSL на использование PKCS#11.

Создайте в рабочем каталоге файл openssl.cnf следующего содержания:

cat > openssl.cnf <<'EOF'
openssl_conf = openssl_init

[openssl_init]
engines = engine_section

[engine_section]
pkcs11 = pkcs11_section

[pkcs11_section]
engine_id = pkcs11
dynamic_path = /usr/lib/x86_64-linux-gnu/engines-3/pkcs11.so
MODULE_PATH = /usr/lib/librtpkcs11ecp.so
default_algorithms = ALL
EOF


Путь к библиотеке pkcs11.so может отличаться в зависимости от используемого дистрибутива Linux и установленной версии пакета libengine-pkcs11-openssl.

Созданный файл используется только при выполнении команд OpenSSL и не изменяет глобальную конфигурацию системы.

Создание ключевой пары

Сгенерируйте на токене ключевую пару RSA длиной 2048 бит:

pkcs11-tool \
    --module /usr/lib/librtpkcs11ecp.so \
    --keypairgen \
    --key-type rsa:2048 \
    -l \
    --id 3132

Во время выполнения команды потребуется ввести пользовательский PIN-код.

При успешном выполнении будет получен вывод, аналогичный следующему:

Using slot 0 with a present token (0x0)
Logging in to "Rutoken ECP <no label>".

WARNING: user PIN to be changed

Please enter User PIN:

Key pair generated:

Private Key Object; RSA
  label:
  ID:          3132
  Usage:       decrypt, sign
  Access:      sensitive, always sensitive,
               never extractable, local

Public Key Object; RSA 2048 bits
  label:
  ID:          3132
  Usage:       encrypt, verify
  Access:      local
Закрытый ключ создаётся непосредственно на токене и не может быть экспортирован средствами PKCS#11.

Создание самоподписанного сертификата

Создайте самоподписанный сертификат, используя закрытый ключ, расположенный на токене:

OPENSSL_CONF=$(pwd)/openssl.cnf \
openssl req \
    -engine pkcs11 \
    -x509 \
    -new \
    -key 0:3132 \
    -keyform engine \
    -out cert.crt \
    -subj "/C=RU/ST=Moscow/L=Moscow/O=Aktiv/OU=dev/CN=testuser/emailAddress=testuser@mail.com"

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

cert.crt

В файловой системе сохраняется только сертификат. Закрытый ключ продолжает храниться исключительно на токене и никогда не копируется на компьютер.


Запись сертификата на токен

После создания сертификата его необходимо записать на токен, чтобы закрытый ключ, открытый ключ и сертификат находились на одном носителе.

Выполните команду:

pkcs11-tool \
    --module /usr/lib/librtpkcs11ecp.so \
    -l \
    -y cert \
    -w cert.crt \
    --id 3132

Во время выполнения команды потребуется ввести пользовательский PIN-код.

При успешном выполнении будет получен вывод, аналогичный следующему:

Using slot 0 with a present token (0x0)
Logging in to "Rutoken ECP <no label>".

WARNING: user PIN to be changed

Please enter User PIN:

Created certificate:

Certificate Object; type = X.509 cert
  label:
  subject:
      DN: C=RU,
          ST=Moscow,
          L=Moscow,
          O=Aktiv,
          OU=dev,
          CN=testuser,
          emailAddress=testuser@mail.com

  serial: 1108129EF7B2EC6058E7399A3B9EE31237D3B117
  ID:     3132

После выполнения данной операции на токене будут размещены:

  • закрытый ключ;
  • открытый ключ;
  • сертификат X.509.

Проверить содержимое токена можно командой:

pkcs11-tool --module /usr/lib/librtpkcs11ecp.so -O

В выводе должны присутствовать объекты типа Private Key, Public Key и Certificate с одинаковым идентификатором (ID).

Настройка GnuPG

Для обеспечения работы GnuPG с токенами, поддерживающими PKCS#11, необходимо настроить использование демона gnupg-pkcs11-scd, который выступает промежуточным звеном между GnuPG и библиотекой PKCS#11.

Если каталог конфигурации отсутствует, создайте его:

mkdir -p ~/.gnupg

При необходимости установите рекомендуемые права доступа:

chmod 700 ~/.gnupg

Настройте gpg-agent на использование gnupg-pkcs11-scd вместо стандартного scdaemon:

echo "scdaemon-program /usr/bin/gnupg-pkcs11-scd" > ~/.gnupg/gpg-agent.conf

Создайте файл конфигурации gnupg-pkcs11-scd:

cat > ~/.gnupg/gnupg-pkcs11-scd.conf <<'EOF'
providers rutoken

provider-rutoken-library /usr/lib/librtpkcs11ecp.so
EOF

Если библиотека librtpkcs11ecp.so расположена по другому пути, необходимо указать фактическое расположение.

После изменения конфигурации перезапустите gpg-agent:

gpgconf --kill gpg-agent

Команда завершает работу процесса gpg-agent. При следующем обращении к GnuPG агент будет автоматически запущен с новой конфигурацией.

Проверка обнаружения токена

Проверьте, что GnuPG обнаруживает токен:

gpg --card-status

Несмотря на использование PKCS#11, демон gnupg-pkcs11-scd предоставляет GnuPG виртуальный интерфейс OpenPGP-карты. По этой причине вывод команды соответствует формату, используемому для OpenPGP-карт.

Пример вывода:

Reader ...........: [none]
Application ID ...: D27600012401115031311743D2571111
Application type .: OpenPGP
Version ..........: 11.50
Manufacturer .....: ?
Serial number ....: 1743D257
Name of cardholder: [not set]
Language prefs ...: [not set]
Salutation .......:
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: rsa48 rsa48 rsa48
Max. PIN lengths .: 0 0 0
PIN retry counter : 0 0 0
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

Такой вывод является ожидаемым.

Следует обратить внимание, что gnupg-pkcs11-scd не превращает токен в OpenPGP-карту. Он лишь предоставляет GnuPG совместимый интерфейс, благодаря которому приложение может работать с PKCS#11 без собственной реализации данного стандарта.

Если команда завершается ошибкой или не отображает сведения о виртуальной карте, рекомендуется проверить:

  • корректность установки библиотеки librtpkcs11ecp;
  • правильность пути, указанного в gnupg-pkcs11-scd.conf;
  • успешное завершение команды gpgconf --kill gpg-agent;
  • подключение токена к системе.

Регистрация ключа в GnuPG

После того как GnuPG получил доступ к токену, необходимо зарегистрировать существующий ключ в локальном хранилище OpenPGP.

Запустите команду:

gpg --expert --full-generate-key

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

После запуска программы выполните следующие действия.

  1. Выберите пункт Existing key from card независимо от его номера в меню. Нумерация пунктов может отличаться между версиями GnuPG.
  2. После обнаружения токена GnuPG предложит выбрать поддерживаемые алгоритмы хэширования. Выберите необходимые алгоритмы, затем завершите выбор нажатием клавиши Q.
  3. Укажите срок действия ключа. Значение 0 означает бессрочный срок действия. После истечения заданного срока ключ не удаляется, а получает статус expired.
  4. Подтвердите выбранные параметры, ответив Y.
  5. Укажите имя пользователя, адрес электронной почты и, при необходимости, комментарий.
  6. После проверки введённых данных подтвердите их, выбрав O (Okay).

После завершения процедуры будет создан локальный объект OpenPGP, связанный с закрытым ключом, размещённым на токене.

Пример вывода:

gpg: revocation certificate stored as '~/.gnupg/openpgp-revocs.d/F3F07ACE83CE8EB92301295353266002A0C1C56B.rev'

public and secret key created and signed.

pub   rsa2048 2026-03-27 [SCEAR]
      F3F07ACE83CE8EB92301295353266002A0C1C56B

uid   User Name <test@email.com>
Следует понимать, что секретный ключ не копируется в локальное хранилище GnuPG. В каталоге ~/.gnupg создаётся лишь ссылка на закрытый ключ, который продолжает храниться исключительно на токене.


Проверка зарегистрированного ключа

Убедитесь, что ключ успешно зарегистрирован в локальном хранилище GnuPG:

gpg --list-secret-keys --keyid-format=long

При успешной регистрации вывод будет выглядеть примерно следующим образом:

sec> rsa2048/53266002A0C1C56B 2026-03-26 [SCEAR]
      F3F07ACE83CE8EB92301295353266002A0C1C56B
      Card serial no. = 3131 1743D257

uid   [ultimate] User Name <test@email.com>

Следует обратить внимание на несколько особенностей вывода.

Строка sec> означает, что GnuPG располагает ссылкой на секретный ключ, однако сам закрытый ключ хранится на внешнем устройстве и не может быть экспортирован в локальное хранилище.

Строка Card serial no. = 3131 1743D257 подтверждает, что зарегистрированный ключ связан с аппаратным токеном.

Если вместо sec> отображается sec, это означает, что используется локальный секретный ключ, а не ключ, расположенный на смарт-карте.

Настройка Git

После регистрации ключа необходимо настроить Git на его использование при создании цифровых подписей.

Назначьте зарегистрированный ключ ключом подписи по умолчанию:

git config --global user.signingkey F3F07ACE83CE8EB92301295353266002A0C1C56B

Вместо указанного отпечатка необходимо использовать идентификатор собственного ключа.

Включите обязательную подпись всех создаваемых коммитов:

git config --global commit.gpgsign true

При необходимости явно укажите программу подписи:

git config --global gpg.program gpg

Хотя Git обычно автоматически использует программу gpg, явное указание данного параметра делает конфигурацию более очевидной и исключает неоднозначность при наличии нескольких реализаций GnuPG.

Проверить текущую конфигурацию можно командой:

git config --global --list

Проверка независимости подписи от данных автора:

Для демонстрации того, что цифровая подпись определяется закрытым ключом, а не параметрами автора коммита, можно временно изменить глобальные настройки Git.

Например:

git config --global user.name "My Name"
git config --global user.email "no@email.com"

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

Создание подписанного коммита

Создайте подписанный коммит:

git commit -S -m "test signed commit"

Если параметр commit.gpgsign=true уже установлен, использование ключа -S не является обязательным, поскольку Git автоматически подписывает каждый создаваемый коммит.

Во время выполнения команды GnuPG запросит доступ к закрытому ключу на токене. При необходимости потребуется ввести пользовательский PIN-код.

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

Проверка подписи коммита

Для просмотра информации о цифровых подписях выполните команду:

git log --show-signature

Для просмотра только последнего коммита можно использовать:

git log --show-signature -1

Пример вывода:

commit 487a2127d90c9ccac3ceb331a365cb8e9fd3b344 (HEAD -> master)

gpg: WARNING: unsafe permissions on homedir '~/.gnupg'
gpg: Signature made Thu 26 Mar 2026 09:47:49 PM MSK
gpg: using RSA key F3F07ACE83CE8EB92301295353266002A0C1C56B
gpg: Good signature from "User Name <test@email.com>" [ultimate]

Author: My Name <no@email.com>
Date:   Thu Mar 26 21:47:48 2026 +0300

    test signed commit

Строка gpg: Good signature from "User Name <test@email.com>" означает, что подпись успешно проверена и соответствует зарегистрированному открытому ключу.

Следует обратить внимание, что автор коммита Author: My Name <no@email.com> не совпадает с владельцем цифровой подписи.

Это является ожидаемым поведением.

Поля Author и Committer определяют автора изменений и пользователя, выполнившего коммит, тогда как цифровая подпись подтверждает только то, каким закрытым ключом был подписан данный объект Git.

Именно поэтому изменение параметров user.name и user.email не влияет на владельца цифровой подписи.

Дополнительная проверка подписи

Проверить подпись конкретного коммита можно командой:

git verify-commit <хэш_коммита>

При успешной проверке GnuPG выведет информацию о владельце подписи и использованном ключе.

Аналогичным образом можно проверить подпись аннотированного тега:

git verify-tag <имя_тега>


Работа при отключённом токене

Если токен отсутствует или недоступен, попытка создания подписанного коммита завершится ошибкой. GnuPG сообщит об отсутствии устройства либо запросит подключение токена с соответствующим серийным номером.

После подключения устройства операция может быть повторена без изменения конфигурации Git или GnuPG. Подписание станет возможным после успешного обнаружения токена и ввода корректного пользовательского PIN-кода.

Закрытый ключ при этом продолжит храниться исключительно на аппаратном носителе и никогда не будет скопирован в память компьютера.

Устранение типичных проблем

Предупреждение unsafe permissions on homedir

Сообщение:

gpg: WARNING: unsafe permissions on homedir '~/.gnupg'

указывает на слишком широкие права доступа к каталогу ~/.gnupg.

Для исправления рекомендуется выполнить:

chmod 700 ~/.gnupg
find ~/.gnupg -type f -exec chmod 600 {} \;

После изменения прав предупреждение перестанет отображаться.

GnuPG не обнаруживает токен

Если команда:

gpg --card-status

не выводит информацию о виртуальной OpenPGP-карте, необходимо проверить:

  1. подключение токена;
  2. корректность установки библиотеки librtpkcs11ecp;
  3. правильность пути, указанного в ~/.gnupg/gnupg-pkcs11-scd.conf;
  4. успешное выполнение команды gpgconf --kill gpg-agent;
  5. наличие объектов на токене командой:
pkcs11-tool --module /usr/lib/librtpkcs11ecp.so -O

Git не может подписать коммит

Если при выполнении команды git commit отображается сообщение:

gpg failed to sign the data
fatal: failed to write commit object

последовательно проверьте:

  1. подключён ли токен;
  2. корректен ли введённый PIN-код;
  3. отображается ли токен командой gpg --card-status;
  4. зарегистрирован ли ключ (gpg --list-secret-keys);
  5. правильно ли указан параметр user.signingkey;
  6. доступен ли модуль librtpkcs11ecp.

Проверка содержимого токена

При возникновении проблем рекомендуется убедиться, что на токене присутствуют все необходимые объекты:

pkcs11-tool --module /usr/lib/librtpkcs11ecp.so -O

В выводе должны присутствовать:

  1. закрытый ключ (Private Key);
  2. открытый ключ (Public Key);
  3. сертификат (Certificate).

Все три объекта должны иметь одинаковый идентификатор (ID).

Заключение

В результате выполнения описанных действий Git будет использовать GnuPG для создания цифровых подписей, а все криптографические операции с закрытым ключом будут выполняться непосредственно на токене Рутокен ЭЦП 3.0 через интерфейс PKCS#11. Такая схема обеспечивает невозможность извлечения закрытого ключа из памяти токена и позволяет использовать аппаратный носитель для безопасной подписи Git-коммитов, тегов и других объектов репозитория, поддерживающих подписи OpenPGP.

Рассмотренный сценарий протестирован на Ubuntu 24.04 с использованием GnuPG 2.4.4, OpenSC 0.25 и библиотеки librtpkcs11ecp версии 2.18.4.0. При использовании других дистрибутивов Linux или более новых версий программного обеспечения отдельные пути к библиотекам и пакетам могут отличаться, однако общий порядок настройки остаётся аналогичным.

  • No labels