Как стать автором
Обновить

Инфраструктура открытых ключей: GnuPG/SMIME и токены PKCS#11 с поддержкой российской криптографии

Время на прочтение 24 мин
Количество просмотров 6.1K
Неумолимо приближается час «Ч»: «использование схемы подписи ГОСТ Р 34.10-2001 для формирования подписи после 31 декабря 2018 года не допускается!». И вот, наконец, 16 июля 2018 г. на сайте Федерального ситуационного центра электронного правительства появилось Уведомление о начале выпуска сертификатов ключей проверки электронных подписей подчиненных удостоверяющих центров на головном удостоверяющем центре в соответствии с ГОСТ Р 34.10-2012. Правда, все равно не ясно когда же прекратится выпуск старых сертификатов. Но «процесс пошел» и это радует.

Введение в обиход новой электронной подписи предполагает наличие средств не только по выпуску сертификатов по ГОСТ Р 34.10-2012, но и наличие средств формирования и проверки этой подписи. Об одном из таких средств можно на страницах habr уже рассказывалось



Электронная подпись активно используется не только в электронном документообороте (ЭДО), но и при электронной переписке (email):



Если говорить об OpenSource проектах в области ИОК на базе сертификатов X509, то наряду с Network Security Services (NSS), OpenSSL, широкой популярностью пользуется проект GnuPG/SMIME. Криптографическим ядром данного проекта является библиотека LibGCrypt. Существенным при этом является то, что эта библиотека поддерживает криптоалгоритмы ГОСТ Р 34.10-2012 и ГОСТ Р 34.11-2012 (STRIBOG256 и STRIBOG512) и не требует никакой доработки, по крайней мере, для функций формирования электронной подписи и ее проверки. Для разбора, манипулирования объектами ИОК (сертификаты, запросы на сертификаты, электронная подпись и т.п.) в GnuPG используется библиотека LibKSBA. В отличии от LibGCrypt в библиотеке libksba не реализованы рекомендации ТК-26 для криптографических сообщений формата CMS. В статье показана модернизация библиотеки libksba, связанная с разбором сертификатов X509 и сообщений CMS (PKCS7), что позволило обеспечить проверку электронной подписи сертификатов и электронной подписи документов PKCS#7. Сегодня мы пойдем дальше и добавим в библиотеку функционал для формирования CMS формата PKCS#7. Это позволит обеспечить поддержку электронной подписи по ГОСТ Р 34.10-2001.2012 (после определенной модернизации самого проекта GnuPG) и в модуле GnuPG/SM.

А в конечном итоге, окажется возможным использование отечественной электронной подписи как в почтовом клиенте KMail (кстати говоря, и во многих других почтовых клиентах), так и в утилите Kleopatra как для проверки электронной подписи, так и ее формирования для различных файлов:



imageТеперь о главном. В дальнейшем будем исходим из того, что пользователи хранят свои личные сертификаты (закрытый ключ и сертификат ключа проверки электронной подписи) на токенах/смарткартах PKCS#11 с реализацией на них по крайней мере функций формирования электронной подписи (механизмы CKM_GOSTR3410 для формирования ЭП по ГОСТ Р 34.10-2001/2012 с длиной ключа 256 бит и/или CKM_GOSTR3410_512 для формирования ЭП по ГОСТ Р 34.10-2012 с длиной ключа 512 бит). Оптимальный вариант это когда функционал токена/смаркарты поддерживает стандарт PKCS#11 v.2.40.

Для доступа к токенам PKCS#11 в GnuPG используется утилита gnupg_pkcs11_scd. В наших примерах используется утилита gnupg_pkcs11_csd версии 0.9.1. Главной задачей этой утилиты (помимо организации выполнения криптографических функций PKCS#11, например, подписать хэш), является разбор сертификатов, хранящихся на токене, получение информации об отрытом ключе в виде S-выражения и передача ее вверх по цепочке. Это операцию выполняет функция keyutil_get_cert_sexp, которая находится в файле keyutil.c. К сожалению, эта утилита заточена только на работу с RSA-сертификатами. Но поскольку в нашем распоряжении имеется библиотека libksba да еще доработанная для работы ГОСТ-сертификатами, то оказалось на удивление просто переписать функцию keyutil_get_cert_sexp для обработки любых сертификатов, поддерживаемых библиотекой libksba:

/*
   Convert X.509 RSA/ECC/DSA/GOST public key into gcrypt internal sexp form. The resul is stored 
   in *sexp, which must be freed (using ) when not needed anymore. *sexp must be
   NULL on entry, since it is overwritten.
*/
gpg_err_code_t
keyutil_get_cert_sexp (
	unsigned char *der,
	size_t len,
	gcry_sexp_t *p_sexp
) {
	gpg_err_code_t error = GPG_ERR_GENERAL;
	gcry_mpi_t n_mpi = NULL;
	gcry_mpi_t e_mpi = NULL;
	gcry_sexp_t sexp = NULL;
	gpg_error_t err;
	ksba_sexp_t p;
	ksba_cert_t ks_cert;
	size_t n;
        err = ksba_cert_new (&ks_cert);
	if (err) {
	    error = GPG_ERR_BAD_KEY;
	    goto cleanup;
	}
	err = ksba_cert_init_from_mem (ks_cert, der, len);
	if (err) {
	    error = GPG_ERR_BAD_KEY;
	    goto cleanup;
	}
  /* Get the public key from the certificate.  */
	p = ksba_cert_get_public_key (ks_cert);
	n = gcry_sexp_canon_len (p, 0, NULL, NULL);
	if (!n)
	{
    	    ksba_free (p);
	    error = GPG_ERR_BAD_KEY;
	    goto cleanup;
	}
	err = gcry_sexp_sscan ( p_sexp, NULL, p, n);
	if (err) {
	    error = GPG_ERR_BAD_KEY;
	    goto cleanup;
	}
	ksba_free (p);
	error = GPG_ERR_NO_ERROR;
	goto cleanup;
#if 0
	if (
		(error = keyutil_get_cert_mpi (
			der,
			len,
			&n_mpi,
			&e_mpi
		)) != GPG_ERR_NO_ERROR
	) {
		goto cleanup;
	}
	if (
		gcry_sexp_build (
			&sexp,
			NULL,
			"(public-key (rsa (n %m) (e %m)))",
			n_mpi,
			e_mpi
		)
	) {
		error = GPG_ERR_BAD_KEY;
		goto cleanup;
	}
	*p_sexp = sexp;
	sexp = NULL;
	error = GPG_ERR_NO_ERROR;
#endif
cleanup:
	if (n_mpi != NULL) {
		gcry_mpi_release (n_mpi);
		n_mpi = NULL;
	}
	if (e_mpi != NULL) {
		gcry_mpi_release (e_mpi);
		e_mpi = NULL;
	}
	if (sexp != NULL) {
		gcry_sexp_release (sexp);
		sexp = NULL;
	}
	return error;
}

Оригинальный код заключен в блок:

#if 0
…
#endif

Полный патч для утилиты gnupg_pkcs11_scd находится здесь
diff -u PKCS11_CSD_ORIG/command.c PKCS11_CSD/command.c
--- PKCS11_CSD_ORIG/command.c	2018-07-19 16:25:20.778692015 +0300
+++ PKCS11_CSD/command.c	2018-07-19 16:25:10.238691435 +0300
@@ -1002,6 +1002,8 @@
 		{ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
 		0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
 		0x00, 0x04, 0x40  };
+	CK_MECHANISM_TYPE mech_type = CKM_RSA_PKCS;
+	int pubkey_type;
 
 	gpg_err_code_t error = GPG_ERR_GENERAL;
 	pkcs11h_certificate_id_t cert_id = NULL;
@@ -1021,8 +1023,18 @@
 		INJECT_SHA224,
 		INJECT_SHA256,
 		INJECT_SHA384,
-		INJECT_SHA512
+		INJECT_SHA512,
+/*For GOST*/
+		INJECT_GOSTR3411_CP,
+		INJECT_STRIBOG256,
+		INJECT_STRIBOG512
 	} inject = INJECT_NONE;
+#define NSSCK_VENDOR_PKCS11_RU_TEAM 0xd4321000 //0x80000000|0x54321000
+#define NSSCK_VENDOR_PKSC11_RU_TEAM NSSCK_VENDOR_PKCS11_RU_TEAM
+#define CK_VENDOR_PKCS11_RU_TEAM_TC26 NSSCK_VENDOR_PKCS11_RU_TEAM
+#define CKM_GOSTR3410                0x00001201
+// TC 26
+#define CKM_GOSTR3410_512 (CK_VENDOR_PKCS11_RU_TEAM_TC26 |0x006)
 
 	if (data->data == NULL) {
 		error = GPG_ERR_INV_DATA;
@@ -1107,6 +1119,16 @@
 		else if (!strcmp(hash, "sha512") && data->size == 0x40) {
 			inject = INJECT_SHA512;
 		}
+		else if (!strcmp(hash, "gost3411_94") || !strcmp(hash, "gost3411_CP") || !strcmp(hash, "gostr3411_94") || !strcmp(hash, "gostr3411_CP")) {
+			inject = INJECT_GOSTR3411_CP;
+		}
+		else if (!strcmp(hash, "gost3411_12_256") || !strcmp(hash, "gostr3411_12_256") || !strcasecmp(hash, "stribog256")) {
+			inject = INJECT_STRIBOG256;
+		}
+		else if (!strcmp(hash, "gost3411_12_512") || !strcmp(hash, "gostr3411_12_512") || !strcasecmp(hash, "stribog512")) {
+			inject = INJECT_STRIBOG512;
+		}
+
 		else {
 			common_log (LOG_DEBUG, "unsupported hash algo (hash=%s,size=%d)", hash, data->size);
 			error = GPG_ERR_UNSUPPORTED_ALGORITHM;
@@ -1169,6 +1191,23 @@
 				oid = sha512_prefix;
 				oid_size = sizeof(sha512_prefix);
 			break;
+/*Механизмы ГОСТ*/
+			case INJECT_GOSTR3411_CP:
+				oid = "";
+				oid_size = 0;
+				mech_type = CKM_GOSTR3410;
+			break;
+			case INJECT_STRIBOG256:
+				oid = "";
+				oid_size = 0;
+				mech_type = CKM_GOSTR3410;
+			break;
+			case INJECT_STRIBOG512:
+				oid = "";
+				oid_size = 0;
+				mech_type = CKM_GOSTR3410_512;
+			break;
+
 			default:
 				error = GPG_ERR_INV_DATA;
 				goto cleanup;
@@ -1232,7 +1271,7 @@
 		(error = common_map_pkcs11_error (
 			pkcs11h_certificate_signAny (
 				cert,
-				CKM_RSA_PKCS,
+				mech_type,
 				_data->data,
 				_data->size,
 				NULL,
@@ -1252,7 +1291,7 @@
 		(error = common_map_pkcs11_error (
 			pkcs11h_certificate_signAny (
 				cert,
-				CKM_RSA_PKCS,
+				mech_type,
 				_data->data,
 				_data->size,
 				sig,
@@ -1298,6 +1337,9 @@
 	return gpg_error (error);
 }
 
+/*для ГОСТ*/
+#define CKM_GOSTR3410_KEY_WRAP       0x00001203
+
 /** Decrypt data (set by SETDATA) with certificate id in line. */
 gpg_error_t cmd_pkdecrypt (assuan_context_t ctx, char *line)
 {
@@ -1309,6 +1351,7 @@
 	int session_locked = 0;
 	cmd_data_t *data = (cmd_data_t *)assuan_get_pointer (ctx);
 	cmd_data_t _data;
+	CK_MECHANISM_TYPE mech_type;
 
 	if (
 		data == NULL ||
@@ -1317,6 +1360,13 @@
 		error = GPG_ERR_INV_DATA;
 		goto cleanup;
 	}
+	if(memmem(data->data, data->size,"\x2A\x85\x03", 3)){
+/*Если ГОСТ*/
+	    mech_type = CKM_GOSTR3410_KEY_WRAP;
+	}
+	else{
+	    mech_type = CKM_RSA_PKCS;
+	}
 
 	/*
 	 * Guess.. taken from openpgp card implementation
@@ -1376,7 +1426,7 @@
 		(error = common_map_pkcs11_error (
 			pkcs11h_certificate_decryptAny (
 				cert,
-				CKM_RSA_PKCS, 
+				mech_type,
 				_data.data,
 				_data.size,
 				NULL,
@@ -1396,7 +1446,7 @@
 		(error = common_map_pkcs11_error (
 			pkcs11h_certificate_decryptAny (
 				cert,
-				CKM_RSA_PKCS, 
+				mech_type,
 				_data.data,
 				_data.size,
 				ptext,
@@ -1591,6 +1641,29 @@
 			goto cleanup;
 		}
 	}
+	else if (!strcmp (line, "APPTYPE")) {
+		if (
+			(error = assuan_write_status(
+				ctx,
+				"APPTYPE",
+				"NKS"
+			)) != GPG_ERR_NO_ERROR
+		) {
+			goto cleanup;
+		}
+	}
+	else if (!strcmp (line, "NKS-VERSION")) {
+		if (
+			(error = assuan_write_status(
+				ctx,
+				"NKS-VERSION",
+				"3"
+			)) != GPG_ERR_NO_ERROR
+		) {
+			goto cleanup;
+		}
+	}
+
 	else if (!strcmp (line, "KEY-ATTR")) {
 		int i;
 		for (i=0;i<3;i++) {
diff -u PKCS11_CSD_ORIG/keyutil.c PKCS11_CSD/keyutil.c
--- PKCS11_CSD_ORIG/keyutil.c	2018-07-19 16:25:20.779692015 +0300
+++ PKCS11_CSD/keyutil.c	2018-07-19 16:51:48.934779338 +0300
@@ -45,6 +45,7 @@
 typedef const unsigned char *my_openssl_d2i_t;
 #endif
 #endif
+#include <ksba.h>
 
 gpg_err_code_t
 keyutil_get_cert_mpi (
@@ -193,10 +194,10 @@
 
 	return error;
 }
-/**
-   Convert X.509 RSA public key into gcrypt internal sexp form. Only RSA
-   public keys are accepted at the moment. The resul is stored in *sexp,
-   which must be freed (using ) when not needed anymore. *sexp must be
+
+/*
+   Convert X.509 RSA/ECC/DSA/GOST public key into gcrypt internal sexp form. The resul is stored 
+   in *sexp, which must be freed (using ) when not needed anymore. *sexp must be
    NULL on entry, since it is overwritten.
 */
 gpg_err_code_t
@@ -210,6 +211,40 @@
 	gcry_mpi_t e_mpi = NULL;
 	gcry_sexp_t sexp = NULL;
 
+	gpg_error_t err;
+	ksba_sexp_t p;
+	ksba_cert_t ks_cert;
+	size_t n;
+
+        err = ksba_cert_new (&ks_cert);
+	if (err) {
+	    error = GPG_ERR_BAD_KEY;
+	    goto cleanup;
+	}
+	err = ksba_cert_init_from_mem (ks_cert, der, len);
+	if (err) {
+	    error = GPG_ERR_BAD_KEY;
+	    goto cleanup;
+	}
+  /* Get the public key from the certificate.  */
+	p = ksba_cert_get_public_key (ks_cert);
+	n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+	if (!n)
+	{
+    	    ksba_free (p);
+	    error = GPG_ERR_BAD_KEY;
+	    goto cleanup;
+	}
+	err = gcry_sexp_sscan ( p_sexp, NULL, p, n);
+	if (err) {
+	    error = GPG_ERR_BAD_KEY;
+	    goto cleanup;
+	}
+	ksba_free (p);
+	error = GPG_ERR_NO_ERROR;
+	goto cleanup;
+#if 0
+
 	if (
 		(error = keyutil_get_cert_mpi (
 			der,
@@ -237,6 +272,7 @@
 	*p_sexp = sexp;
 	sexp = NULL;
 	error = GPG_ERR_NO_ERROR;
+#endif
 
 cleanup:


С учетом этого патча для сборки утилиты gnupg_pkcs11_scd используется следующий скрипт:

export LIBS=" -lksba"
#./configure  --without-gnutls
./configure  --without-openssl
make

Этот патч в итоге позволит использовать ГОСТ-токены (в том числе сертифицированные ФСБ России), по крайней мере, для формирования электронной подписи.

Не будем останавливаться на доработках библиотеки libksba, а просто приведем
полный патч для libksba
diff -u KSBA_ORIG/cms.c KSBA/cms.c
--- KSBA_ORIG/cms.c	2013-03-15 23:26:38.000000000 +0400
+++ KSBA/cms.c	2018-07-19 08:24:48.774106713 +0300
@@ -1581,6 +1581,15 @@
 
   Note that IDX is only used for consistency checks.
  */
+/* For GOST
+  r_sig  = (sig-val
+ 	      (gost
+ 		(r <mpi>)
+ 		(s <mpi>)
+ 	      )
+ 	      (hash <name_hash>))
+*/
+
 gpg_error_t
 ksba_cms_set_sig_val (ksba_cms_t cms, int idx, ksba_const_sexp_t sigval)
 {
@@ -1588,6 +1597,7 @@
   unsigned long n;
   struct sig_val_s *sv, **sv_tail;
   int i;
+  int gost;
 
   if (!cms)
     return gpg_error (GPG_ERR_INV_VALUE);
@@ -1615,6 +1625,11 @@
   /* Break out the algorithm ID. */
   if (!(n = snext (&s)))
     return gpg_error (GPG_ERR_INV_SEXP);
+  gost = 0;
+  if (n==4 && s[0] == 'g' && s[1] == 'o' && s[2] == 's' && s[3] == 't') {
+   /* kludge to allow "gost" to be passed as algorithm name */
+      gost = 1;
+  }
 
   sv = xtrycalloc (1, sizeof *sv);
   if (!sv)
@@ -1680,6 +1695,11 @@
       s++;
       n--;
     }
+if(gost){
+  sv->value = xtrymalloc (n * 2);
+}
+else
+
   sv->value = xtrymalloc (n);
   if (!sv->value)
     {
@@ -1687,6 +1707,11 @@
       xfree (sv);
       return gpg_error (GPG_ERR_ENOMEM);
     }
+/*r и s в подписи меняем местами - ТК-26*/
+if(gost == 1)
+  memcpy (sv->value + n, s, n);
+else
+
   memcpy (sv->value, s, n);
   sv->valuelen = n;
   s += n;
@@ -1698,6 +1723,84 @@
       return gpg_error (GPG_ERR_UNKNOWN_SEXP); /* but may also be an invalid one */
     }
   s++;
+if(gost == 1){
+    unsigned char sh[30];
+
+  if (*s != '(')
+    {
+      xfree (sv->algo);
+      xfree (sv);
+      return gpg_error (digitp (s)? GPG_ERR_UNKNOWN_SEXP : GPG_ERR_INV_SEXP);
+    }
+  s++;
+  if (!(n = snext (&s)))
+    {
+      xfree (sv->algo);
+      xfree (sv);
+      return gpg_error (GPG_ERR_INV_SEXP);
+    }
+
+  s += n; /* ignore the name of the parameter */
+
+  if (!digitp(s))
+    {
+      xfree (sv->algo);
+      xfree (sv);
+      /* May also be an invalid S-EXP.  */
+      return gpg_error (GPG_ERR_UNKNOWN_SEXP);
+    }
+  if (!(n = snext (&s)))
+    {
+      xfree (sv->algo);
+      xfree (sv);
+      return gpg_error (GPG_ERR_INV_SEXP);
+    }
+
+  if (n > 1 && !*s)
+    { /* We might have a leading zero due to the way we encode
+         MPIs - this zero should not go into the OCTECT STRING.  */
+      s++;
+      n--;
+    }
+/*r и s в подписи меняем местами - ТК-26*/
+  memcpy (sv->value, s, n);
+  sv->valuelen += n;
+  s += n;
+
+  if ( *s != ')' && s[1] != ')' &&  s[2] != '(' )
+    {
+      xfree (sv->value);
+      xfree (sv->algo);
+      xfree (sv);
+      return gpg_error (GPG_ERR_UNKNOWN_SEXP); /* but may also be an invalid one */
+    }
+  s++;s++;s++;
+  if (!(n = snext (&s)))
+    {
+      xfree (sv->algo);
+      xfree (sv);
+      return gpg_error (GPG_ERR_INV_SEXP);
+    }
+   s += n;
+  if (!(n = snext (&s)))
+    {
+      xfree (sv->algo);
+      xfree (sv);
+      return gpg_error (GPG_ERR_INV_SEXP);
+    }
+  strncpy(sh, s, n);
+  if(!strcmp(sh, "1.2.643.7.1.1.2.2") || !strcasecmp(sh, "stribog256"))
+        sv->algo = xtrystrdup ("1.2.643.7.1.1.1.1");
+  else if(!strcmp(sh, "1.2.643.7.1.1.2.3") || !strcasecmp(sh, "stribog512"))
+        sv->algo = xtrystrdup ("1.2.643.7.1.1.1.2");
+  else if(!strcmp(sh, "1.2.643.2.2.9") || !strcasecmp(sh, "gostr3411_CP"))
+        sv->algo = xtrystrdup ("1.2.643.2.2.19");
+  else {
+    return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+  }
+   s += n;
+
+}
   /* fixme: end loop over parameters */
 
   /* we need 2 closing parenthesis */
diff -u KSBA_ORIG/dn.c KSBA/dn.c
--- KSBA_ORIG/dn.c	2016-08-22 11:40:58.000000000 +0300
+++ KSBA/dn.c	2018-06-26 19:24:32.000000000 +0300
@@ -48,6 +48,7 @@
                  2 = David Chadwick, July 2003
                  <draft-ietf-pkix-dnstrings-02.txt>
                  3 = Peter Gutmann
+                 4 = tk26
               */
   const char *description;
   size_t      oidlen;
@@ -74,12 +75,17 @@
     "\x09\x92\x26\x89\x93\xF2\x2C\x64\x01\x19", "0.9.2342.19200300.100.1.25" },
 {"UID", 1, "userid",              10,
     "\x09\x92\x26\x89\x93\xF2\x2C\x64\x01\x01", "0.9.2342.19200300.100.1.1 " },
-{"EMAIL", 3, "emailAddress",       9,
+{"E", 1, "emailAddress",       9,
     "\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01",     "1.2.840.113549.1.9.1" },
+/*oid-ы квалифицированного сертификата от TK-26*/
+{"OGRN", 4, "OGRN",             5, "\x2a\x85\x03\x64\x01", "1.2.643.100.1" },
+{"INN", 4, "INN",             8, "\x2a\x85\x03\x03\x81\x03\x01\x01", "1.2.643.3.131.1.1" },
+{"SNILS", 4, "SNILS",             5, "\x2a\x85\x03\x64\x03", "1.2.643.100.3" },
+{"OGRNIP", 4, "OGRNIP",             5, "\x2a\x85\x03\x64\x05", "1.2.643.100.5" },
+
 { NULL }
 };
 
-
 #define N 0x00
 #define P 0x01
 static unsigned char charclasses[128] = {
@@ -555,8 +561,8 @@
   name = NULL;
   for (i=0; oid_name_tbl[i].name; i++)
     {
-      if (oid_name_tbl[i].source == 1
-          && node->len == oid_name_tbl[i].oidlen
+/*Все oid-ы из DN переводим в текстовую форму*/
+      if (node->len == oid_name_tbl[i].oidlen
           && !memcmp (image+node->off+node->nhdr,
                       oid_name_tbl[i].oid, node->len))
         {
@@ -604,6 +610,9 @@
     case TYPE_UTF8_STRING:
       append_utf8_value (image+node->off+node->nhdr, node->len, sb);
       break;
+/*Добавляем обработку NUMERIC_STRING*/
+    case TYPE_NUMERIC_STRING:
+
     case TYPE_PRINTABLE_STRING:
     case TYPE_IA5_STRING:
       /* we assume that wrong encodings are latin-1 */
diff -u KSBA_ORIG/keyinfo.c KSBA/keyinfo.c
--- KSBA_ORIG/keyinfo.c	2015-10-28 13:41:48.000000000 +0300
+++ KSBA/keyinfo.c	2018-07-19 09:03:27.936234230 +0300
@@ -45,7 +45,6 @@
 #include "convert.h"
 #include "ber-help.h"
 
-
 /* Constants used for the public key algorithms.  */
 typedef enum
   {
@@ -98,6 +97,19 @@
     "1.2.840.10045.2.1", /*  ecPublicKey */
     "\x2a\x86\x48\xce\x3d\x02\x01", 7,
     1, PKALGO_ECC, "ecc", "q", "\x80" },
+/*oid-ы ГОСТ-овых ключей*/
+  { /* GOST3410-2001 */
+    "1.2.643.2.2.19", /*  gostPublicKey-2001 */
+    "\x2a\x85\x03\x02\x02\x13", 6,
+    1, PKALGO_ECC, "ecc", "q", "\x80" },
+  { /* GOST3410-2012-256 */
+    "1.2.643.7.1.1.1.1", /*  gostPublicKey-2012-256 */
+    "\x2a\x85\x03\x07\x01\x01\x01\x01", 8,
+    1, PKALGO_ECC, "ecc", "q", "\x80" },
+  { /* GOST3410-2012-512 */
+    "1.2.643.7.1.1.1.2", /*  gostPublicKey-2012-512 */
+    "\x2a\x85\x03\x07\x01\x01\x01\x02", 8,
+    1, PKALGO_ECC, "ecc", "q", "\x80" },
 
   {NULL}
 };
@@ -209,6 +221,31 @@
     "1.3.36.3.4.3.2.2",     /* sigS_ISO9796-2rndWithrsa_ripemd160 */
     "\x2B\x24\x03\x04\x03\x02\x02", 7,
     0, PKALGO_RSA, "rsa", "s", "\x82", NULL, NULL, "rmd160" },
+  { /* GOST3410-2001 */
+    "1.2.643.2.2.19", /*  gostPublicKey-2001 */
+    "\x2a\x85\x03\x02\x02\x13", 6,
+    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "gostr3411_CP" },
+  { /* GOST3410-2012-256 */
+    "1.2.643.7.1.1.1.1", /*  gostPublicKey-2012-256 */
+    "\x2a\x85\x03\x07\x01\x01\x01\x01", 8,
+    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog256"},
+  { /* GOST3410-2012-512 */
+    "1.2.643.7.1.1.1.2", /*  gostPublicKey-2012-512 */
+    "\x2a\x85\x03\x07\x01\x01\x01\x02", 8,
+    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog512"},
+
+  { /* GOST3411-2012-256 */
+    "1.2.643.7.1.1.3.2", /*  STRIBOG256 */
+    "\x2a\x85\x03\x07\x01\x01\x03\x02", 8,
+    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog256" },
+  { /* GOST3411-2012-512 */
+    "1.2.643.7.1.1.3.3", /*  STRIBOG512 */
+    "\x2a\x85\x03\x07\x01\x01\x03\x03", 8,
+    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog512" },
+  { /* GOST3410-2001-Signature */
+    "1.2.643.2.2.3", /*  gosrPublicKey-2001 avec signature */
+    "\x2a\x85\x03\x02\x02\x03", 6,
+    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "gostr3411_CP" },
 
   {NULL}
 };
@@ -218,6 +255,20 @@
     "1.2.840.113549.1.1.1", /* rsaEncryption (RSAES-PKCA1-v1.5) */
     "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01", 9,
     1, PKALGO_RSA, "rsa", "a", "\x82" },
+/*oid-ы ГОСТ-ых ключей для ассиметричного шифрования*/
+  {
+    "1.2.643.2.2.19", /*GOST R34.10-2001 */
+    "\x2A\x85\x03\x02\x02\x13", 6,
+    1, PKALGO_ECC, "ecc", "a", "\x80" },
+  {
+    "1.2.643.7.1.1.1.1", /*GOST R34.10-2012-256 */
+    "\x2A\x85\x03\x07\x01\x01\x01\x01", 8,
+    1, PKALGO_ECC, "ecc", "a", "\x80" },
+  {
+    "1.2.643.7.1.1.1.2", /*GOST R34.10-2012-512 */
+    "\x2A\x85\x03\x07\x01\x01\x01\x02", 8,
+    1, PKALGO_ECC, "ecc", "a", "\x80" },
+
   {NULL}
 };
 
@@ -267,6 +318,13 @@
     { "1.2.643.2.2.35.1",    "GOST2001-CryptoPro-A" },
     { "1.2.643.2.2.35.2",    "GOST2001-CryptoPro-B" },
     { "1.2.643.2.2.35.3",    "GOST2001-CryptoPro-C" },
+/*дополнительные oid-ы точек эллиптической кривой для ГОСТ Р 34.10-2001/2012*/
+//    "GOST2001-CryptoPro-XchA" 
+    { "1.2.643.2.2.36.0", "GOST2001-CryptoPro-A" }, 
+//    "GOST2001-CryptoPro-XchB" 
+    { "1.2.643.2.2.36.1", "GOST2001-CryptoPro-C" }, 
+
+
     { "1.2.643.7.1.2.1.2.1", "GOST2012-tc26-A"      },
     { "1.2.643.7.1.2.1.2.2", "GOST2012-tc26-B"      },
 
@@ -393,7 +451,8 @@
   /* get the object identifier */
   if (!derlen)
     return gpg_error (GPG_ERR_INV_KEYINFO);
-  c = *der++; derlen--;
+  c = *der++;
+   derlen--;
   if ( c != 0x06 )
     return gpg_error (GPG_ERR_UNEXPECTED_TAG); /* not an OBJECT IDENTIFIER */
   TLV_LENGTH(der);
@@ -418,6 +477,7 @@
       if (!derlen)
         return gpg_error (GPG_ERR_INV_KEYINFO);
       c = *der++; derlen--;
+
       if ( c == 0x05 )
         {
           /*printf ("parameter: NULL \n"); the usual case */
@@ -471,6 +531,7 @@
       else
         {
 /*            printf ("parameter: with tag %02x - ignored\n", c); */
+
           TLV_LENGTH(der);
           seqlen -= der - startparm;
           /* skip the value */
@@ -692,6 +753,8 @@
   const unsigned char *ctrl;
   const char *elem;
   struct stringbuf sb;
+  int gost_key;
+  char *parm_oid_hash = NULL;
 
   *r_string = NULL;
 
@@ -701,6 +764,7 @@
   c = *der++; derlen--;
   if ( c != 0x30 )
     return gpg_error (GPG_ERR_UNEXPECTED_TAG); /* not a SEQUENCE */
+
   TLV_LENGTH(der);
   /* and now the inner part */
   err = get_algorithm (1, der, derlen, &nread, &off, &len, &is_bitstr,
@@ -715,13 +779,36 @@
            && !memcmp (der+off, pk_algo_table[algoidx].oid, len))
         break;
     }
+
   if (!pk_algo_table[algoidx].oid)
     return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM);
   if (!pk_algo_table[algoidx].supported)
     return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
+/*Определяем тип ключа 1 - ГОСТ-овый ключ*/
+  gost_key = !memcmp(pk_algo_table[algoidx].oidstring, "1.2.643", 7);
 
   if (parm_off && parm_len && parm_type == TYPE_OBJECT_ID)
     parm_oid = ksba_oid_to_str (der+parm_off, parm_len);
+  else 
+/*Извлекаем параметры ГОСТ-ового ключа*/
+  if (parm_off && parm_len && parm_type == TYPE_SEQUENCE && gost_key && (*(der+parm_off + off - 2) == TYPE_OBJECT_ID)){
+/*Вытаскиваем oid curve для ГОСТ-ключа*/
+    int len_hash;
+    int len_curve;
+    unsigned char* addr_hash;
+    unsigned char* addr_curve;
+    len_curve = (int) *(der+parm_off + off -1);
+    addr_curve = der+parm_off + off;
+    parm_oid = ksba_oid_to_str (addr_curve, len_curve);
+/*Вытаскиваем oid хэша для ГОСТ-ключа*/
+    if( *(addr_curve + len_curve)== TYPE_OBJECT_ID) {
+	len_hash = (unsigned int) *(der+parm_off + off + len_curve + 1);
+	addr_hash = addr_curve + len_curve + 2;
+	parm_oid_hash = ksba_oid_to_str (addr_hash, len_hash);
+    }
+/*Вытаскиваем oid алгоритма шифрования для ГОСТ-ключа*/
+  }
+
   else if (parm_off && parm_len)
     {
       parmder = der + parm_off;
@@ -762,6 +849,13 @@
       put_stringbuf_sexp (&sb, "curve");
       put_stringbuf_sexp (&sb, parm_oid);
       put_stringbuf (&sb, ")");
+/*Устанавливаем oid-хэша для ГОСТ-ового ключа*/
+      if(gost_key && parm_oid_hash) {
+        put_stringbuf (&sb, "(");
+	put_stringbuf_sexp (&sb, "hash");
+        put_stringbuf_sexp (&sb, parm_oid_hash);
+        put_stringbuf (&sb, ")");
+      }
     }
 
   /* If parameters are given and we have a description for them, parse
@@ -851,6 +945,43 @@
           put_stringbuf (&sb, "(");
           tmp[0] = *elem; tmp[1] = 0;
           put_stringbuf_sexp (&sb, tmp);
+/*Извлечение значения открытого ключа в соответствии с рекомендациями TK-26*/
+          if(gost_key){
+            unsigned char pk[129];
+            unsigned char *x;
+            unsigned char *y;
+            int len_pk;
+            int len_xy;
+	    int i;
+	    unsigned char c_inv;
+	    int offset;
+            pk[0] = 0x04;
+            if(len == 131 || len == 66){
+        	offset = 0;
+    		if(der[0] == 0x04 && der[1] & 0x80)
+    		    offset = 3;
+    		else if(der[0] == 0x04 && der[1] & 0x40)
+    		    offset = 2;
+		len_pk = len - offset;
+		memcpy(&pk[1], der + offset, len_pk);
+		x = &pk[1];
+		len_xy = len_pk / 2;
+		y = x + len_xy;
+/*REVERT-INVERTIROVANIE*/
+        	for (i = 0; i < (len_xy/2); i++) {
+            	    c_inv = *(x + i);
+            	    *(x + i) = *(x + len_xy - i - 1);
+            	    *(x + len_xy - i - 1) = c_inv;
+        	}
+        	for (i = 0; i < (len_xy/2); i++) {
+            	    c_inv = y[i];
+            	    y[i] = y[len_xy - i -1];
+            	    y[len_xy - i - 1] = c_inv;
+        	}
+        	put_stringbuf_mem_sexp (&sb, pk , len_pk + 1);
+	    }
+          } else
+
           put_stringbuf_mem_sexp (&sb, der, len);
           der += len;
           derlen -= len;
@@ -1606,6 +1737,7 @@
   const unsigned char *ctrl;
   const char *elem;
   struct stringbuf sb;
+  int gost_sign;
 
   /* FIXME: The entire function is very similar to keyinfo_to_sexp */
   *r_string = NULL;
@@ -1615,7 +1747,6 @@
   else
     algo_table = enc_algo_table;
 
-
   err = get_algorithm (1, der, derlen, &nread, &off, &len, &is_bitstr,
                        NULL, NULL, NULL);
   if (err)
@@ -1628,11 +1759,16 @@
            && !memcmp (der+off, algo_table[algoidx].oid, len))
         break;
     }
+
   if (!algo_table[algoidx].oid)
     return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM);
+
   if (!algo_table[algoidx].supported)
     return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
 
+/*Определяем тип подписи по oid-у*/
+  gost_sign = !memcmp(algo_table[algoidx].oidstring, "1.2.643", 7);
+
   der += nread;
   derlen -= nread;
 
@@ -1682,8 +1818,21 @@
 
           put_stringbuf (&sb, "(");
           tmp[0] = *elem; tmp[1] = 0;
+/*Если ЭП по ГОСТ, то r находится справа, а s находится слева */
+	  if(gost_sign == 1 && algo_table == sig_algo_table){
+	    put_stringbuf_sexp (&sb, "r");
+	    put_stringbuf_mem_sexp (&sb, der+(len/2), len/2);
+    	    put_stringbuf (&sb, ")");
+    	    put_stringbuf (&sb, "(");
+	    put_stringbuf_sexp (&sb, "s");
+	    put_stringbuf_mem_sexp (&sb, der, len/2);
+	  }
+	  else{
+	  
           put_stringbuf_sexp (&sb, tmp);
           put_stringbuf_mem_sexp (&sb, der, len);
+	  }
+
           der += len;
           derlen -= len;
           put_stringbuf (&sb, ")");


Имеющиеся комментарии позволяют понять логику вносимых изменений, которая в первую очередь определяется рекомендациями ТК-26.

Осталось внести изменения в модули gnupg-agent (каталог agent) и модуль gpgsm (каталог sm).

Патч для модуля gnupg-agent находится здесь
diff -u AGENT_ORIG/call-scd.c AGENT/call-scd.c
--- AGENT_ORIG/call-scd.c	2017-05-15 15:13:22.000000000 +0300
+++ AGENT/call-scd.c	2018-07-19 09:27:36.904313900 +0300
@@ -806,6 +806,14 @@
     case GCRY_MD_SHA256: return "--hash=sha256";
     case GCRY_MD_SHA384: return "--hash=sha384";
     case GCRY_MD_SHA512: return "--hash=sha512";
+/*ГОСТ Р 34.11-2001/2012*/
+    case GCRY_MD_STRIBOG512:
+	return "--hash=stribog512";
+    case GCRY_MD_STRIBOG256:
+	return "--hash=stribog256";
+    case GCRY_MD_GOSTR3411_CP:
+	return "--hash=gostr3411_CP";
+
     default:             return "";
     }
 }
@@ -884,6 +892,7 @@
   else
     snprintf (line, sizeof line, "PKSIGN %s %s",
               hash_algo_option (mdalgo), keyid);
+
   rc = assuan_transact (ctrl->scd_local->ctx, line,
                         put_membuf_cb, &data,
                         inq_needpin, &inqparm,
@@ -901,6 +910,7 @@
     }
 
   *r_buf = get_membuf (&data, r_buflen);
+
   return unlock_scd (ctrl, 0);
 }
 
diff -u AGENT_ORIG/divert-scd.c AGENT/divert-scd.c
--- AGENT_ORIG/divert-scd.c	2017-04-03 18:13:56.000000000 +0300
+++ AGENT/divert-scd.c	2018-07-19 09:29:03.896318684 +0300
@@ -160,6 +160,17 @@
       log_error ("no object identifier for algo %d\n", algo);
       return gpg_error (GPG_ERR_INTERNAL);
     }
+/*ГОСТ-алгоритмы*/
+    switch(algo) {
+		case GCRY_MD_STRIBOG512:
+		case GCRY_MD_STRIBOG256:
+		case GCRY_MD_GOSTR3411_CP:
+		case GCRY_MD_GOSTR3411_94:
+		    asnlen = 0;
+		    break;
+		default :
+		    break;
+    }
 
   frame = xtrymalloc (asnlen + digestlen);
   if (!frame)
@@ -423,6 +434,7 @@
   (void)desc_text;
 
   rc = ask_for_card (ctrl, shadow_info, &kid);
+
   if (rc)
     return rc;
 
@@ -498,7 +510,7 @@
   n = snext (&s);
   if (!n)
     return gpg_error (GPG_ERR_INV_SEXP);
-  if (smatch (&s, n, "rsa"))
+  if (smatch (&s, n, "rsa") || smatch (&s, n, "ecc"))
     {
       if (*s != '(')
         return gpg_error (GPG_ERR_UNKNOWN_SEXP);
diff -u AGENT_ORIG/pksign.c AGENT/pksign.c
--- AGENT_ORIG/pksign.c	2017-05-15 15:13:22.000000000 +0300
+++ AGENT/pksign.c	2018-07-19 09:30:28.771323350 +0300
@@ -328,6 +328,7 @@
       int is_RSA = 0;
       int is_ECDSA = 0;
       int is_EdDSA = 0;
+      int is_ECGOST = 0;
 
       rc = agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey);
       if (rc)
@@ -345,6 +346,21 @@
             is_RSA = 1;
           else if (key_type == GCRY_PK_ECDSA)
             is_ECDSA = 1;
+/*Проверка ГОСТ-алгоритма*/
+	  if (is_ECDSA) {
+	    switch(ctrl->digest.algo)
+	    {
+		case GCRY_MD_STRIBOG512:
+		case GCRY_MD_STRIBOG256:
+		case GCRY_MD_GOSTR3411_CP:
+		case GCRY_MD_GOSTR3411_94:
+		    is_ECGOST = 1;
+		    break;
+		default :
+		    is_ECGOST = 0;
+		    break;
+	    }
+	  }
         }
 
       {
@@ -361,7 +377,7 @@
       }
       if (rc)
         {
-          log_error ("smartcard signing failed: %s\n", gpg_strerror (rc));
+          log_error ("agent_pksign_do: smartcard signing failed: %s\n", gpg_strerror (rc));
           goto leave;
         }
 
@@ -396,7 +412,8 @@
 
           r_buflen = s_buflen = len/2;
 
-          if (*buf & 0x80)
+          if ((*buf & 0x80) && !is_ECGOST)
+
             {
               r_buflen++;
               r_buf_allocated = xtrymalloc (r_buflen);
@@ -409,8 +426,8 @@
             }
           else
             r_buf = buf;
+          if ((*(buf + len/2) & 0x80) && !is_ECGOST)
 
-          if (*(buf + len/2) & 0x80)
             {
               s_buflen++;
               s_buf_allocated = xtrymalloc (s_buflen);
@@ -427,6 +444,15 @@
           else
             s_buf = buf + len/2;
 
+    if(is_ECGOST){
+          rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(gost(r%b)(s%b))(hash %s))",
+                                s_buflen, s_buf,
+                                r_buflen, r_buf,
+                                gcry_md_algo_name(ctrl->digest.algo));
+          gcry_log_debugsxp ("SIG_VAL", s_sig);
+    }
+    else
+
           rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))",
                                 r_buflen, r_buf,
                                 s_buflen, s_buf);


Патч для модуля gpgsm можно увидеть здесь
diff -u SM_ORIG/call-agent.c SM/call-agent.c
--- SM_ORIG/call-agent.c	2017-04-03 18:13:56.000000000 +0300
+++ SM/call-agent.c	2018-07-19 09:37:08.411345324 +0300
@@ -234,6 +234,7 @@
   rc = start_agent (ctrl);
   if (rc)
     return rc;
+
   inq_parm.ctrl = ctrl;
   inq_parm.ctx = agent_ctx;
 
@@ -313,6 +314,14 @@
     case GCRY_MD_RMD160:hashopt = "--hash=rmd160"; break;
     case GCRY_MD_MD5:   hashopt = "--hash=md5"; break;
     case GCRY_MD_SHA256:hashopt = "--hash=sha256"; break;
+/*ГОСТ-алгоритмы*/
+    case GCRY_MD_STRIBOG512:
+	hashopt = "--hash=stribog512"; break;
+    case GCRY_MD_STRIBOG256:
+	hashopt = "--hash=stribog256"; break;
+    case GCRY_MD_GOSTR3411_CP:
+	hashopt = "--hash=gostr3411_CP"; break;
+
     default:
       return gpg_error (GPG_ERR_DIGEST_ALGO);
     }
@@ -357,7 +366,20 @@
       xfree (sigbuf);
       return 0;
     }
-  p = stpcpy (p, "(7:sig-val(3:rsa(1:s" );
+/*Обработка для ГОСТ-алгоритмов*/
+  switch(digestalgo)
+    {
+    case GCRY_MD_STRIBOG512:
+    case GCRY_MD_STRIBOG256:
+    case GCRY_MD_GOSTR3411_CP:
+    case GCRY_MD_GOSTR3411_94:
+	p = stpcpy (p, "(7:sig-val(3:ecc(1:s" );
+	break;
+    default: 
+	p = stpcpy (p, "(7:sig-val(3:rsa(1:s" );
+	break;
+    }
+
   sprintf (p, "%u:", (unsigned int)sigbuflen);
   p += strlen (p);
   memcpy (p, sigbuf, sigbuflen);
diff -u SM_ORIG/certcheck.c SM/certcheck.c
--- SM_ORIG/certcheck.c	2017-04-03 18:13:56.000000000 +0300
+++ SM/certcheck.c	2018-07-19 09:37:52.028347722 +0300
@@ -71,11 +71,13 @@
   size_t nframe;
   unsigned char *frame;
 
-  if (pkalgo == GCRY_PK_DSA || pkalgo == GCRY_PK_ECDSA)
+  if (pkalgo == GCRY_PK_DSA || pkalgo == GCRY_PK_ECDSA || pkalgo == GCRY_PK_ECC)
+
     {
       unsigned int qbits;
 
-      if ( pkalgo == GCRY_PK_ECDSA )
+      if ( pkalgo == GCRY_PK_ECDSA || pkalgo == GCRY_PK_ECC)
+      
         qbits = gcry_pk_get_nbits (pkey);
       else
         qbits = get_dsa_qbits (pkey);
@@ -169,7 +171,7 @@
       memcpy ( frame+n, gcry_md_read(md, algo), len ); n += len;
       assert ( n == nframe );
     }
-  if (DBG_CRYPTO)
+//  if (DBG_CRYPTO)
     {
       int j;
       log_debug ("encoded hash:");
@@ -177,6 +179,22 @@
         log_printf (" %02X", frame[j]);
       log_printf ("\n");
     }
+    int i;
+    for (i = 0; i < (nframe/2); i++) {
+	unsigned char c;
+        c = frame[i];
+        frame[i] = frame[nframe - i -1];
+        frame[nframe - i - 1] = c;
+    }
+    if (DBG_CRYPTO)
+    {
+      int j;
+      log_debug ("GOST encoded hash:");
+      for (j=0; j < nframe; j++)
+        log_printf (" %02X", frame[j]);
+      log_printf ("\n");
+    }
+  }
 
   gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, n, &nframe);
   xfree (frame);
diff -u SM_ORIG/gpgsm.c SM/gpgsm.c
--- SM_ORIG/gpgsm.c	2017-05-15 15:13:22.000000000 +0300
+++ SM/gpgsm.c	2018-07-19 09:38:33.277349990 +0300
@@ -471,6 +471,7 @@
 {
   switch (algo)
     {
+    case GCRY_PK_ECC:
     case GCRY_PK_RSA:
     case GCRY_PK_ECDSA:
       return gcry_pk_test_algo (algo);
@@ -1577,6 +1578,10 @@
     opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.3";
   else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA256") )
     opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.4";
+  else if (!strcmp (opt.def_cipher_algoid, "GOST28147") )
+//    opt.def_cipher_algoid = "1.2.643.2.2.21";
+    opt.def_cipher_algoid = "1.2.643.2.2.31.1";
+
 
   if (cmd != aGPGConfList)
     {
diff -u SM_ORIG/keylist.c SM/keylist.c
--- SM_ORIG/keylist.c	2017-05-15 15:13:22.000000000 +0300
+++ SM/keylist.c	2018-07-19 20:20:11.560466784 +0300
@@ -769,6 +769,42 @@
     unsigned int nbits;
 
     algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits));
+/*Информация по ГОСТ-ключам*/
+    if(!strncasecmp(algoname, "ecc", 3)){
+	gpg_error_t err;
+	ksba_sexp_t p;
+	size_t n;
+	gcry_sexp_t s_pkey, l2;
+	const char *str_hash;
+	const char *str_curve;
+  /* Get the public key from the issuer certificate.  */
+	p = ksba_cert_get_public_key (cert);
+	n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+	err = gcry_sexp_sscan ( &s_pkey, NULL, p, n);
+	ksba_free (p);
+/*Ищем хэш*/
+	l2 = gcry_sexp_find_token (s_pkey, "hash", 0);
+	str_hash = gcry_sexp_nth_string (l2, 1);
+        gcry_sexp_release (l2);
+/*Ищем curve*/
+	l2 = gcry_sexp_find_token (s_pkey, "curve", 0);
+	str_curve = gcry_sexp_nth_string (l2, 1);
+        gcry_sexp_release (l2);
+        gcry_sexp_release (s_pkey);
+	if(!memcmp(str_hash, "1.2.643.7", 9)){
+/*    GOST R 34.10-2012-512  */
+		es_fprintf (fp, "      keyType: %u bit %s (Curve: %s))\n",
+            	    nbits, "GOST R 34.10-2012", str_curve);
+	} else if(!memcmp(str_hash, "1.2.643.2", 9)){
+/*    GOST R 34.10-2012-246   */
+		es_fprintf (fp, "      keyType: %u bit %s (Curve: %s))\n",
+            	    nbits, "GOST R 34.10-2001", str_curve);
+	} else
+	    es_fprintf (fp, "      keyType: %u bit %s\n",
+                nbits, algoname? algoname:"?");
+    }
+    else
+
     es_fprintf (fp, "      keyType: %u bit %s\n",
                 nbits, algoname? algoname:"?");
   }
@@ -1118,6 +1154,42 @@
     unsigned int nbits;
 
     algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits));
+/*Информация по ГОСТ-ключам*/
+    if(!strncasecmp(algoname, "ecc", 3)){
+	gpg_error_t err;
+	ksba_sexp_t p;
+	size_t n;
+	gcry_sexp_t s_pkey, l2;
+	const char *str_hash;
+	const char *str_curve;
+  /* Get the public key from the issuer certificate.  */
+	p = ksba_cert_get_public_key (cert);
+	n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+	err = gcry_sexp_sscan ( &s_pkey, NULL, p, n);
+	ksba_free (p);
+/*Ищем хэш*/
+	l2 = gcry_sexp_find_token (s_pkey, "hash", 0);
+	str_hash = gcry_sexp_nth_string (l2, 1);
+        gcry_sexp_release (l2);
+/*Ищем curve*/
+	l2 = gcry_sexp_find_token (s_pkey, "curve", 0);
+	str_curve = gcry_sexp_nth_string (l2, 1);
+        gcry_sexp_release (l2);
+        gcry_sexp_release (s_pkey);
+	if(!memcmp(str_hash, "1.2.643.7", 9)){
+/*    GOST R 34.10-2012-512  */
+		es_fprintf (fp, "      keyType: %u bit %s (Curve: %s))\n",
+            	    nbits, "GOST R 34.10-2012", str_curve);
+	} else if(!memcmp(str_hash, "1.2.643.2", 9)){
+/*    GOST R 34.10-2012-246   */
+		es_fprintf (fp, "      keyType: %u bit %s (Curve: %s))\n",
+            	    nbits, "GOST R 34.10-2001", str_curve);
+	} else
+	    es_fprintf (fp, "      keyType: %u bit %s\n",
+                nbits, algoname? algoname:"?");
+    }
+    else
+
     es_fprintf (fp, "     key type: %u bit %s\n",
                 nbits, algoname? algoname:"?");
   }
diff -u SM_ORIG/sign.c SM/sign.c
--- SM_ORIG/sign.c	2017-05-15 15:13:22.000000000 +0300
+++ SM/sign.c	2018-07-19 09:41:05.738358373 +0300
@@ -33,7 +33,41 @@
 
 #include "keydb.h"
 #include "../common/i18n.h"
+char *cert_get_digest_algo_from_pk(ksba_cert_t cert) {
+  gpg_error_t err;
+  ksba_sexp_t p;
+  size_t n;
+  gcry_sexp_t s_pubkey;
+  char buf[50];
+  const char *s;
+  gcry_sexp_t h_pk;
+
+  /* Get the public key from the certificate.  */
+  p = ksba_cert_get_public_key (cert);
+  n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+  if (!n)
+    {
+      ksba_free (p);
+      return NULL;
+    }
+  err = gcry_sexp_sscan ( &s_pubkey, NULL, p, n);
+  ksba_free (p);
+  if (err) {
+      return NULL;
+    }
 
+  h_pk = gcry_sexp_find_token (s_pubkey, "hash", 0);
+  if (h_pk){
+    s = gcry_sexp_nth_data (h_pk, 1, &n);
+    memcpy(buf, s, n);
+    buf[n] = '\0';
+    gcry_sexp_release (s_pubkey);
+    gcry_sexp_release (h_pk);
+    return (strdup(buf));
+  } else {
+    return NULL;
+  }
+}
 
 /* Hash the data and return if something was hashed.  Return -1 on error.  */
 static int
@@ -301,8 +335,6 @@
   return err;
 }
 
-
-
 
 /* Perform a sign operation.
 
@@ -328,6 +360,8 @@
   ksba_isotime_t signed_at;
   certlist_t cl;
   int release_signerlist = 0;
+  const unsigned char *shash;
+  size_t lshash;
 
   audit_set_type (ctrl->audit, AUDIT_TYPE_SIGN);
 
@@ -435,8 +469,15 @@
         }
       else
         {
+	  oid = (char *)cert_get_digest_algo_from_pk(cl->cert);
+	  if (oid == NULL || strncmp(oid, "1.2.643", 7))
+        
           oid = ksba_cert_get_digest_algo (cl->cert);
           cl->hash_algo = oid ? gcry_md_map_name (oid) : 0;
+	  if(oid != NULL && !strcmp(oid, "1.2.643.2.2.30.1")){
+	    cl->hash_algo = GCRY_MD_GOSTR3411_CP; 
+	  }
+
         }
       switch (cl->hash_algo)
         {
@@ -446,6 +487,15 @@
         case GCRY_MD_SHA256: oid = "2.16.840.1.101.3.4.2.1"; break;
         case GCRY_MD_SHA384: oid = "2.16.840.1.101.3.4.2.2"; break;
         case GCRY_MD_SHA512: oid = "2.16.840.1.101.3.4.2.3"; break;
+	case GCRY_MD_STRIBOG256:	/* GOST R 34.11-2012, 256 bit.  */
+	    oid = "1.2.643.7.1.1.2.2"; break;
+	case GCRY_MD_STRIBOG512:	 /* GOST R 34.11-2012, 512 bit.  */
+	    oid = "1.2.643.7.1.1.2.3"; break;
+	case GCRY_MD_GOSTR3411_CP:	/* GOST R 34.11-94.  */
+	    oid = "1.2.643.2.2.9"; break;
+	case GCRY_MD_GOSTR3411_94:	/* GOST R 34.11-94. TK26 */
+	    oid = "1.2.643.2.2.9"; break;
+
 /*         case GCRY_MD_WHIRLPOOL: oid = "No OID yet"; break; */
 
         case GCRY_MD_MD5:  /* We don't want to use MD5.  */


Патчи модулей gnupg-agent и gpgsm делались для версии gnupg-2.1.21. Перенести эти патчи скажем на версию gnupg-2.2.9 не составляет большого труда.

После сборки доработанных версий libksba, gnupg, утилиты gnupg_pkcs11_scd и установки их в систему, для обеспечения интерфейса с токенами PKCS#11 необходимо в конфигурационный файл gpg-agent.conf добавить следующие строки:

…
scdaemon-program /usr/local/bin64/gnupg-pkcs11-scd
pinentry-program /usr/bin/pinentry-qt
…

Затем необходимо в конфигурационном файле gnu-pkcs11-scd.conf указать библиотеки PKCS#11 для токенов, которые будут использоваться:

#В примере предполагается использование токена 
#с библиотекой /usr/local/lib64/libls11sw2016.so
…
# Comma-separated list of available provider names. Then set
# attributes for each provider using the provider-[name]-attribute
# syntax.
providers mytokenpkcs11
provider-mytokenpkcs11-library /usr/local/lib64/libls11sw2016.so
#Если используется, например, токен ruTokenECP
#provider-metokenpkcs11-library /usr/local/lib64/librutokenecp.so
provider-mytokenpkcs11-cert-private
…

Все готово к работе. Проверяем, что модуль gpg-agent запущен:

bash-4.3$ gpg-agent --daemon 
gpg-agent[1196]: enabled debug flags: ipc 
gpg-agent: агент gpg уже запущен - еще один, новый, запущен не будет 
gpg-agent: secmem usage: 0/32768 bytes in 0 blocks 
bash-4.3$

Итак, на арену выходит kleopatra:



Кнопка «Импорт» используется прежде всего для импорта в систему корневых сертификатов, которые необходимы для проверки валидности личных сертификатов.

При запуске утилиты kleopatra может быть запрошен PIN-код к подключенному токену:



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

Итак, вы бираем пункт меню «Сервис → Manage Smartcards». Если ранее с токена сертификаты не загружались, то высветится следующее окно:



Для того, чтобы увидеть сертификаты на подключенном токене, достаточно нажать кнопку LoadCertificates (Загрузить Сертификаты):



Если корневые сертификаты отсутствуют для сертификатов пользователя отсутствуют (not certified), то необходимо их установить (Файл → Импорт или кнопка Импорт):



Теперь, после установки корневых сертификатов, хорошие сертификаты можно видеть в списке доверенных сертификатов:



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

Все это наглядно можно увидеть при установке сертификата, который по умолчанию будет использоваться для подписания писем в почтовом клиенте KMail (Настройки → Настройки KMail → Профили → <профиль владельца> → Изменить → Криптография → Сертификат подписи SMIME):



Можно также попытаться подписать файл «плохим сертификатом»:



Попытка подписать документ «плохим сертификатом» не удается, надо использовать хорошие сертификаты:



Для того, чтобы письмо при отправке было подписано электронной подписью достаточно выставить галочку Параметры → Подписать письмо:



Письмо будет автоматически подписано при отправке (кнопка отправить).

В заключение еще раз хочется обратить разработчиков приложений для отечественной ИОК, что проект GnuPG как среда разработки мало чем уступает по своим возможностям как OpenSSL, так и NSS.
Теги:
Хабы:
+12
Комментарии 9
Комментарии Комментарии 9

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн