Приветствую всех.
Шлю запросы по https с подписью.
Кратко опишу свой путь.
Использовал [B]Delphi 7[/B](лиценз.),
[B]P12FromGostCSP[/B] для получения *.key (закрытый ключ для подписи).
[B]SoapUI[/B] для получения и отладки запросов.
Статья на Хабре "[I]Взаимодействие с ГИС ЖКХ с помощью stunnel и openssl по ГОСТу[/I]".
[B]OpenSSL[/B] с гост, по ссылке той статьи.
[B]Python[/B] + [B]Eclipse[/B] для изучения алгоритма подписи.
Компоненты [B]Indy[/B] поддерживают OpenSSL, т.о. защищенный канал "из коробки".
Каждый метод ГИСа, реализовал отдельным классом, с генерацией каноникализированного xml ручками. Для удобства наследовал от интерфейса.
Для подписи, по алгоритму статьи, написал класс для подписи, процедуры шифрования вызываю из dll, скачал заголовочный файл [U]libeay32.pas[/U], каноникализации в Delphi 7 нет, но т.к. запросы сами каноникализированы, то разобрался с namespace и всё, готов класс.
Парсинг ответных сообщений с помощью [U]SimpleXML.pas[/U], для логирования - [U]LDSLogger.pas[/U].
Задача с ГИСом пока фоновая, есть поважнее проекты.
КриптоПро не использую, это тот же самый OpenSSL + сервисные функции, и за деньги.
stunnel также не нужен использую Indy
[SIZE=85px][COLOR=greenpt]Отправлено спустя 11 минуты 42 секунды:[/COLOR][/SIZE]
Шифрование по ГОСТу (алгоритм)
[code:fs2tuymr]function SignGOST(const Data, KeyFN: string): string;
var
dlen: Integer;
slen: Cardinal;
md: pEVP_MD;
Key: pEVP_PKEY;
mdctx: pEVP_MD_CTX;
memout, Base64: pBIO;
b64Length: Integer;
outBuf: array[0..4095] of Char;
mdValue: array[0..EVP_MAX_MD_SIZE] of byte;
begin
dlen := Length(Data);
Key := ReadPrivateKey(KeyFN); // читаем файл закрытого ключа
slen := EVP_PKEY_size(Key); // определяем размер ключа
SetLength(Result, slen); // размер подписи будет таким же
md := EVP_get_digestbyname('md_gost94'); // получаем доступ к хэш-функции
New(mdctx); // формируем контекст подписи
EVP_SignInit(mdctx, md); // устанавливаем хэш-функцию, которая буддет использована
EVP_SignUpdate(mdctx, PChar(Data), dlen); // подписываем данные
EVP_SignFinal(mdctx, @mdValue, slen, Key); // завершаем формирование подписи
EVP_PKEY_free(Key); // освобождаем ресурсы
Dispose(mdctx);
// вывод в Base64
Base64 := BIO_new(BIO_f_base64); // BIO типа base64
memout := BIO_new(BIO_s_mem); // BIO в памяти для чтения-записи
Base64 := BIO_push(Base64, memout);
BIO_write(Base64, @mdValue, slen); // Пытается записать в Base64 sLen байт из буфера Result. Возвращает количество записанных байт
BIO_flush(Base64);
b64Length := BIO_read(memout, @outbuf, 4096);
outbuf[b64Length - 1] := #0;
Result := StrPas(@outbuf);
end;[/code:fs2tuymr]
[SIZE=85px][COLOR=greenpt]Отправлено спустя 2 минуты 23 секунды:[/COLOR][/SIZE]
Заголовок класса подписи
[code:fs2tuymr]unit Xades;
interface
//{$DEFINE TEST}
uses
Classes;
(*
Класс TXades подписывает XML,
пример:
x: TXades;
создание при указании файла ключа -> x := TXades.Create('C:\cert.key');
создание при передаче параметров -> x := TXades.Create(key, digest2, x509_issuer_name, x509_sn);
возврат текста подписанного XML -> mmo1.Text := x.GetSignXML(XML, SignedId);
*)
type
TXades = class
private
FItemXML: TStringList; // элементы сообщения
FBodyXML: TStringList; // тело сообщения
FSignXML: TStringList; // подписанный XML
FNameSpace: TStringList; // пространство имен
FSignedInfo: TStringList;// информация о подписи
Fsignature_id: string; // генерируются
Fsigned_id: string; // ID запроса, который указываем, как хотим и на который ориентируемся, подписывая запрос
Fdigest1: string; // Берётся содержимое тега <ns1:Body>), канонизируется алгоритмом C14N (exclusive=True), считается от неё хеш-сумма по ГОСТу и выводится в виде BASE64
Fdigest2: string; // Берётся сертификат в x509, декодируется из BASE64, считается от него хеш-сумма и вывод кодируется в BASE64
Fdigest3: string; // Формируется содержимое тега <xades:SignedProperties>, канонизируется алгоритмом C14N (exclusive=False) и от содержимого считается
Fsignature_value: string; // Формируется блок <ds:SignedInfo>, канонизируется алгоритмом C14N (exclusive=False), подписывается и кодируется в BASE64.
Fx590_cert: string; // x509 ключ из файла
Fsigning_time: string; // запоминается текущее время
Fx509_issuer_name: string; // Данные УЦ, выпустившего сертификат
Fx509_sn: string; // номер сертификата
Fkey: string; // имя и путь ключа
procedure GetDigest1(); // Вычисление Fdigest1
procedure GetDigest3(); // Вычисление Fdigest3
procedure LoadNameSpace(); // Поиск и загрузка пространства имен
procedure SetSignatureId(); // Генерация Id
procedure SetTextSignXML(); // Все полученные значения вносятся в шаблон XML-документа формата XAdES-BES
procedure Signature(); // Вычисление Fsignature_value
public
constructor Create(const key, digest2, x509_issuer_name, x509_sn: string); overload;
constructor Create(const Key: string); overload;
destructor Destroy; override;
function GetSignXML(const XML, SignedId: string): string;
{$IFDEF TEST}
function GetDigests(const XML, SignedId: string): string; // значения: Fdiges1, Fdiges2, Fdiges3
{$ENDIF}
end;
implementation[/code:fs2tuymr]
[SIZE=85px][COLOR=greenpt]Отправлено спустя 2 минуты 14 секунды:[/COLOR][/SIZE]
Один из классов для ГИСа
[code:fs2tuymr]{*------------------------------------------------------------------------------
Экспорт сведений об организациях
-------------------------------------------------------------------------------}
unit ExportOrgRegistry;
interface
uses
GIS_Interface;
type
TExportOrgRegistry = class(TInterfacedObject, IGIS)
private
FOGRN: string; /// ОГРН
FAsync: Boolean;
FDate: string;
FGUID: string;
FXML: string;
function Header(): string;
public
constructor Create(const orgPPAGUID, OGRN: string; Async: Boolean = False);
function Async(): Boolean;
function Command(): string;
function Date(): string;
function HeaderAction(): string;
function GetObject: TObject;
function GUID(): string;
function WebService(): string;
function XML(): string;
end;
implementation
uses
GIS_Query, GIS_Const;
{ TExportOrgRegistry }
function TExportOrgRegistry.Async: Boolean;
begin
Result := FAsync;
end;
function TExportOrgRegistry.Command: string;
begin
Result := 'exportOrgRegistry';
if FAsync then
Result := Result + '(Async)';
end;
constructor TExportOrgRegistry.Create(const orgPPAGUID, OGRN: string; Async: Boolean = False);
begin
FAsync := Async;
FOGRN := OGRN;
FDate := SetDate;
FGUID := SetGUID_Message(orgPPAGUID, metExportOrgRegistry);
FXML := Concat(Header(),
MakeXMLnode('soapenv:Body', '', BeginNode),
MakeXMLnode('org:exportOrgRegistryRequest', cVer_ExportOrgRegistry, '', BeginNode),
MakeXMLnode('org:SearchCriteria', '', BeginNode),
MakeXMLnode('org1:OGRN', FOGRN, FullNode),
MakeXMLnode('org:SearchCriteria', '', EndNode),
MakeXMLnode('org:exportOrgRegistryRequest', '', EndNode),
MakeXMLnode('soapenv:Body', '', EndNode),
MakeXMLnode('soapenv:Envelope', '', EndNode));
end;
function TExportOrgRegistry.Date: string;
begin
Result := FDate;
end;
function TExportOrgRegistry.GetObject: TObject;
begin
Result := Self;
end;
function TExportOrgRegistry.GUID: string;
begin
Result := FGUID;
end;
function TExportOrgRegistry.Header: string;
begin
Result := Concat(cHeader, cNS_ExportOrgRegistry, #10,
MakeXMLnode('soapenv:Header', '', BeginNode),
MakeXMLnode('base:ISRequestHeader', '', BeginNode),
FDate, #10, FGUID, #10,
MakeXMLnode('base:ISRequestHeader', '', EndNode),
MakeXMLnode('soapenv:Header', '', EndNode));
end;
function TExportOrgRegistry.HeaderAction: string;
begin
Result := '"urn:exportOrgRegistry"';
end;
function TExportOrgRegistry.WebService: string;
begin
Result := 'ext-bus-org-registry-common-service/services/OrgRegistryCommon';
if FAsync then
Result := Result + 'Async';
end;
function TExportOrgRegistry.XML: string;
begin
Result := FXML;
end;
end.
[/code:fs2tuymr]
[SIZE=85px][COLOR=greenpt]Отправлено спустя 8 минуты 58 секунды:[/COLOR][/SIZE]
Важно! Загрузка ГОСТа из dll (последняя строка)
[code:fs2tuymr](*
инициализация библиотеки
*)
procedure LoadSSL;
begin
OpenSSL_add_all_algorithms;
OpenSSL_add_all_ciphers;
OpenSSL_add_all_digests;
ERR_load_crypto_strings;
ERR_load_RSA_strings;
// используется имя файла, указанное в OPENSSL_CONF
OPENSSL_config(PChar(GetDOSEnvVar('OPENSSL_CONF')));
end;
[/code:fs2tuymr]
[SIZE=85px][COLOR=greenpt]Отправлено спустя 14 минуты 20 секунды:[/COLOR][/SIZE]
Из ИндиЛога заголовок запроса на https (по http похожий):
[I]POST /ext-bus-org-registry-common-service/services/OrgRegistryCommon HTTP/1.0
Content-Type: text/xml; charset=UTF-8
Content-Length: 6333
SOAPAction: "urn:exportOrgRegistry"
Authorization: Basic c2l0OnJaX0dHNzJYU15WZjU1Wlc=
X-Client-Cert-Fingerprint: cb.... (тут мой отпечаток серт.)
Host: 217.107.108.156:10081
Accept: text/html, */*
Accept-Encoding: gzip,deflate, identity
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)[/I]
[SIZE=85px][COLOR=greenpt]Отправлено спустя 5 минуты 19 секунды:[/COLOR][/SIZE]
Для изучения шифрования по ГОСТу помогло изучение "Библиотека libcrypto.Руководство программиста" от КриптоКома.
Всех с наступающим 23 февраля, вспомним как служили!