feat(crypto-service): gRPC-каркас сервиса криптографии (КриптоПро JCP)
- services/crypto-service/proto/crypto.proto — protobuf-контракт VerifyXMLDSig/SignXMLDSig/Health, package ru.zetit.bridgeandjoins.crypto.v1
- services/crypto-service/build.gradle.kts — Gradle Java 21 + protobuf-плагин + shadowJar
- services/crypto-service/src/main/java/.../CryptoServer.java — точка входа на UDS (Netty Epoll)
- services/crypto-service/src/main/java/.../CryptoServiceImpl.java — gRPC-биндинг
- services/crypto-service/src/main/java/.../{Verify,Sign,Health}Handler.java — заглушки операций
- services/crypto-service/src/main/java/.../KeystoreProvider.java — абстракция cryptopro/validata/vipnet/stub
- services/crypto-service/Dockerfile — Liberica JDK 21 → shadowJar → slim
- internal/cryptocli/client.go — Go-клиент по UDS, реализует m2mcore.CryptoVerifier (M1 stub)
- internal/cryptocli/client_test.go — тесты на доступность сокета и ErrNotImplemented
- deploy/docker-compose/docker-compose.yml — добавлен сервис crypto-service с UDS-volume
Реальная криптография КриптоПро JCP подключается после получения
лицензии и jar (положить в services/crypto-service/libs/jcp.jar) и
открытия Maven Central через прокси zetit (для grpc-java/santuario).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Dockerfile для crypto-service.
|
||||
# Базовый образ — Liberica JDK 21 (открытый дистрибутив BellSoft с
|
||||
# поддержкой работы на территории РФ). Можно заменить на любой
|
||||
# OpenJDK 21.
|
||||
FROM bellsoft/liberica-openjdk-debian:21 AS build
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
RUN ./gradlew --no-daemon shadowJar
|
||||
|
||||
FROM bellsoft/liberica-openjre-debian:21-slim
|
||||
RUN useradd -r -u 1100 -g root bj-crypto && \
|
||||
mkdir -p /run/bj && \
|
||||
chown bj-crypto:root /run/bj
|
||||
USER bj-crypto
|
||||
COPY --from=build /src/build/libs/crypto-service-*-all.jar /opt/crypto-service.jar
|
||||
ENV BJ_CRYPTO_SOCKET=/run/bj/crypto.sock \
|
||||
BJ_CRYPTO_PROVIDER=stub
|
||||
ENTRYPOINT ["java", "-jar", "/opt/crypto-service.jar"]
|
||||
@@ -1,24 +1,66 @@
|
||||
# services/crypto-service — Java + КриптоПро JCP
|
||||
# services/crypto-service — крипто-сервис на Java + КриптоПро JCP
|
||||
|
||||
Изолированный сервис для криптографических операций по ГОСТ:
|
||||
gRPC-сервис криптографических операций по ГОСТ Р 34.10-2012:
|
||||
|
||||
- проверка подписи входящих квитанций ЭДО, ответов НРД и сообщений
|
||||
от брокеров;
|
||||
- серверная подпись действий оператора в `admin-ui` (ради соблюдения
|
||||
SLA 2 мин — клиентский КриптоПро на АРМ исключён);
|
||||
- резервный канал отправки в НРД через WS ONYX напрямую (когда ИШ не
|
||||
задействован — упаковка и подпись на нашей стороне);
|
||||
- проверка XMLDSig-подписи входящих квитанций НРД, ответов брокеров,
|
||||
заявлений ЛК;
|
||||
- серверная подпись действий оператора (admin-ui, SLA 2 мин);
|
||||
- резервный канал в НРД через WS ONYX напрямую (когда ИШ не задействован
|
||||
— упаковка и подпись на нашей стороне);
|
||||
- криптографические проверки целостности эталонов в MinIO.
|
||||
|
||||
Стек: Java 21 (Liberica JDK), Apache Santuario с ГОСТ-патчем, КриптоПро
|
||||
JCP. КриптоПро CSP ставится на ВМ. Класс СКЗИ — **КС1** (программная
|
||||
защита, без HSM).
|
||||
Слушает Unix Domain Socket (`/run/bj/crypto.sock` по умолчанию,
|
||||
переопределяется через `BJ_CRYPTO_SOCKET`). gRPC по UDS — без сетевого
|
||||
хопа, минимальная задержка.
|
||||
|
||||
Канал общения с Go-ядром — gRPC по Unix Domain Socket (без сетевого хопа,
|
||||
минимальная задержка).
|
||||
Стек: Java 21 (Liberica JDK), Apache Santuario с ГОСТ-патчем,
|
||||
КриптоПро JCP. Класс СКЗИ — **КС1** (программная защита, без HSM).
|
||||
|
||||
Архитектурно сохранена `Provider`-абстракция (Валидата JCP / VipNet и
|
||||
прочее) — для тиражирования другим компаниям. На M1 реализован только
|
||||
КриптоПро.
|
||||
## Состав
|
||||
|
||||
Реализация — задача M1-M2 (заглушка gRPC) и M3-M4 (полный функционал).
|
||||
- `proto/crypto.proto` — protobuf-контракт: `VerifyXMLDSig`,
|
||||
`SignXMLDSig`, `Health`. Go-package `cryptopb`, Java-package
|
||||
`ru.zetit.bridgeandjoins.crypto.v1`.
|
||||
- `build.gradle.kts` — Gradle сборка с протобуф-плагином и shadowJar.
|
||||
- `src/main/java/.../CryptoServer.java` — точка входа, поднимает gRPC
|
||||
на UDS (Netty Epoll). Перед стартом удаляет старый сокет.
|
||||
- `src/main/java/.../CryptoServiceImpl.java` — gRPC-биндинг,
|
||||
делегирует на хендлеры.
|
||||
- `src/main/java/.../VerifyHandler.java`, `SignHandler.java`,
|
||||
`HealthHandler.java` — реализации операций (на M1 заглушки).
|
||||
- `src/main/java/.../KeystoreProvider.java` — provider-абстракция
|
||||
(`cryptopro` | `validata` | `vipnet` | `stub`). Архитектурно
|
||||
сохранена для тиражирования другим компаниям.
|
||||
- `Dockerfile` — Liberica JDK 21 → shadowJar → slim-image, запуск под
|
||||
`bj-crypto` (uid 1100).
|
||||
|
||||
## Состояние
|
||||
|
||||
На M1 — **только скелет**. Реальная криптография подключается, когда
|
||||
заказчик предоставит:
|
||||
|
||||
- лицензию и jar КриптоПро JCP (положить в `libs/jcp.jar`);
|
||||
- доступ к Maven Central через прокси zetit для скачивания
|
||||
grpc-java, protobuf-java, Apache Santuario.
|
||||
|
||||
После этого `VerifyHandler` и `SignHandler` реализуются через
|
||||
Apache Santuario с ГОСТ-патчем (см. TODO в коде).
|
||||
|
||||
## Запуск (после подключения зависимостей)
|
||||
|
||||
```bash
|
||||
./gradlew shadowJar
|
||||
java -jar build/libs/crypto-service-0.1.0-all.jar
|
||||
```
|
||||
|
||||
Или в docker-compose:
|
||||
|
||||
```bash
|
||||
podman-compose -f deploy/docker-compose/docker-compose.yml up crypto-service
|
||||
```
|
||||
|
||||
## Go-клиент
|
||||
|
||||
`internal/cryptocli/` — реализация `m2mcore.CryptoVerifier` по UDS.
|
||||
На M1 проверяет доступность сокета и возвращает `ErrNotImplemented`
|
||||
(чтобы отличать «сокет недоступен» от «криптография не подключена»).
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// build.gradle.kts — Gradle build для crypto-service.
|
||||
//
|
||||
// Артефакт: ./build/libs/crypto-service-0.1.0-all.jar (shadow jar).
|
||||
// Запуск: java -jar crypto-service-0.1.0-all.jar (UDS из BJ_CRYPTO_SOCKET).
|
||||
//
|
||||
// Зависимости (Maven Central, подключаются автоматически):
|
||||
// - grpc-java (server + protoc-gen-java)
|
||||
// - protobuf-java
|
||||
// - apache santuario (XMLDSig)
|
||||
// - jcp (КриптоПро JCP) — внешний jar, поставляется заказчиком
|
||||
// отдельно вместе с лицензией; положить в libs/.
|
||||
//
|
||||
// Соберётся, когда Maven Central доступен через прокси zetit
|
||||
// (пока ждём админа).
|
||||
|
||||
plugins {
|
||||
id("java")
|
||||
id("application")
|
||||
id("com.google.protobuf") version "0.9.4"
|
||||
id("com.github.johnrengelman.shadow") version "8.1.1"
|
||||
}
|
||||
|
||||
group = "ru.zetit.bridgeandjoins"
|
||||
version = "0.1.0"
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
flatDir { dirs("libs") } // для jcp.jar
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val grpcVer = "1.62.2"
|
||||
val protobufVer = "3.25.3"
|
||||
|
||||
implementation("io.grpc:grpc-netty-shaded:$grpcVer")
|
||||
implementation("io.grpc:grpc-protobuf:$grpcVer")
|
||||
implementation("io.grpc:grpc-stub:$grpcVer")
|
||||
implementation("com.google.protobuf:protobuf-java:$protobufVer")
|
||||
|
||||
// XMLDSig (с ГОСТ-патчем поставляется в libs/ заказчиком).
|
||||
implementation("org.apache.santuario:xmlsec:3.0.4")
|
||||
|
||||
// КриптоПро JCP — кладётся в services/crypto-service/libs/jcp.jar.
|
||||
// implementation(files("libs/jcp.jar"))
|
||||
|
||||
compileOnly("javax.annotation:javax.annotation-api:1.3.2")
|
||||
implementation("ch.qos.logback:logback-classic:1.5.6")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
|
||||
testImplementation("io.grpc:grpc-testing:$grpcVer")
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("ru.zetit.bridgeandjoins.crypto.CryptoServer")
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = "com.google.protobuf:protoc:3.25.3"
|
||||
}
|
||||
plugins {
|
||||
id("grpc") {
|
||||
artifact = "io.grpc:protoc-gen-grpc-java:1.62.2"
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
all().forEach { task ->
|
||||
task.plugins {
|
||||
id("grpc")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package bridge_and_joins.crypto.v1;
|
||||
|
||||
option go_package = "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/cryptocli/cryptopb;cryptopb";
|
||||
option java_package = "ru.zetit.bridgeandjoins.crypto.v1";
|
||||
option java_multiple_files = true;
|
||||
|
||||
// CryptoService — серверная криптография по ГОСТ через КриптоПро JCP.
|
||||
// Слушает на Unix Domain Socket (по умолчанию /run/bj/crypto.sock).
|
||||
service CryptoService {
|
||||
// Проверка XMLDSig-подписи (ГОСТ или RSA). Возвращает сведения о
|
||||
// подписанте: CN, ИНН (если есть), срок действия сертификата.
|
||||
rpc VerifyXMLDSig(VerifyRequest) returns (VerifyResponse);
|
||||
|
||||
// Подпись XML по ГОСТ — для резервного канала WS ONYX и для
|
||||
// серверной подписи действий оператора в admin-ui.
|
||||
rpc SignXMLDSig(SignRequest) returns (SignResponse);
|
||||
|
||||
// Health-check.
|
||||
rpc Health(HealthRequest) returns (HealthResponse);
|
||||
}
|
||||
|
||||
message VerifyRequest {
|
||||
// Целиком подписанный XML.
|
||||
bytes payload = 1;
|
||||
|
||||
// Профиль ключей и сертификатов: "guest-gost" | "test3-gost" |
|
||||
// "prod-gost" | "guest-rsa" | ... — определяет хранилище и trust store.
|
||||
string profile = 2;
|
||||
}
|
||||
|
||||
message VerifyResponse {
|
||||
// Прошла ли проверка.
|
||||
bool valid = 1;
|
||||
|
||||
// CN из сертификата подписанта.
|
||||
string signer_cn = 2;
|
||||
|
||||
// ИНН из сертификата (если присутствует в OID 1.2.643.3.131.1.1).
|
||||
string signer_inn = 3;
|
||||
|
||||
// Серийный номер сертификата (hex).
|
||||
string serial = 4;
|
||||
|
||||
// Срок действия сертификата (unix epoch, секунды).
|
||||
int64 not_before = 5;
|
||||
int64 not_after = 6;
|
||||
|
||||
// Тексты ошибок проверки (если valid=false).
|
||||
repeated string errors = 7;
|
||||
}
|
||||
|
||||
message SignRequest {
|
||||
// Канонизированный XML, который нужно подписать.
|
||||
bytes payload = 1;
|
||||
|
||||
// Алиас ключа в JCP-keystore.
|
||||
string key_alias = 2;
|
||||
|
||||
// Профиль (тот же что у Verify).
|
||||
string profile = 3;
|
||||
}
|
||||
|
||||
message SignResponse {
|
||||
// Подписанный XML (с детачированной или встроенной подписью —
|
||||
// зависит от профиля).
|
||||
bytes signed_xml = 1;
|
||||
}
|
||||
|
||||
message HealthRequest {}
|
||||
|
||||
message HealthResponse {
|
||||
bool ok = 1;
|
||||
string version = 2;
|
||||
// Активный провайдер криптографии: "cryptopro" | "validata" | "vipnet" | "stub".
|
||||
string provider = 3;
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package ru.zetit.bridgeandjoins.crypto;
|
||||
|
||||
import io.grpc.Server;
|
||||
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
|
||||
import io.grpc.netty.shaded.io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.grpc.netty.shaded.io.netty.channel.epoll.EpollServerDomainSocketChannel;
|
||||
import io.grpc.netty.shaded.io.netty.channel.unix.DomainSocketAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Точка входа crypto-service.
|
||||
*
|
||||
* Слушает Unix Domain Socket по пути из переменной окружения
|
||||
* BJ_CRYPTO_SOCKET (по умолчанию /run/bj/crypto.sock).
|
||||
* Используется UDS вместо TCP — минимальная задержка и изоляция
|
||||
* от сети.
|
||||
*/
|
||||
public final class CryptoServer {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(CryptoServer.class.getName());
|
||||
private static final String DEFAULT_SOCKET = "/run/bj/crypto.sock";
|
||||
|
||||
private CryptoServer() {
|
||||
// utility
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
String socketPath = System.getenv().getOrDefault("BJ_CRYPTO_SOCKET", DEFAULT_SOCKET);
|
||||
String provider = System.getenv().getOrDefault("BJ_CRYPTO_PROVIDER", "stub");
|
||||
|
||||
Files.deleteIfExists(Path.of(socketPath));
|
||||
|
||||
KeystoreProvider keystore = KeystoreProvider.byName(provider);
|
||||
VerifyHandler verifyHandler = new VerifyHandler(keystore);
|
||||
SignHandler signHandler = new SignHandler(keystore);
|
||||
HealthHandler healthHandler = new HealthHandler(provider);
|
||||
|
||||
CryptoServiceImpl service = new CryptoServiceImpl(verifyHandler, signHandler, healthHandler);
|
||||
|
||||
Server server = NettyServerBuilder
|
||||
.forAddress(new DomainSocketAddress(socketPath))
|
||||
.channelType(EpollServerDomainSocketChannel.class)
|
||||
.bossEventLoopGroup(new EpollEventLoopGroup(1))
|
||||
.workerEventLoopGroup(new EpollEventLoopGroup())
|
||||
.addService(service)
|
||||
.build()
|
||||
.start();
|
||||
|
||||
LOG.info("crypto-service: UDS " + socketPath + ", provider=" + provider);
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
LOG.info("crypto-service: graceful shutdown");
|
||||
server.shutdown();
|
||||
}));
|
||||
|
||||
server.awaitTermination();
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package ru.zetit.bridgeandjoins.crypto;
|
||||
|
||||
import io.grpc.Status;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.CryptoServiceGrpc;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.HealthRequest;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.HealthResponse;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.SignRequest;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.SignResponse;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.VerifyRequest;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.VerifyResponse;
|
||||
|
||||
/**
|
||||
* gRPC-биндинг: делегирует на хендлеры. Сам по себе не реализует
|
||||
* криптографию.
|
||||
*/
|
||||
public final class CryptoServiceImpl extends CryptoServiceGrpc.CryptoServiceImplBase {
|
||||
|
||||
private final VerifyHandler verify;
|
||||
private final SignHandler sign;
|
||||
private final HealthHandler health;
|
||||
|
||||
public CryptoServiceImpl(VerifyHandler verify, SignHandler sign, HealthHandler health) {
|
||||
this.verify = verify;
|
||||
this.sign = sign;
|
||||
this.health = health;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyXMLDSig(VerifyRequest request, StreamObserver<VerifyResponse> obs) {
|
||||
try {
|
||||
obs.onNext(verify.handle(request));
|
||||
obs.onCompleted();
|
||||
} catch (Exception e) {
|
||||
obs.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signXMLDSig(SignRequest request, StreamObserver<SignResponse> obs) {
|
||||
try {
|
||||
obs.onNext(sign.handle(request));
|
||||
obs.onCompleted();
|
||||
} catch (Exception e) {
|
||||
obs.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void health(HealthRequest request, StreamObserver<HealthResponse> obs) {
|
||||
obs.onNext(health.handle(request));
|
||||
obs.onCompleted();
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package ru.zetit.bridgeandjoins.crypto;
|
||||
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.HealthRequest;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.HealthResponse;
|
||||
|
||||
/** Хендлер Health: всегда ok=true с указанием активного провайдера. */
|
||||
public final class HealthHandler {
|
||||
|
||||
private static final String VERSION = "0.1.0";
|
||||
|
||||
private final String provider;
|
||||
|
||||
public HealthHandler(String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public HealthResponse handle(HealthRequest request) {
|
||||
return HealthResponse.newBuilder()
|
||||
.setOk(true)
|
||||
.setVersion(VERSION)
|
||||
.setProvider(provider)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package ru.zetit.bridgeandjoins.crypto;
|
||||
|
||||
/**
|
||||
* Provider-абстракция над хранилищем ключей. Реальные реализации
|
||||
* подключаются вместе с соответствующими jar-ами (КриптоПро JCP,
|
||||
* Валидата JCP, ViPNet). На M1 — Stub.
|
||||
*/
|
||||
public interface KeystoreProvider {
|
||||
|
||||
/** Уникальное имя провайдера (используется в HealthResponse.provider). */
|
||||
String name();
|
||||
|
||||
/** Возвращает true, если провайдер инициализирован и готов к работе. */
|
||||
boolean ready();
|
||||
|
||||
/**
|
||||
* Подбирает реализацию по имени из переменной окружения
|
||||
* BJ_CRYPTO_PROVIDER. Неизвестное имя — Stub.
|
||||
*/
|
||||
static KeystoreProvider byName(String name) {
|
||||
switch (name == null ? "" : name) {
|
||||
case "cryptopro": return new CryptoProJcpProvider();
|
||||
case "validata": return new ValidataJcpProvider();
|
||||
case "vipnet": return new VipNetProvider();
|
||||
default: return new StubProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Заглушка по умолчанию. Возвращает ready()=false. */
|
||||
final class StubProvider implements KeystoreProvider {
|
||||
@Override public String name() { return "stub"; }
|
||||
@Override public boolean ready() { return false; }
|
||||
}
|
||||
|
||||
/** КриптоПро JCP. Реализация — после получения jar и лицензии. */
|
||||
final class CryptoProJcpProvider implements KeystoreProvider {
|
||||
// TODO(M2): инициализация Provider'а через Security.addProvider(new ru.CryptoPro.JCP.JCP());
|
||||
@Override public String name() { return "cryptopro"; }
|
||||
@Override public boolean ready() { return false; }
|
||||
}
|
||||
|
||||
/** Валидата JCP. Заглушка. */
|
||||
final class ValidataJcpProvider implements KeystoreProvider {
|
||||
@Override public String name() { return "validata"; }
|
||||
@Override public boolean ready() { return false; }
|
||||
}
|
||||
|
||||
/** ViPNet. Заглушка. */
|
||||
final class VipNetProvider implements KeystoreProvider {
|
||||
@Override public String name() { return "vipnet"; }
|
||||
@Override public boolean ready() { return false; }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package ru.zetit.bridgeandjoins.crypto;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.SignRequest;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.SignResponse;
|
||||
|
||||
/**
|
||||
* Хендлер подписи XML. На M1 — заглушка, возвращает UNIMPLEMENTED.
|
||||
* Реальная реализация подключается, когда лицензия КриптоПро JCP
|
||||
* будет доступна.
|
||||
*/
|
||||
public final class SignHandler {
|
||||
|
||||
private final KeystoreProvider keystore;
|
||||
|
||||
public SignHandler(KeystoreProvider keystore) {
|
||||
this.keystore = keystore;
|
||||
}
|
||||
|
||||
public SignResponse handle(SignRequest request) {
|
||||
// TODO(M2): реальная реализация:
|
||||
// 1. Загружаем ключ по key_alias из keystore (через KeystoreProvider).
|
||||
// 2. Канонизуем payload по xml-exc-c14n.
|
||||
// 3. Создаём <Signature> элемент через Santuario с GOST-алгоритмом.
|
||||
// 4. Возвращаем signed_xml.
|
||||
//
|
||||
// Сейчас — заглушка.
|
||||
|
||||
throw new StatusRuntimeException(Status.UNIMPLEMENTED
|
||||
.withDescription("crypto-service: SignXMLDSig stub — провайдер "
|
||||
+ keystore.name() + " не подключён (ждём PR-6/M2 КриптоПро JCP)"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private SignResponse empty() {
|
||||
return SignResponse.newBuilder().setSignedXml(ByteString.EMPTY).build();
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package ru.zetit.bridgeandjoins.crypto;
|
||||
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.VerifyRequest;
|
||||
import ru.zetit.bridgeandjoins.crypto.v1.VerifyResponse;
|
||||
|
||||
/**
|
||||
* Хендлер проверки XMLDSig-подписи. На M1 — заглушка, возвращающая
|
||||
* valid=false с пояснением. Реальная реализация (Apache Santuario +
|
||||
* КриптоПро JCP) подключается, когда лицензия JCP станет доступна.
|
||||
*/
|
||||
public final class VerifyHandler {
|
||||
|
||||
private final KeystoreProvider keystore;
|
||||
|
||||
public VerifyHandler(KeystoreProvider keystore) {
|
||||
this.keystore = keystore;
|
||||
}
|
||||
|
||||
public VerifyResponse handle(VerifyRequest request) {
|
||||
// TODO(M2): реальная реализация через Apache Santuario.
|
||||
//
|
||||
// Шаги при подключении КриптоПро JCP:
|
||||
// 1. Парсим XML, ищем элемент <Signature>.
|
||||
// 2. Канонизуем по xml-exc-c14n.
|
||||
// 3. Берём сертификат из KeyInfo, проверяем цепочку через
|
||||
// trust store профиля.
|
||||
// 4. Проверяем подпись (alg=GOST34112012-256/-512).
|
||||
// 5. Извлекаем CN, ИНН из subject.
|
||||
//
|
||||
// Сейчас — заглушка.
|
||||
|
||||
return VerifyResponse.newBuilder()
|
||||
.setValid(false)
|
||||
.addErrors("crypto-service: VerifyXMLDSig stub — провайдер " + keystore.name()
|
||||
+ " не подключён (ждём PR-6/M2 КриптоПро JCP)")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user